245 lines
8.4 KiB
PowerShell
245 lines
8.4 KiB
PowerShell
param(
|
|
[string]$SourceScript = (Join-Path $PSScriptRoot "windows99-vmware-autostart.ps1"),
|
|
[string]$AgentRoot = "C:\Wooo\Agent99",
|
|
[string]$SourceRevision = "unknown",
|
|
[string]$TaskName = "AWOOOI-Start-VMware-VMs",
|
|
[string]$StartScript = "C:\ProgramData\AWOOOI\Start-AWOOOI-VMs.ps1",
|
|
[int]$ExpectedVmCount = 5
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$PolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
|
|
$PolicyNames = @(
|
|
"NoAutoRebootWithLoggedOnUsers",
|
|
"AlwaysAutoRebootAtScheduledTime",
|
|
"AUOptions",
|
|
"ScheduledInstallDay",
|
|
"ScheduledInstallTime",
|
|
"AUPowerManagement"
|
|
)
|
|
$ServiceNames = @("VMAuthdService", "VMnetDHCP")
|
|
$PowerShellExe = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
|
|
$Stamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
$TraceId = "windows99-vmware-autostart-$Stamp"
|
|
$BackupDir = Join-Path $AgentRoot "deploy\vmware-autostart-backup-$Stamp"
|
|
$EvidencePath = Join-Path $AgentRoot "evidence\windows99-vmware-autostart-apply-$Stamp.json"
|
|
|
|
function Invoke-AutostartMode {
|
|
param([ValidateSet("DryRun", "Apply", "Verify")][string]$Mode)
|
|
|
|
$output = @(& $PowerShellExe `
|
|
-NoProfile `
|
|
-NonInteractive `
|
|
-ExecutionPolicy Bypass `
|
|
-File $SourceScript `
|
|
-Mode $Mode 2>&1 | ForEach-Object { "$_" })
|
|
[pscustomobject]@{
|
|
mode = $Mode
|
|
exitCode = $LASTEXITCODE
|
|
output = $output
|
|
}
|
|
}
|
|
|
|
function Get-PolicySnapshot {
|
|
$pathExisted = Test-Path -LiteralPath $PolicyPath
|
|
$properties = if ($pathExisted) {
|
|
Get-ItemProperty -LiteralPath $PolicyPath -ErrorAction Stop
|
|
} else {
|
|
$null
|
|
}
|
|
$values = foreach ($name in $PolicyNames) {
|
|
$property = if ($properties) { $properties.PSObject.Properties[$name] } else { $null }
|
|
[pscustomobject]@{
|
|
name = $name
|
|
existed = [bool]$property
|
|
value = if ($property) { [int]$property.Value } else { $null }
|
|
}
|
|
}
|
|
[pscustomobject]@{
|
|
pathExisted = $pathExisted
|
|
values = @($values)
|
|
}
|
|
}
|
|
|
|
function Restore-PolicySnapshot {
|
|
param([object]$Snapshot)
|
|
|
|
if (-not $Snapshot.pathExisted) {
|
|
if (Test-Path -LiteralPath $PolicyPath) {
|
|
Remove-Item -LiteralPath $PolicyPath -Recurse -Force
|
|
}
|
|
return
|
|
}
|
|
New-Item -Path $PolicyPath -Force | Out-Null
|
|
foreach ($row in @($Snapshot.values)) {
|
|
if ($row.existed) {
|
|
New-ItemProperty `
|
|
-Path $PolicyPath `
|
|
-Name $row.name `
|
|
-Value ([int]$row.value) `
|
|
-PropertyType DWord `
|
|
-Force | Out-Null
|
|
} else {
|
|
Remove-ItemProperty `
|
|
-LiteralPath $PolicyPath `
|
|
-Name $row.name `
|
|
-ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
|
|
function Restore-ServiceStartModes {
|
|
param([object[]]$Rows)
|
|
|
|
foreach ($row in $Rows) {
|
|
$startupType = switch ($row.startMode) {
|
|
"Auto" { "Automatic" }
|
|
"Disabled" { "Disabled" }
|
|
default { "Manual" }
|
|
}
|
|
Set-Service -Name $row.name -StartupType $startupType -ErrorAction Stop
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $SourceScript)) {
|
|
throw "VMware autostart source script not found: $SourceScript"
|
|
}
|
|
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop
|
|
if ($task.State -eq "Running") {
|
|
throw "VMware autostart task is running; controlled apply refused."
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $EvidencePath) | Out-Null
|
|
$hadStartScript = Test-Path -LiteralPath $StartScript
|
|
if ($hadStartScript) {
|
|
Copy-Item -LiteralPath $StartScript -Destination (Join-Path $BackupDir "Start-AWOOOI-VMs.ps1")
|
|
}
|
|
$taskXml = Export-ScheduledTask -TaskName $TaskName
|
|
$taskXml | Set-Content -LiteralPath (Join-Path $BackupDir "$TaskName.xml") -Encoding Unicode
|
|
$policySnapshot = Get-PolicySnapshot
|
|
$policySnapshot | ConvertTo-Json -Depth 6 | Set-Content `
|
|
-LiteralPath (Join-Path $BackupDir "windows-update-policy.json") `
|
|
-Encoding UTF8
|
|
$serviceSnapshot = @($ServiceNames | ForEach-Object {
|
|
$service = Get-CimInstance -ClassName Win32_Service -Filter "Name='$_'" -ErrorAction Stop
|
|
[pscustomobject]@{ name = $_; startMode = [string]$service.StartMode }
|
|
})
|
|
$serviceSnapshot | ConvertTo-Json -Depth 4 | Set-Content `
|
|
-LiteralPath (Join-Path $BackupDir "vmware-service-start-modes.json") `
|
|
-Encoding UTF8
|
|
|
|
$preVmxProcessCount = @(Get-CimInstance `
|
|
-ClassName Win32_Process `
|
|
-Filter "Name='vmware-vmx.exe'" `
|
|
-ErrorAction Stop).Count
|
|
$receipt = [ordered]@{
|
|
schemaVersion = "windows99_vmware_autostart_controlled_apply_v1"
|
|
traceId = $TraceId
|
|
sourceRevision = $SourceRevision
|
|
startedAt = (Get-Date -Format o)
|
|
completedAt = $null
|
|
ok = $false
|
|
status = "started"
|
|
sourceScript = $SourceScript
|
|
backupDir = $BackupDir
|
|
evidence = $EvidencePath
|
|
expectedVmCount = $ExpectedVmCount
|
|
preVmxProcessCount = $preVmxProcessCount
|
|
postVmxProcessCount = $null
|
|
dryRun = $null
|
|
apply = $null
|
|
verifier = $null
|
|
rollbackAttempted = $false
|
|
rollbackSucceeded = $null
|
|
error = $null
|
|
boundaries = [ordered]@{
|
|
hostRebootPerformed = $false
|
|
vmPowerChangePerformed = $false
|
|
secretReadPerformed = $false
|
|
}
|
|
}
|
|
|
|
try {
|
|
$receipt.dryRun = Invoke-AutostartMode -Mode DryRun
|
|
if ($receipt.dryRun.exitCode -ne 0 -or -not ($receipt.dryRun.output -match "^DRY_RUN ")) {
|
|
throw "VMware autostart dry-run gate failed."
|
|
}
|
|
|
|
$receipt.apply = Invoke-AutostartMode -Mode Apply
|
|
if ($receipt.apply.exitCode -ne 0) {
|
|
throw "VMware autostart apply failed with exit code $($receipt.apply.exitCode)."
|
|
}
|
|
|
|
$receipt.verifier = Invoke-AutostartMode -Mode Verify
|
|
$postVmxProcessCount = @(Get-CimInstance `
|
|
-ClassName Win32_Process `
|
|
-Filter "Name='vmware-vmx.exe'" `
|
|
-ErrorAction Stop).Count
|
|
$receipt.postVmxProcessCount = $postVmxProcessCount
|
|
$runningRows = @($receipt.verifier.output | Where-Object {
|
|
$_ -match "^VM_POWER alias=.+ vmx_present=1 running=1 source=\S+$"
|
|
})
|
|
$verifyReady = $receipt.verifier.output -contains "VMWARE_AUTOSTART_VERIFY_READY=1"
|
|
$processAwareGuard = [bool](Select-String `
|
|
-LiteralPath $StartScript `
|
|
-Pattern "runningProcesses" `
|
|
-SimpleMatch `
|
|
-Quiet)
|
|
$taskAfter = Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop
|
|
$verified = (
|
|
$receipt.verifier.exitCode -eq 0 -and
|
|
$runningRows.Count -eq $ExpectedVmCount -and
|
|
$verifyReady -and
|
|
$processAwareGuard -and
|
|
$taskAfter.State -ne "Disabled" -and
|
|
$postVmxProcessCount -eq $preVmxProcessCount
|
|
)
|
|
$receipt.verifier = [ordered]@{
|
|
exitCode = $receipt.verifier.exitCode
|
|
output = $receipt.verifier.output
|
|
runningVmRows = $runningRows.Count
|
|
verifyReady = $verifyReady
|
|
processAwareGuard = $processAwareGuard
|
|
taskState = [string]$taskAfter.State
|
|
processCountUnchanged = ($postVmxProcessCount -eq $preVmxProcessCount)
|
|
}
|
|
if (-not $verified) {
|
|
throw "VMware autostart independent post-verifier failed."
|
|
}
|
|
$receipt.ok = $true
|
|
$receipt.status = "completed"
|
|
} catch {
|
|
$receipt.status = "failed_rollback_required"
|
|
$receipt.error = $_.Exception.Message
|
|
$receipt.rollbackAttempted = $true
|
|
try {
|
|
if ($hadStartScript) {
|
|
Copy-Item `
|
|
-Force `
|
|
-LiteralPath (Join-Path $BackupDir "Start-AWOOOI-VMs.ps1") `
|
|
-Destination $StartScript
|
|
} elseif (Test-Path -LiteralPath $StartScript) {
|
|
Remove-Item -LiteralPath $StartScript -Force
|
|
}
|
|
Register-ScheduledTask -TaskName $TaskName -Xml $taskXml -Force | Out-Null
|
|
Restore-PolicySnapshot -Snapshot $policySnapshot
|
|
Restore-ServiceStartModes -Rows $serviceSnapshot
|
|
$receipt.rollbackSucceeded = $true
|
|
$receipt.status = "failed_rolled_back"
|
|
} catch {
|
|
$receipt.rollbackSucceeded = $false
|
|
$receipt.status = "failed_rollback_failed"
|
|
$receipt.error = "$($receipt.error); rollback=$($_.Exception.Message)"
|
|
}
|
|
} finally {
|
|
$receipt.completedAt = Get-Date -Format o
|
|
$receipt | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $EvidencePath -Encoding UTF8
|
|
}
|
|
|
|
$receipt | ConvertTo-Json -Depth 12
|
|
if (-not $receipt.ok) {
|
|
exit 2
|
|
}
|
|
exit 0
|