|
5 | 5 | using GVFS.Platform.Windows; |
6 | 6 | using GVFS.Service.Handlers; |
7 | 7 | using System; |
| 8 | +using System.Collections.Generic; |
8 | 9 | using System.IO; |
9 | 10 | using System.Linq; |
10 | 11 | using System.Security.AccessControl; |
@@ -46,6 +47,46 @@ public void Run() |
46 | 47 | metadata.Add("Version", ProcessHelper.GetCurrentProcessVersion()); |
47 | 48 | this.tracer.RelatedEvent(EventLevel.Informational, $"{nameof(GVFSService)}_{nameof(this.Run)}", metadata); |
48 | 49 |
|
| 50 | + // Set up deferred telemetry pipe attachment FIRST, before any |
| 51 | + // telemetry-emitting work (particularly PendingUpgradeHandler, |
| 52 | + // whose Deferred/Complete events we want to capture). |
| 53 | + // |
| 54 | + // The service runs as SYSTEM and can't read the user's global |
| 55 | + // git config (where gvfs.telemetry-pipe is configured) at |
| 56 | + // startup. The DeferredTelemetryAttacher adds a |
| 57 | + // BufferingTelemetryListener that captures events in memory, |
| 58 | + // then replays them once the real pipe listener attaches. |
| 59 | + // |
| 60 | + // Three attach paths exist: |
| 61 | + // 1. TryAttachTelemetryPipeForAnySessions() below — tries |
| 62 | + // all Active/Disconnected sessions immediately. |
| 63 | + // 2. OnSessionChange (SessionLogon) — fires when a new user |
| 64 | + // logs in after the service is already running. |
| 65 | + // 3. StartRetryTimer — periodic retry (10s, 30s, 1m, 5m) |
| 66 | + // as a fallback, reads system config only (no user |
| 67 | + // config available without a session to impersonate). |
| 68 | + string gitBinRoot = GVFSPlatform.Instance.GitInstallation.GetInstalledGitBinPath(); |
| 69 | + if (!string.IsNullOrEmpty(gitBinRoot)) |
| 70 | + { |
| 71 | + this.telemetryAttacher = new DeferredTelemetryAttacher( |
| 72 | + this.tracer, |
| 73 | + GVFSConstants.Service.ServiceName, |
| 74 | + enlistmentId: null, |
| 75 | + mountId: null); |
| 76 | + this.telemetryAttacher.StartRetryTimer(gitBinRoot); |
| 77 | + |
| 78 | + // If a user is already logged in (e.g. service restart |
| 79 | + // during active session), try attaching immediately by |
| 80 | + // enumerating all interactive sessions. This is needed |
| 81 | + // because WTSGetActiveConsoleSessionId only returns the |
| 82 | + // physical console session, which on Cloud PCs / DevBoxes |
| 83 | + // (RDP-only) has no logged-in user. The actual user is |
| 84 | + // in an RDP session that the console-only check misses. |
| 85 | + // SessionLogon events also won't fire for sessions that |
| 86 | + // were already established before the service started. |
| 87 | + this.TryAttachTelemetryPipeForAnySessions(); |
| 88 | + } |
| 89 | + |
49 | 90 | // Check for a staged upgrade before doing anything else. |
50 | 91 | // If no GVFS.Mount processes are running (typical at boot or after |
51 | 92 | // unmount-all), copy staged files in-place and proceed normally. |
@@ -77,29 +118,6 @@ public void Run() |
77 | 118 | { |
78 | 119 | this.CheckEnableGitStatusCacheTokenFile(); |
79 | 120 |
|
80 | | - // Set up deferred telemetry pipe attachment. The service |
81 | | - // runs as SYSTEM and can't read the user's global git |
82 | | - // config at startup, so the daemon listener can't be |
83 | | - // created in the JsonTracer constructor. |
84 | | - string gitBinRoot = GVFSPlatform.Instance.GitInstallation.GetInstalledGitBinPath(); |
85 | | - if (!string.IsNullOrEmpty(gitBinRoot)) |
86 | | - { |
87 | | - this.telemetryAttacher = new DeferredTelemetryAttacher( |
88 | | - this.tracer, |
89 | | - GVFSConstants.Service.ServiceName, |
90 | | - enlistmentId: null, |
91 | | - mountId: null); |
92 | | - this.telemetryAttacher.StartRetryTimer(gitBinRoot); |
93 | | - |
94 | | - // If a user is already logged in (e.g. service restart |
95 | | - // during active session), try attaching immediately. |
96 | | - int activeSession = NativeMethods.GetActiveConsoleSessionId(); |
97 | | - if (activeSession > 0) |
98 | | - { |
99 | | - this.TryAttachTelemetryPipeForSession(activeSession); |
100 | | - } |
101 | | - } |
102 | | - |
103 | 121 | using (ITracer activity = this.tracer.StartActivity("EnsurePrjFltHealthy", EventLevel.Informational)) |
104 | 122 | { |
105 | 123 | // Make a best-effort to enable PrjFlt. Continue even if it fails. |
@@ -475,6 +493,50 @@ private void TryAttachTelemetryPipeForSession(int sessionId) |
475 | 493 | } |
476 | 494 | } |
477 | 495 |
|
| 496 | + /// <summary> |
| 497 | + /// Enumerates all interactive sessions (Active and Disconnected) |
| 498 | + /// and tries to attach the telemetry pipe using each session's |
| 499 | + /// user profile. Stops on the first successful attach. |
| 500 | + /// </summary> |
| 501 | + /// <remarks> |
| 502 | + /// This method exists because the console-only check |
| 503 | + /// (<c>WTSGetActiveConsoleSessionId</c>) fails on Cloud PCs and |
| 504 | + /// RDP-only machines where the console session is in the Connected |
| 505 | + /// state (login screen, no user). Disconnected sessions are also |
| 506 | + /// checked because an RDP user who disconnected without logging |
| 507 | + /// off still has a valid token and git config. |
| 508 | + /// </remarks> |
| 509 | + private void TryAttachTelemetryPipeForAnySessions() |
| 510 | + { |
| 511 | + if (this.telemetryAttacher == null || this.telemetryAttacher.IsAttached) |
| 512 | + { |
| 513 | + return; |
| 514 | + } |
| 515 | + |
| 516 | + List<int> sessionIds = CurrentUser.GetInteractiveSessionIds(this.tracer); |
| 517 | + if (sessionIds.Count == 0) |
| 518 | + { |
| 519 | + this.tracer.RelatedInfo("TryAttachTelemetryPipeForAnySessions: No interactive sessions found"); |
| 520 | + return; |
| 521 | + } |
| 522 | + |
| 523 | + foreach (int sessionId in sessionIds) |
| 524 | + { |
| 525 | + this.TryAttachTelemetryPipeForSession(sessionId); |
| 526 | + if (this.telemetryAttacher.IsAttached) |
| 527 | + { |
| 528 | + break; |
| 529 | + } |
| 530 | + } |
| 531 | + |
| 532 | + if (!this.telemetryAttacher.IsAttached) |
| 533 | + { |
| 534 | + this.tracer.RelatedWarning( |
| 535 | + "TryAttachTelemetryPipeForAnySessions: Could not attach from any of {0} interactive session(s)", |
| 536 | + sessionIds.Count); |
| 537 | + } |
| 538 | + } |
| 539 | + |
478 | 540 | private DirectorySecurity GetServiceDirectorySecurity(string serviceDataRootPath) |
479 | 541 | { |
480 | 542 | DirectorySecurity serviceDataRootSecurity; |
|
0 commit comments