From 078919812a294cd4ae436d178ab6d777934c228b Mon Sep 17 00:00:00 2001 From: Tony Mocanu <64985430+anmocanu@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:10:05 +0200 Subject: [PATCH 1/2] Updated the GA_offlinefixer script as per TOOLING 59907 and renamed it to GA_offlinefixer.ps1 Updated the GA_offlinefixer script as per TOOLING 59907 and renamed it to GA_offlinefixer.ps1 https://dev.azure.com/Azure-VM-POD/Verticals/_workitems/edit/59907 --- src/windows/GA_offlinefixer.ps1 | 125 +++++++++++++++++++++++ src/windows/GA_offlinefixer_damunozl.ps1 | 85 --------------- 2 files changed, 125 insertions(+), 85 deletions(-) create mode 100644 src/windows/GA_offlinefixer.ps1 delete mode 100644 src/windows/GA_offlinefixer_damunozl.ps1 diff --git a/src/windows/GA_offlinefixer.ps1 b/src/windows/GA_offlinefixer.ps1 new file mode 100644 index 0000000..2d22919 --- /dev/null +++ b/src/windows/GA_offlinefixer.ps1 @@ -0,0 +1,125 @@ +<# +.SYNOPSIS + VMAgent Offline Fixer - Comprehensive Version. + - Fixes Registry and Binaries for Guest Agent and RDAgent. + - Automatically detects and updates active/backup ControlSets (001 and 002). + - Implements strict validation and handle releasing for clean hive unloads. + - Created by Tony.Mocanu@Microsoft.com +#> + +# 1. Import common logic +. .\src\windows\common\setup\init.ps1 +. .\src\windows\common\helpers\Get-Disk-Partitions.ps1 + +# Define log path (SystemDrive is safest for SYSTEM account) +$logFile = "$env:SystemDrive\VMAgent-Fix.log" + +try { + Log-Info "Starting VMAgent Offline Fixer..." | Tee-Object -FilePath $logFile -Append + + # 2. Finder for faulty OS letter + $diskb = "000" + $diskarray = "d","q","w","e","r","t","y","u","i","o","p","s","f","g","h","j","k","l","z","x","v","n","m" + foreach ($diskt in $diskarray) { + if (Test-Path -Path "$($diskt):\Windows") { $diskb = $diskt; break } + } + + if ($diskb -eq "000") { throw "Could not find a rescue OS disk attached." } + Log-Info "Target OS disk found on letter: $($diskb):" | Tee-Object -FilePath $logFile -Append + + # 3. Hive Management (Safety Unload & Load) + & reg.exe unload "HKLM\BROKENSYSTEM" 2>$null + Log-Info "Loading SYSTEM hive..." | Tee-Object -FilePath $logFile -Append + $loadResult = & reg.exe load "HKLM\BROKENSYSTEM" "$($diskb):\Windows\System32\config\SYSTEM" 2>&1 + if ($LASTEXITCODE -ne 0) { throw "Failed to load Registry Hive: $loadResult" } + Start-Sleep -Seconds 2 + + # 4. Registry Injection - Dual ControlSet Logic + # Identify which ControlSet is the default boot set + $selectPath = "Registry::HKLM\BROKENSYSTEM\Select" + $defaultSetID = (Get-ItemProperty -path $selectPath).default + $primarySet = "ControlSet00$defaultSetID" + $otherSet = if ($primarySet -eq "ControlSet001") { "ControlSet002" } else { "ControlSet001" } + + Log-Info "Primary ControlSet identified: $primarySet" | Tee-Object -FilePath $logFile -Append + + $services = @("WindowsAzureGuestAgent", "WindowsAzureTelemetryService", "RdAgent") + + foreach ($service in $services) { + $regFile = "$($diskb):\$service.reg" + # Export healthy key from the current Rescue VM + & reg.exe export "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\$service" "$regFile" /y 2>$null + + if (Test-Path $regFile) { + $originalContent = Get-Content $regFile + + # Update Primary Set + Log-Info "Updating $service in $primarySet..." | Tee-Object -FilePath $logFile -Append + $content = $originalContent -replace 'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet', "HKEY_LOCAL_MACHINE\BROKENSYSTEM\$primarySet" + $content | Set-Content $regFile + & reg.exe import $regFile 2>&1 | Out-Null + + # Update Secondary Set (if it exists on disk) + if (Test-Path "Registry::HKLM\BROKENSYSTEM\$otherSet") { + Log-Info "Updating $service in backup $otherSet..." | Tee-Object -FilePath $logFile -Append + $content = $originalContent -replace 'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet', "HKEY_LOCAL_MACHINE\BROKENSYSTEM\$otherSet" + $content | Set-Content $regFile + & reg.exe import $regFile 2>&1 | Out-Null + } + Remove-Item $regFile -Force + } + } + + # 5. Strict Verification of Changes + $wagaPath = "HKLM\BROKENSYSTEM\$primarySet\Services\WindowsAzureGuestAgent" + $afterImagePath = (Get-ItemProperty -Path "Registry::$wagaPath" -ErrorAction SilentlyContinue).ImagePath + if ([string]::IsNullOrWhiteSpace($afterImagePath)) { + throw "Verification Failed: VMAgent ImagePath is empty after injection attempt." + } + Log-Info "Verification Success: ImagePath is now $afterImagePath" | Tee-Object -FilePath $logFile -Append + + # 6. Binary Copy (GuestAgent Folders Only) + Log-Info "Restoring GuestAgent binaries from Troubleshooter..." | Tee-Object -FilePath $logFile -Append + $sourcePath = "C:\WindowsAzure" + $destPath = "$($diskb):\WindowsAzure" + if (-not (Test-Path $destPath)) { New-Item -Path $destPath -ItemType Directory | Out-Null } + + $agentFolders = Get-ChildItem -Path $sourcePath -Directory -Filter "GuestAgent_*" + foreach ($folder in $agentFolders) { + Log-Info "Copying $($folder.Name) to target..." | Tee-Object -FilePath $logFile -Append + Copy-Item -Path $folder.FullName -Destination $destPath -Recurse -Force -ErrorAction SilentlyContinue + } + + # 7. Release Handles and Unload + Log-Info "Releasing registry handles and unloading hive..." | Tee-Object -FilePath $logFile -Append + [System.GC]::Collect() + [System.GC]::WaitForPendingFinalizers() + Start-Sleep -Seconds 3 + + $unloaded = $false + for ($i=1; $i -le 3; $i++) { + & reg.exe unload "HKLM\BROKENSYSTEM" 2>&1 | Out-Null + if ($LASTEXITCODE -eq 0) { $unloaded = $true; break } + Log-Warning "Unload attempt $i failed, retrying..." | Tee-Object -FilePath $logFile -Append + Start-Sleep -Seconds 5 + } + + if (-not $unloaded) { throw "Critical Failure: Could not unload BROKENSYSTEM hive." } + + Log-Output "VMAgent Fix completed and verified successfully." | Tee-Object -FilePath $logFile -Append + return $STATUS_SUCCESS + +} +catch { + $errorMessage = $_.Exception.Message + Log-Error "SCRIPT FAILED: $errorMessage" | Tee-Object -FilePath $logFile -Append + + # Final emergency attempt to unload hive so disk can detach + [System.GC]::Collect() + & reg.exe unload "HKLM\BROKENSYSTEM" 2>$null + + return $STATUS_ERROR +} +finally { + Log-Info "Execution ended at $(Get-Date)" | Tee-Object -FilePath $logFile -Append +} diff --git a/src/windows/GA_offlinefixer_damunozl.ps1 b/src/windows/GA_offlinefixer_damunozl.ps1 deleted file mode 100644 index 824a063..0000000 --- a/src/windows/GA_offlinefixer_damunozl.ps1 +++ /dev/null @@ -1,85 +0,0 @@ -# Welcome to the VM Azure Agent Offline fixer by Daniel Muñoz L! -# Contact me Daniel Muñoz L : damunozl@microsoft.com if questions. -# .SUMMARY -# Fixes integrity of files and registry from windows guest agent offline. -# Backs up registry. Copies installation folder and registry values related to Windows Guest Agent to attached disk. -# Public doc: https://learn.microsoft.com/en-us/troubleshoot/azure/virtual-machines/windows/install-vm-agent-offline -# -# .RESOLVES -# The server needs to use the Guest Agent (e.g. for a password reset) but the user is currently unable to because -# the Guest Agent is not installed. After performing this fix, the Guest Agent will effectively be installed on the attached OS disk as well. -# The Azure Virtual Machine Agent (VM Agent) provides useful features, such as local administrator password reset and script pushing. - - -out-null -cmd /c color 0A -$host.UI.RawUI.WindowTitle = " --== VMAgent Offline Fixer by Daniel Muñoz L ==--" - -# Rescue OS variable -$diska='c' - -# FINDER FOR FAULTY OS DRIVE -$diskarray = "d","q","w","e","r","t","y","u","i","o","p","s","f","g","h","j","k","l","z","x","v","n","m" -$diskb="000" -foreach ($diskt in $diskarray) -{ - if (Test-Path -Path "$($diskt):\Windows") - { - $diskb=$diskt - } -} - -# IN CASE OF FINDER FAILURE -if ($diskb -eq "000") {write-output "SCRIPT COULD NOT FIND A RESCUE OS DISK ATTACHED, EXITING";start-sleep 10;Exit} - -# HIVE LOADER -# A Backup of the BROKENSYSTEM was taken and left on $($diskb):\ as regbackupbeforeGAchanges just in case! -reg load "HKLM\BROKENSYSTEM" "$($diskb):\Windows\System32\config\SYSTEM" -reg export "HKLM\BROKENSYSTEM" "$($diskb):\regbackupbeforeGAchanges" /y - -# EXPORTING GOOD VMAGENT REGS FROM RESCUE VM -reg export "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\WindowsAzureGuestAgent" "$($diskb):\WAGA.reg" /y -# Telemetry service was merged into rdagent so you can expect an error from wats -reg export "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\WindowsAzureTelemetryService" "$($diskb):\WATS.reg" /y -reg export "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\RdAgent" "$($diskb):\RdAgent.reg" /y - -# MODIFYING REG FILES WITH GOOD REG KEYS -# WAAGENT MODIFICATIONS -(gc "$($diskb):\waga.reg") -replace 'LocalSystem', 'notyet' | Out-File "$($diskb):\waga.reg" -(gc "$($diskb):\waga.reg") -replace 'system', 'BROKENSYSTEM' | Out-File "$($diskb):\waga.reg" -(gc "$($diskb):\waga.reg") -replace 'notyet', 'localsystem' | Out-File "$($diskb):\waga.reg" - -# TELEMETRY MODIFICATIONS -# Telemetry service was merged into rdagent so an error might be expected from wats key -(gc "$($diskb):\wats.reg") -replace 'LocalSystem', 'notyet' | Out-File "$($diskb):\wats.reg" -(gc "$($diskb):\wats.reg") -replace 'system', 'BROKENSYSTEM' | Out-File "$($diskb):\wats.reg" -(gc "$($diskb):\wats.reg") -replace 'notyet', 'localsystem' | Out-File "$($diskb):\wats.reg" - -# RDAGENT MODIFICATIONS -(gc "$($diskb):\RdAgent.reg") -replace 'LocalSystem', 'notyet' | Out-File "$($diskb):\RdAgent.reg" -(gc "$($diskb):\RdAgent.reg") -replace 'system', 'BROKENSYSTEM' | Out-File "$($diskb):\RdAgent.reg" -(gc "$($diskb):\RdAgent.reg") -replace 'notyet', 'localsystem' | Out-File "$($diskb):\RdAgent.reg" - -# ADDING REG FILES IN %diskb% DRIVE -regedit /s "$($diskb):\WATS.reg" -regedit /s "$($diskb):\WAGA.reg" -regedit /s "$($diskb):\RdAgent.reg" - -# BACKUP TAKEN ON FOLDER $($diskb):\WindowsazurefaultyGAbackup" -mkdir "$($diskb):\WindowsazurefaultyGAbackup" -xcopy "$($diskb):\WindowsAzure" "$($diskb):\WindowsazurefaultyGAbackup" /e /h /y - -# RESTORING VMAgent BIN FILES -del "$($diskb):\WindowsAzure" -force -recurse -mkdir "$($diskb):\WindowsAzure" -xcopy "$($diska):\WindowsAzure" "$($diskb):\WindowsAzure" /e /h /y -del "$($diskb):\WindowsAzure\logs" -force -recurse - -# Unloading HIVE" -reg.exe unload "HKLM\BROKENSYSTEM" -del "$($diskb):\rdagent.reg" -force -del "$($diskb):\waga.reg" -force -del "$($diskb):\wats.reg" -force - -write-output " -------------- SCRIPT FINISHED PROPERLY, BACKUP OF PREVIOUS GA IS IN ROOT AS WindowsazurefaultyGAbackup folder and registry backup as regbackupbeforeGAchanges -------------- " -start-sleep 10 From 756b945ca9201af098f3a41c694e50b72731cc25 Mon Sep 17 00:00:00 2001 From: Tony Mocanu <64985430+anmocanu@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:14:00 +0200 Subject: [PATCH 2/2] Rename GA_offlinefixer_damunozl.ps1 to GA_offlinefixer.ps1 in map.json Rename GA_offlinefixer_damunozl.ps1 to GA_offlinefixer.ps1 in map.json to point to the new updated script as per https://dev.azure.com/Azure-VM-POD/Verticals/_workitems/edit/59907 --- map.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map.json b/map.json index b5b7dee..6d2707e 100644 --- a/map.json +++ b/map.json @@ -21,7 +21,7 @@ }, { "id" : "win-GA-fix", - "path" : "src/windows/GA_offlinefixer_damunozl.ps1", + "path" : "src/windows/GA_offlinefixer.ps1", "description" : "Fixes integrity of files and registry from windows guest agent offline" }, {