Files
awoooi/scripts/reboot-recovery/windows99-vmware-autostart.ps1
ogt c1131fc76c
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m30s
CD Pipeline / build-and-deploy (push) Successful in 6m30s
CD Pipeline / post-deploy-checks (push) Successful in 1m57s
fix(recovery): detect cross-session vmware guests
2026-07-11 19:26:22 +08:00

439 lines
16 KiB
PowerShell

param(
[ValidateSet("Verify", "DryRun", "Apply")]
[string]$Mode = "Verify",
[string]$VmrunPath = "C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe",
[string]$Host110Vmx = "",
[string]$Host111Vmx = "",
[string]$Host188Vmx = "",
[string]$Host120Vmx = "",
[string]$Host121Vmx = "",
[string]$Host112Vmx = "",
[string[]]$RequiredVmAliases = @("111", "188", "120", "121", "112"),
[switch]$EnableRecursiveDiscovery,
[string[]]$DiscoveryRoot = @(
"D:\Documents\Virtual Machines",
"D:\Downloads",
"D:\VMs",
"E:\VMs",
"C:\VMs",
"C:\Users\Public\Documents\Virtual Machines"
)
)
$ErrorActionPreference = "Stop"
$TaskName = "AWOOOI-Start-VMware-VMs"
$ProgramDataDir = "C:\ProgramData\AWOOOI"
$StartScript = Join-Path $ProgramDataDir "Start-AWOOOI-VMs.ps1"
$VmOrder = @($RequiredVmAliases)
$SuppliedVmx = @{
"110" = $Host110Vmx
"111" = $Host111Vmx
"188" = $Host188Vmx
"120" = $Host120Vmx
"121" = $Host121Vmx
"112" = $Host112Vmx
}
$KnownVmxCandidates = @{
"110" = @("D:\Documents\Virtual Machines\Ubuntu 64-bit (3)\Ubuntu 64-bit (3).vmx")
"111" = @("D:\Documents\Virtual Machines\192.168.0.111_Ubuntu_64-bit\192.168.0.111_Ubuntu_64-bit.vmx")
"188" = @("D:\Documents\Virtual Machines\Ollama_Ubuntu_64-bit\Ollama_Ubuntu_64-bit.vmx")
"120" = @("D:\Documents\Virtual Machines\192.168.0.120_Ubuntu_64-bit\192.168.0.120_Ubuntu_64-bit.vmx")
"121" = @("D:\Documents\Virtual Machines\192.168.0.121_Ubuntu_64-bit\192.168.0.121_Ubuntu_64-bit.vmx")
"112" = @("D:\Downloads\kali-linux-2025.4-vmware-amd64\kali-linux-2025.4-vmware-amd64.vmwarevm\kali-linux-2025.4-vmware-amd64.vmx")
}
function Resolve-VmxPath {
param([string]$HostAlias)
$supplied = $SuppliedVmx[$HostAlias]
if ($supplied -and (Test-Path -LiteralPath $supplied)) {
return (Resolve-Path -LiteralPath $supplied).Path
}
foreach ($candidate in @($KnownVmxCandidates[$HostAlias])) {
if ($candidate -and (Test-Path -LiteralPath $candidate)) {
return (Resolve-Path -LiteralPath $candidate).Path
}
}
if (-not $EnableRecursiveDiscovery) {
return ""
}
foreach ($root in $DiscoveryRoot) {
if (-not (Test-Path -LiteralPath $root)) {
continue
}
$match = Get-ChildItem -LiteralPath $root -Recurse -Filter "*.vmx" -ErrorAction SilentlyContinue |
Where-Object {
$displayName = ""
try {
$displayName = (Select-String -LiteralPath $_.FullName -Pattern "^displayName\s*=\s*" -ErrorAction SilentlyContinue |
Select-Object -First 1).Line
} catch {
$displayName = ""
}
$_.FullName -match "(^|[\\_\-\s])$HostAlias([\\_\-\s]|$)" -or
$displayName -match "(^|[\\_\-\s])$HostAlias([\\_\-\s]|$)"
} |
Select-Object -First 1
if ($match) {
return $match.FullName
}
}
return ""
}
function Get-VmMap {
$map = [ordered]@{}
foreach ($alias in $VmOrder) {
$map[$alias] = Resolve-VmxPath -HostAlias $alias
}
return $map
}
function Write-StartupScript {
param([hashtable]$VmMap)
New-Item -ItemType Directory -Path $ProgramDataDir -Force | Out-Null
$vmRows = foreach ($alias in $VmOrder) {
" @{ Alias = `"$alias`"; Path = `"$($VmMap[$alias])`" }"
}
$body = @"
`$ErrorActionPreference = "Continue"
`$VmrunPath = "$VmrunPath"
`$VMs = @(
$($vmRows -join ",`n")
)
Start-Service -Name "VMAuthdService" -ErrorAction SilentlyContinue
Start-Service -Name "VMnetDHCP" -ErrorAction SilentlyContinue
Start-Sleep -Seconds 30
`$running = (& `$VmrunPath list 2>`$null) -join [Environment]::NewLine
`$runningProcesses = @(Get-CimInstance -ClassName Win32_Process -Filter "Name='vmware-vmx.exe'" -ErrorAction SilentlyContinue | ForEach-Object { [string]`$_.CommandLine })
foreach (`$vm in `$VMs) {
if (-not (Test-Path -LiteralPath `$vm.Path)) {
Write-Output "VMX_MISSING alias=`$(`$vm.Alias) path=`$(`$vm.Path)"
continue
}
`$runningByVmrun = `$running -like "*`$(`$vm.Path)*"
`$runningByProcess = @(`$runningProcesses | Where-Object { `$_ -and `$_.IndexOf(`$vm.Path, [StringComparison]::OrdinalIgnoreCase) -ge 0 }).Count -gt 0
if (`$runningByVmrun -or `$runningByProcess) {
Write-Output "VM_ALREADY_RUNNING alias=`$(`$vm.Alias) path=`$(`$vm.Path)"
continue
}
& `$VmrunPath -T ws start `$vm.Path nogui
Write-Output "VM_START_REQUESTED alias=`$(`$vm.Alias) path=`$(`$vm.Path) exit=`$LASTEXITCODE"
Start-Sleep -Seconds 20
}
"@
Set-Content -LiteralPath $StartScript -Value $body -Encoding UTF8
}
function Apply-WindowsUpdatePolicy {
$auPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
New-Item -Path $auPath -Force | Out-Null
New-ItemProperty -Path $auPath -Name "NoAutoRebootWithLoggedOnUsers" -Value 1 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path $auPath -Name "AlwaysAutoRebootAtScheduledTime" -Value 0 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path $auPath -Name "AUOptions" -Value 3 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path $auPath -Name "ScheduledInstallDay" -Value 0 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path $auPath -Name "ScheduledInstallTime" -Value 3 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path $auPath -Name "AUPowerManagement" -Value 0 -PropertyType DWord -Force | Out-Null
}
function Apply-ScheduledTask {
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$StartScript`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-MultipleInstances IgnoreNew `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 2) `
-StartWhenAvailable
Register-ScheduledTask `
-TaskName $TaskName `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Settings $settings `
-Force | Out-Null
}
function Get-VmrunList {
if (-not (Test-Path -LiteralPath $VmrunPath)) {
return @()
}
try {
return @(& $VmrunPath list 2>$null | Select-Object -Skip 1)
} catch {
return @()
}
}
function Get-VmwareVmxProcessCommandLines {
try {
return @(Get-CimInstance -ClassName Win32_Process -Filter "Name='vmware-vmx.exe'" -ErrorAction SilentlyContinue |
ForEach-Object { [string]$_.CommandLine } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
} catch {
return @()
}
}
function Get-VmxRunningSource {
param(
[string]$VmxPath,
[string[]]$VmrunList,
[string[]]$ProcessCommandLines
)
if (-not $VmxPath) { return "none" }
$byVmrun = [bool]($VmrunList -contains $VmxPath)
$byProcess = [bool](@($ProcessCommandLines | Where-Object {
$_ -and $_.IndexOf($VmxPath, [StringComparison]::OrdinalIgnoreCase) -ge 0
}).Count -gt 0)
if ($byVmrun -and $byProcess) { return "vmrun_and_process" }
if ($byVmrun) { return "vmrun" }
if ($byProcess) { return "vmware_vmx_process" }
return "none"
}
function Get-VmwareServiceReadback {
$serviceNames = @("VMAuthdService", "VMnetDHCP")
foreach ($name in $serviceNames) {
$service = Get-Service -Name $name -ErrorAction SilentlyContinue
$startupType = "unknown"
try {
$cim = Get-CimInstance -ClassName Win32_Service -Filter "Name='$name'" -ErrorAction SilentlyContinue
if ($cim) {
$startupType = $cim.StartMode
}
} catch {
$startupType = "unknown"
}
$present = [int][bool]$service
$status = if ($service) { "$($service.Status)" } else { "missing" }
$ok = [int]($service -and $service.Status -eq "Running" -and $startupType -in @("Auto", "Automatic"))
Write-Output "VMWARE_SERVICE name=$name present=$present status=$status startup_type=$startupType ok=$ok"
}
}
function Format-ReadbackValue {
param([object]$Value)
if ($null -eq $Value) {
return "unknown"
}
$text = "$Value"
if ([string]::IsNullOrWhiteSpace($text)) {
return "unknown"
}
return ($text -replace "\s+", "_")
}
function Get-AutostartTaskReadback {
$task = $null
$taskInfo = $null
try {
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
} catch {
$task = $null
}
try {
$taskInfo = Get-ScheduledTaskInfo -TaskName $TaskName -ErrorAction SilentlyContinue
} catch {
$taskInfo = $null
}
$present = [int][bool]$task
$enabled = 0
$state = "missing"
$triggerCount = 0
$actionCount = 0
$principalUser = "missing"
$runLevel = "missing"
$logonType = "missing"
$lastRunTime = "never"
$lastTaskResult = "unknown"
$nextRunTime = "unknown"
$actionExecute = "missing"
$actionArgument = "missing"
if ($task) {
$enabled = [int]($task.State -ne "Disabled")
$state = "$($task.State)"
$triggerCount = @($task.Triggers).Count
$actionCount = @($task.Actions).Count
if ($task.Principal) {
$principalUser = "$($task.Principal.UserId)"
$runLevel = "$($task.Principal.RunLevel)"
$logonType = "$($task.Principal.LogonType)"
}
$actionExecute = (@($task.Actions) | ForEach-Object { "$($_.Execute)" }) -join "|"
$actionArgument = (@($task.Actions) | ForEach-Object { "$($_.Arguments)" }) -join "|"
}
if ($taskInfo) {
if ($taskInfo.LastRunTime) {
$lastRunTime = $taskInfo.LastRunTime.ToString("s")
}
$lastTaskResult = "$($taskInfo.LastTaskResult)"
if ($taskInfo.NextRunTime) {
$nextRunTime = $taskInfo.NextRunTime.ToString("s")
}
}
$startScriptPresent = [int](Test-Path -LiteralPath $StartScript)
$ok = [int]($task -and $enabled -eq 1 -and $triggerCount -gt 0 -and $actionCount -gt 0)
Write-Output "VMWARE_AUTOSTART_TASK name=$TaskName present=$present enabled=$enabled state=$state trigger_count=$triggerCount action_count=$actionCount ok=$ok"
Write-Output "VMWARE_AUTOSTART_TASK_DETAIL name=$TaskName principal_user=$(Format-ReadbackValue $principalUser) run_level=$(Format-ReadbackValue $runLevel) logon_type=$(Format-ReadbackValue $logonType) start_script_present=$startScriptPresent last_run_time=$(Format-ReadbackValue $lastRunTime) last_task_result=$(Format-ReadbackValue $lastTaskResult) next_run_time=$(Format-ReadbackValue $nextRunTime)"
Write-Output "VMWARE_AUTOSTART_TASK_ACTION name=$TaskName execute=$(Format-ReadbackValue $actionExecute) argument=$(Format-ReadbackValue $actionArgument)"
}
function Get-WindowsUpdatePolicyReadback {
$auPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
$expected = [ordered]@{
"NoAutoRebootWithLoggedOnUsers" = 1
"AlwaysAutoRebootAtScheduledTime" = 0
"AUOptions" = 3
"ScheduledInstallDay" = 0
"ScheduledInstallTime" = 3
"AUPowerManagement" = 0
}
$props = $null
if (Test-Path -LiteralPath $auPath) {
$props = Get-ItemProperty -LiteralPath $auPath -ErrorAction SilentlyContinue
}
foreach ($name in $expected.Keys) {
$value = "missing"
if ($props -and $null -ne $props.$name) {
$value = "$($props.$name)"
}
$ok = [int]($value -eq "$($expected[$name])")
Write-Output "WINDOWS_UPDATE_POLICY name=$name value=$value expected=$($expected[$name]) ok=$ok"
}
}
function Get-VmPowerReadback {
param([hashtable]$VmMap)
$running = Get-VmrunList
$processCommands = Get-VmwareVmxProcessCommandLines
foreach ($alias in $VmOrder) {
$path = "$($VmMap[$alias])"
$present = [int][bool]$path
$runningSource = Get-VmxRunningSource -VmxPath $path -VmrunList $running -ProcessCommandLines $processCommands
$runningMatch = [int]($runningSource -ne "none")
Write-Output "VM_POWER alias=$alias vmx_present=$present running=$runningMatch source=$runningSource"
}
}
$vmMap = Get-VmMap
$missing = @($VmOrder | Where-Object { -not $vmMap[$_] })
$vmrunPresent = Test-Path -LiteralPath $VmrunPath
Write-Output "AWOOOI_WINDOWS99_VMWARE_AUTOSTART=1"
Write-Output "MODE=$Mode"
Write-Output "VMRUN_PRESENT=$([int]$vmrunPresent)"
if ($SuppliedVmx["111"] -or $VmOrder -contains "111") {
Write-Output "HOST111_VMWARE_TARGET=explicit_or_required"
} else {
Write-Output "HOST111_VMWARE_TARGET=not_required_by_default_missing_no_vmx_found_on_99"
}
foreach ($alias in $VmOrder) {
Write-Output "VMX alias=$alias path=$($vmMap[$alias]) present=$([int][bool]$vmMap[$alias])"
}
Get-VmwareServiceReadback
Get-AutostartTaskReadback
Get-WindowsUpdatePolicyReadback
Get-VmPowerReadback -VmMap $vmMap
$serviceReady = @("VMAuthdService", "VMnetDHCP") | ForEach-Object {
$svc = Get-Service -Name $_ -ErrorAction SilentlyContinue
$svc -and $svc.Status -eq "Running"
}
$task = $null
try {
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
} catch {
$task = $null
}
$policyValues = @{}
try {
$policy = Get-ItemProperty -LiteralPath "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -ErrorAction SilentlyContinue
if ($policy) {
$policyValues["NoAutoRebootWithLoggedOnUsers"] = $policy.NoAutoRebootWithLoggedOnUsers
$policyValues["AlwaysAutoRebootAtScheduledTime"] = $policy.AlwaysAutoRebootAtScheduledTime
$policyValues["AUOptions"] = $policy.AUOptions
$policyValues["ScheduledInstallDay"] = $policy.ScheduledInstallDay
$policyValues["ScheduledInstallTime"] = $policy.ScheduledInstallTime
$policyValues["AUPowerManagement"] = $policy.AUPowerManagement
}
} catch {
$policyValues = @{}
}
$windowsUpdateReady = (
"$($policyValues["NoAutoRebootWithLoggedOnUsers"])" -eq "1" -and
"$($policyValues["AlwaysAutoRebootAtScheduledTime"])" -eq "0" -and
"$($policyValues["AUOptions"])" -eq "3" -and
"$($policyValues["ScheduledInstallDay"])" -eq "0" -and
"$($policyValues["ScheduledInstallTime"])" -eq "3" -and
"$($policyValues["AUPowerManagement"])" -eq "0"
)
$runningVmList = Get-VmrunList
$runningVmxProcessCommandLines = Get-VmwareVmxProcessCommandLines
$vmPowerReady = $true
foreach ($alias in $VmOrder) {
$path = "$($vmMap[$alias])"
$runningSource = Get-VmxRunningSource -VmxPath $path -VmrunList $runningVmList -ProcessCommandLines $runningVmxProcessCommandLines
if (-not $path -or $runningSource -eq "none") {
$vmPowerReady = $false
}
}
$configReady = (
$vmrunPresent -and
$missing.Count -eq 0 -and
($serviceReady -notcontains $false) -and
$task -and
$task.State -ne "Disabled" -and
@($task.Triggers).Count -gt 0 -and
@($task.Actions).Count -gt 0 -and
$windowsUpdateReady
)
Write-Output "VMWARE_AUTOSTART_CONFIG_READY=$([int]$configReady)"
Write-Output "VMWARE_AUTOSTART_POWER_READY=$([int]$vmPowerReady)"
Write-Output "WINDOWS_UPDATE_NO_AUTO_REBOOT_READY=$([int]$windowsUpdateReady)"
Write-Output "VMWARE_AUTOSTART_VERIFY_READY=$([int]($configReady -and $vmPowerReady))"
if (-not $vmrunPresent) {
Write-Error "vmrun.exe not found at $VmrunPath"
}
if ($missing.Count -gt 0) {
Write-Output "MISSING_VMX_ALIASES=$($missing -join ',')"
if ($Mode -eq "Apply") {
throw "Apply requires explicit or discoverable VMX paths for every required VM."
}
}
if ($Mode -eq "Verify") {
exit 0
}
if ($Mode -eq "DryRun") {
Write-Output "DRY_RUN would_write_start_script=$StartScript"
Write-Output "DRY_RUN would_register_task=$TaskName"
Write-Output "DRY_RUN would_apply_windows_update_no_auto_reboot_policy=1"
exit 0
}
Write-StartupScript -VmMap $vmMap
Set-Service -Name "VMAuthdService" -StartupType Automatic -ErrorAction SilentlyContinue
Set-Service -Name "VMnetDHCP" -StartupType Automatic -ErrorAction SilentlyContinue
Apply-ScheduledTask
Apply-WindowsUpdatePolicy
Write-Output "APPLIED scheduled_task=$TaskName start_script=$StartScript windows_update_no_auto_reboot_policy=1"