88 Created: 2026-04-16
99 Modified: 2026-07-17
1010 File: Private/Invoke-InstallerWatchdog.ps1
11- Version: 1.1.0
12- Description: Monitors the VS Code installer and related worker processes for CPU and disk activity,
13- detects idle or stalled states, and terminates processes when the installer becomes
14- unresponsive. Uses real-time loop deltas and basic invariants for stability.
11+ Version: 2.2.0
12+ Description: Monitors the VS Code installer using deterministic module‑based progress detection.
13+ Tracks child‑process module loads, unloads, and phase transitions to identify real
14+ installer activity, replacing filesystem‑scanning heuristics with noise‑free,
15+ phase‑aware stall detection. Terminates the installer only when module state and
16+ CPU/disk activity indicate a true stall.
1517#>
1618# PSScriptAnalyzer SuppressMessage = PSUseApprovedVerbs "Intentional verb"
19+ # PSScriptAnalyzer SuppressMessage = PSUseConsistentWhitespace
20+ # PSScriptAnalyzer SuppressMessage = PSUseConsistentIndentation
1721function Invoke-InstallerWatchdog {
1822 [OutputType ([Int32 ])]
1923 param (
@@ -22,167 +26,128 @@ function Invoke-InstallerWatchdog {
2226 [int ]$IdleTimeout
2327 )
2428
29+ # Validate parent
2530 if ($ParentPID -eq 0 -or -not (Get-Process - Id $ParentPID - ErrorAction SilentlyContinue)) {
2631 Write-VSCodeUpdaterLog " [WATCHDOG] Invalid parent PID ($ParentPID ) — aborting watchdog"
2732 return [WatchdogExitCode ]::Unknown
2833 }
2934
35+ # Safe mode bypass
3036 if ($script :SafeMode ) {
3137 Write-VSCodeUpdaterLog " [WATCHDOG] SAFE MODE — watchdog disabled"
3238 return [WatchdogExitCode ]::Success
3339 }
3440
35- $idleSeconds = 0.0
36- $activeSeconds = 0.0
37- $fsIdleSeconds = 0.0
38- $lastState = " "
39- $lastCPU = 0.0
40- $lastDisk = 0
41-
42- # Detect real VS Code install path from the 'code' command
43- $codeCmd = Get-Command code - ErrorAction SilentlyContinue
44-
45- $installPaths = @ ()
46-
47- if ($codeCmd -and $codeCmd.Source ) {
48- $installPaths += (Split-Path $codeCmd.Source - Parent)
49- }
41+ # Module tracking (v3.0)
42+ $previousModules = @ ()
43+ $moduleInitialized = $false
44+ $currentPhase = " Bootstrap"
5045
51- # Fallbacks
52- $installPaths += @ (
53- " $env: LOCALAPPDATA \Programs\Microsoft VS Code" ,
54- " $env: LOCALAPPDATA \Programs\VSCode" ,
55- $env: TEMP
56- )
57-
58- $lastWriteTime = Get-Date
59- $fsLogCooldown = 30
60- $lastFsLog = (Get-Date ).AddSeconds(-10 )
46+ # Final install directory detection
47+ $finalInstallRoot = (Join-Path $env: LOCALAPPDATA " Programs\Microsoft VS Code" )
6148
49+ # Idle timer
50+ $idleSeconds = 0.0
6251 $lastLoopTime = Get-Date
6352
6453 Write-VSCodeUpdaterLog " [WATCHDOG] Monitoring child PID $ ( $ChildProcess.Id ) , parent PID $ParentPID "
6554
66- # Grace period: let installer initialize
55+ # Grace period
6756 Start-Sleep - Seconds 3
68- $fsIdleSeconds = 0.0
6957 $idleSeconds = 0.0
70- $activeSeconds = 0.0
7158
7259 while ($true ) {
73- $now = Get-Date
60+
61+ # Loop delta
62+ $now = Get-Date
7463 $delta = ($now - $lastLoopTime ).TotalSeconds
7564 if ($delta -lt 0 ) { $delta = 0 }
7665 $lastLoopTime = $now
7766
78- $fsIdleSeconds += $delta
7967 $idleSeconds += $delta
80- $activeSeconds += $delta
8168
69+ # Refresh child
8270 $child = Get-Process - Id $ChildProcess.Id - ErrorAction SilentlyContinue
8371
8472 if (-not $child ) {
85- $newChild = Get-Process - ErrorAction SilentlyContinue |
86- Where-Object { $_.Parent.Id -eq $ParentPID }
87-
88- if ($newChild ) {
89- Write-VSCodeUpdaterLog " [WATCHDOG] Child replaced — new PID $ ( $newChild.Id ) "
90- $ChildProcess = $newChild
91- continue
92- }
93-
94- Write-VSCodeUpdaterLog " [WATCHDOG] Child exited — success"
73+ Write-VSCodeUpdaterLog " [WATCHDOG] Child exited — installer completed"
9574 return [WatchdogExitCode ]::Success
9675 }
9776
77+ #
78+ # MODULE-BASED PROGRESS DETECTION (v3.0)
79+ #
9880 try {
99- $latestWrite = $null
100-
101- foreach ($path in $installPaths ) {
102- try {
103- if (Test-Path $path ) {
104- $candidate = Get-ChildItem - Recurse $path - File - ErrorAction SilentlyContinue |
105- Where-Object {
106- $_.Extension -notin ' .log' , ' .tmp' , ' .bak' -and
107- $_.FullName -notmatch ' \\logs?\\' -and
108- $_.FullName -notmatch ' \\Crashpad\\'
109- } |
110- Sort-Object LastWriteTime |
111- Select-Object - Last 1
112-
113- if ($candidate -and (! $latestWrite -or $candidate.LastWriteTime -gt $latestWrite.LastWriteTime )) {
114- $latestWrite = $candidate
115- }
116- }
117- }
118- catch {
119- Write-VSCodeUpdaterLog " [WATCHDOG] FS scan exception on $ ( $path ) : $ ( $_.Exception.Message ) "
120- }
81+ # Normalize module paths
82+ $currentModules = $child.Modules.FileName |
83+ ForEach-Object { $_.ToLowerInvariant () }
84+
85+ if (-not $moduleInitialized ) {
86+ Write-VSCodeUpdaterLog " [WATCHDOG] Module tracking initialized ($ ( $currentModules.Count ) modules)"
87+ $previousModules = $currentModules
88+ $moduleInitialized = $true
12189 }
90+ else {
91+ # Single delta calculation
92+ $moduleDelta = Compare-Object $previousModules $currentModules
12293
123- if ($latestWrite -and $latestWrite.LastWriteTime -gt $lastWriteTime ) {
124- if ((Get-Date ) -gt $lastFsLog.AddSeconds ($fsLogCooldown )) {
125- Write-VSCodeUpdaterLog " [WATCHDOG] FS activity: $ ( $latestWrite.FullName ) "
126- $lastFsLog = Get-Date
127- }
94+ if ($moduleDelta ) {
95+ Write-VSCodeUpdaterLog " [WATCHDOG] Module change detected"
96+ $previousModules = $currentModules
97+ $idleSeconds = 0.0
12898
129- $lastWriteTime = $latestWrite.LastWriteTime
130- $fsIdleSeconds = 0.0
131- $activeSeconds = 0.0
132- $idleSeconds = 0.0
99+ #
100+ # PHASE DETECTION (v3.0)
101+ #
102+ $newPhase = $currentPhase
103+
104+ if ($currentModules -match ' \\nsm.*\.tmp\\' ) {
105+ $newPhase = " Extraction"
106+ }
107+ elseif ($currentModules -match ' chrome_elf\.dll' -or
108+ $currentModules -match ' node\.dll' ) {
109+ $newPhase = " Payload"
110+ }
111+ elseif ($currentModules -match ' \microsoft vs code\\' ) {
112+ $newPhase = " Finalization"
113+ }
114+
115+ if ($newPhase -ne $currentPhase ) {
116+ $currentPhase = $newPhase
117+ Write-VSCodeUpdaterLog " [WATCHDOG] Phase: $currentPhase "
118+ }
119+ }
133120 }
134121 }
135122 catch {
136- Write-VSCodeUpdaterLog " [WATCHDOG] Exception : $ ( $_.Exception.Message ) "
123+ Write-VSCodeUpdaterLog " [WATCHDOG] Module polling exception : $ ( $_.Exception.Message ) "
137124 }
138125
139- if ($fsIdleSeconds -ge $IdleTimeout ) {
140- Write-VSCodeUpdaterLog " [WATCHDOG] FS stall after {0:N2}s — killing installer" -f $fsIdleSeconds
141- Stop-Process - Id $ChildProcess.Id - Force - ErrorAction SilentlyContinue
142- Stop-Process - Id $ParentPID - Force - ErrorAction SilentlyContinue
143- return [WatchdogExitCode ]::FSStalled
144- }
145-
146- $cpuNow = $child.CPU
147- $diskNow = $child.IOReadBytes + $child.IOWriteBytes
126+ #
127+ # FINALIZATION SUCCESS DETECTION (v3.0)
128+ #
129+ $finalModulesLoaded = $currentModules |
130+ Where-Object { $_ -like " $ ( $finalInstallRoot.ToLowerInvariant ()) \*" }
148131
149- $cpuDelta = $cpuNow - $lastCPU
150- $diskDelta = $diskNow - $lastDisk
151-
152- $lastCPU = $cpuNow
153- $lastDisk = $diskNow
132+ if ($finalModulesLoaded.Count -gt 0 -and $currentPhase -eq " Finalization" ) {
133+ Write-VSCodeUpdaterLog " [WATCHDOG] Final modules loaded — installer completed"
134+ return [WatchdogExitCode ]::Success
135+ }
154136
155- if ($cpuDelta -eq 0 -and $diskDelta -eq 0 ) {
156- if ($lastState -ne " Idle" ) {
157- Write-VSCodeUpdaterLog " [WATCHDOG] Child transitioned to idle"
158- $lastState = " Idle"
159- }
137+ #
138+ # PURE MODULE-ONLY STALL DETECTION (v3.0)
139+ #
140+ $moduleDelta = Compare-Object $previousModules $currentModules
160141
142+ if (-not $moduleDelta ) {
161143 if ($idleSeconds -ge $IdleTimeout ) {
162- Write-VSCodeUpdaterLog " [WATCHDOG] Idle stall after {0:N2}s — killing parent " -f $idleSeconds
163- Stop-Process - Id $ParentPID - Force - ErrorAction SilentlyContinue
164- Wait -Process - Id $ChildProcess .Id - ErrorAction SilentlyContinue
144+ Write-VSCodeUpdaterLog " [WATCHDOG] Deterministic stall — no module changes for $IdleTimeout seconds "
145+ Stop-Process - Id $ChildProcess .Id - Force - ErrorAction SilentlyContinue
146+ Stop -Process - Id $ParentPID - Force - ErrorAction SilentlyContinue
165147 return [WatchdogExitCode ]::IdleStalled
166148 }
167149 }
168150 else {
169- if ($lastState -ne " Active" ) {
170- Write-VSCodeUpdaterLog " [WATCHDOG] Child transitioned to active"
171- $lastState = " Active"
172- }
173-
174- if ($cpuDelta -eq 0 -and $diskDelta -eq 0 ) {
175- if ($activeSeconds -ge $IdleTimeout ) {
176- Write-VSCodeUpdaterLog " [WATCHDOG] Active stall after {0:N2}s — killing parent and child" -f $activeSeconds
177- Stop-Process - Id $ChildProcess.Id - Force - ErrorAction SilentlyContinue
178- Stop-Process - Id $ParentPID - Force - ErrorAction SilentlyContinue
179- return [WatchdogExitCode ]::ActiveStalled
180- }
181- }
182- else {
183- $activeSeconds = 0.0
184- }
185-
186151 $idleSeconds = 0.0
187152 }
188153
0 commit comments