function Get-PublicRuntimeTaskXmlSha256 { param([Parameter(Mandatory = $true)][string]$Xml) $bytes = [System.Text.UTF8Encoding]::new($false).GetBytes($Xml) $hasher = [System.Security.Cryptography.SHA256]::Create() try { return ([BitConverter]::ToString($hasher.ComputeHash($bytes))).Replace("-", "").ToLowerInvariant() } finally { $hasher.Dispose() } } function Get-PublicRuntimeTaskXmlContractSha256 { param([Parameter(Mandatory = $true)][string]$Xml) try { [xml]$document = $Xml $enabledNodes = @( $document.SelectNodes("//*[local-name()='Settings']/*[local-name()='Enabled']") ) if ($enabledNodes.Count -ne 1) { throw "Task XML must contain exactly one settings Enabled node" } $enabledNodes[0].InnerText = "TASK_OPERATIONAL_STATE" return Get-PublicRuntimeTaskXmlSha256 -Xml $document.OuterXml } catch { throw "Public runtime task XML contract normalization failed" } } function Get-PublicRuntimeTaskDefinitionSetSha256 { param([Parameter(Mandatory = $true)][object[]]$Entries) $lines = @() foreach ($entry in @($Entries | Sort-Object role)) { if ( [string]$entry.role -notmatch "^(boot|watchdog)$" -or [string]$entry.xml_sha256 -notmatch "^[0-9a-f]{64}$" ) { throw "Public runtime task definition digest input is invalid" } $enabled = "false" if ([bool]$entry.enabled) { $enabled = "true" } $lines += ([string]$entry.role + ":" + [string]$entry.xml_sha256 + ":" + $enabled) } if ($lines.Count -ne 2) { throw "Public runtime task definition set must contain exactly two roles" } return Get-PublicRuntimeTaskXmlSha256 -Xml (@($lines) -join "`n") } function Get-ExactRootScheduledTaskForDefinition { param([Parameter(Mandatory = $true)][string]$TaskName) if ( [string]::IsNullOrWhiteSpace($TaskName) -or $TaskName -match "[\\/\x00-\x1f\x7f]" ) { throw "Public runtime scheduled task name is invalid" } $matches = @( Get-ScheduledTask ` -TaskName $TaskName ` -TaskPath "\" ` -ErrorAction SilentlyContinue ) if ($matches.Count -ne 1) { throw "Public runtime root scheduled task is missing or ambiguous" } return $matches[0] } function Assert-PublicRuntimeCoordinatedTaskNamesExact { param([Parameter(Mandatory = $true)][string[]]$TaskNames) $actual = @($TaskNames | Sort-Object -Unique) $expected = @("VignettePublicRuntime", "VignettePublicRuntimeWatchdog") if ( $actual.Count -ne 2 -or -not ($actual -ccontains $expected[0]) -or -not ($actual -ccontains $expected[1]) ) { throw "Public runtime task-definition cutover requires the exact two root tasks" } } function Get-PublicRuntimeTaskDefinitionSnapshot { param( [string]$BootTaskName = "VignettePublicRuntime", [string]$WatchdogTaskName = "VignettePublicRuntimeWatchdog", [switch]$RequireEnabled ) if ([string]::Equals( $BootTaskName, $WatchdogTaskName, [System.StringComparison]::OrdinalIgnoreCase )) { throw "Public runtime boot and watchdog task names must differ" } $entries = @() foreach ($spec in @( [pscustomobject][ordered]@{ role = "boot"; task_name = $BootTaskName }, [pscustomobject][ordered]@{ role = "watchdog"; task_name = $WatchdogTaskName } )) { $task = Get-ExactRootScheduledTaskForDefinition -TaskName $spec.task_name if ($RequireEnabled -and -not [bool]$task.Settings.Enabled) { throw "Expected public runtime task is not enabled before bootstrap" } $actions = @($task.Actions) if ($actions.Count -ne 1) { throw "Public runtime task must have exactly one action" } $xml = [string](Export-ScheduledTask -InputObject $task -ErrorAction Stop) if ([string]::IsNullOrWhiteSpace($xml)) { throw "Public runtime scheduled task XML snapshot is empty" } $entries += [pscustomobject][ordered]@{ role = [string]$spec.role task_name = [string]$spec.task_name xml_sha256 = Get-PublicRuntimeTaskXmlSha256 -Xml $xml contract_sha256 = Get-PublicRuntimeTaskXmlContractSha256 -Xml $xml enabled = [bool]$task.Settings.Enabled action_execute = [string]$actions[0].Execute action_arguments = [string]$actions[0].Arguments action_working_directory = [string]$actions[0].WorkingDirectory } } return [pscustomobject][ordered]@{ entries = @($entries) set_sha256 = Get-PublicRuntimeTaskDefinitionSetSha256 -Entries $entries } } function Assert-PublicRuntimeTaskDefinitionSnapshotCurrent { param([Parameter(Mandatory = $true)][object]$ExpectedSnapshot) $boot = @($ExpectedSnapshot.entries | Where-Object role -eq "boot") $watchdog = @($ExpectedSnapshot.entries | Where-Object role -eq "watchdog") if ($boot.Count -ne 1 -or $watchdog.Count -ne 1) { throw "Expected public runtime task definition snapshot is invalid" } $actual = Get-PublicRuntimeTaskDefinitionSnapshot ` -BootTaskName ([string]$boot[0].task_name) ` -WatchdogTaskName ([string]$watchdog[0].task_name) if ([string]$actual.set_sha256 -cne [string]$ExpectedSnapshot.set_sha256) { throw "Public runtime task definition set drifted" } return $actual } function Get-PublicRuntimeExpectedTaskActionContracts { param( [Parameter(Mandatory = $true)][string]$StableSourceRoot, [Parameter(Mandatory = $true)][string]$ExpectedSourceCommit, [Parameter(Mandatory = $true)][string]$ExpectedSourceTree, [Parameter(Mandatory = $true)][string]$PythonPath, [Parameter(Mandatory = $true)][string]$UserUploadDir, [Parameter(Mandatory = $true)][string]$UserUploadManifestPath, [Parameter(Mandatory = $true)][string]$ExpectedUserUploadManifestSha256, [Parameter(Mandatory = $true)][string]$UserUploadWriteFreezePath, [Parameter(Mandatory = $true)][string]$CloudflaredPath, [Parameter(Mandatory = $true)][string]$CloudflaredConfigPath, [Parameter(Mandatory = $true)][string]$PublicHealthUrl ) $root = (Resolve-Path -LiteralPath $StableSourceRoot).Path $bootScript = (Resolve-Path -LiteralPath (Join-Path $root "scripts\boot-public-runtime.ps1")).Path $watchScript = (Resolve-Path -LiteralPath (Join-Path $root "scripts\watch-public-runtime.ps1")).Path $startScript = (Resolve-Path -LiteralPath (Join-Path $root "scripts\start-public-runtime.ps1")).Path $watchLauncher = (Resolve-Path -LiteralPath (Join-Path $root "scripts\watch-public-runtime-task.vbs")).Path $bootSha256 = (Get-FileHash -LiteralPath $bootScript -Algorithm SHA256).Hash.ToLowerInvariant() $watchSha256 = (Get-FileHash -LiteralPath $watchScript -Algorithm SHA256).Hash.ToLowerInvariant() $startSha256 = (Get-FileHash -LiteralPath $startScript -Algorithm SHA256).Hash.ToLowerInvariant() $resolvedPython = (Resolve-Path -LiteralPath $PythonPath).Path $resolvedCloudflared = (Resolve-Path -LiteralPath $CloudflaredPath).Path $resolvedCloudflaredConfig = (Resolve-Path -LiteralPath $CloudflaredConfigPath).Path $pythonSha256 = (Get-FileHash -LiteralPath $resolvedPython -Algorithm SHA256).Hash.ToLowerInvariant() $cloudflaredSha256 = (Get-FileHash -LiteralPath $resolvedCloudflared -Algorithm SHA256).Hash.ToLowerInvariant() $cloudflaredConfigSha256 = (Get-FileHash -LiteralPath $resolvedCloudflaredConfig -Algorithm SHA256).Hash.ToLowerInvariant() $windowsPowerShell = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe" $wscript = Join-Path $env:SystemRoot "System32\wscript.exe" $bootArguments = @( "-NoProfile", "-ExecutionPolicy Bypass", "-WindowStyle Hidden", "-File `"$bootScript`"", "-StableSourceRoot `"$root`"", "-ExpectedSourceCommit $ExpectedSourceCommit", "-ExpectedSourceTree $ExpectedSourceTree", "-ExpectedBootScriptSha256 $bootSha256", "-ExpectedStartScriptSha256 $startSha256", "-ExpectedPythonSha256 $pythonSha256", "-ExpectedCloudflaredSha256 $cloudflaredSha256", "-ExpectedCloudflaredConfigSha256 $cloudflaredConfigSha256", "-Python `"$resolvedPython`"", "-Cloudflared `"$resolvedCloudflared`"", "-CloudflaredConfig `"$resolvedCloudflaredConfig`"", "-UserUploadDir `"$UserUploadDir`"", "-UserUploadManifestPath `"$UserUploadManifestPath`"", "-ExpectedUserUploadManifestSha256 $ExpectedUserUploadManifestSha256", "-UserUploadWriteFreezePath `"$UserUploadWriteFreezePath`"" ) -join " " $watchArguments = @( "-File `"$watchScript`"", "-StableSourceRoot `"$root`"", "-ExpectedSourceCommit $ExpectedSourceCommit", "-ExpectedSourceTree $ExpectedSourceTree", "-ExpectedWatchdogSha256 $watchSha256", "-ExpectedStartScriptSha256 $startSha256", "-ExpectedPythonSha256 $pythonSha256", "-ExpectedCloudflaredSha256 $cloudflaredSha256", "-ExpectedCloudflaredConfigSha256 $cloudflaredConfigSha256", "-Python `"$resolvedPython`"", "-Cloudflared `"$resolvedCloudflared`"", "-CloudflaredConfig `"$resolvedCloudflaredConfig`"", "-UserUploadDir `"$UserUploadDir`"", "-UserUploadManifestPath `"$UserUploadManifestPath`"", "-ExpectedUserUploadManifestSha256 $ExpectedUserUploadManifestSha256", "-UserUploadWriteFreezePath `"$UserUploadWriteFreezePath`"", "-PublicHealthUrl `"$PublicHealthUrl`"" ) -join " " return @( [pscustomobject][ordered]@{ role = "boot" execute = $windowsPowerShell arguments = $bootArguments working_directory = $root }, [pscustomobject][ordered]@{ role = "watchdog" execute = $wscript arguments = "`"$watchLauncher`" $watchArguments" working_directory = $root } ) } function Assert-NewPublicRuntimeTaskDefinitionsPinned { param( [Parameter(Mandatory = $true)][object]$Snapshot, [Parameter(Mandatory = $true)][string]$StableSourceRoot, [Parameter(Mandatory = $true)][string]$ExpectedSourceCommit, [Parameter(Mandatory = $true)][string]$ExpectedSourceTree, [Parameter(Mandatory = $true)][string]$PythonPath, [Parameter(Mandatory = $true)][string]$UserUploadDir, [Parameter(Mandatory = $true)][string]$UserUploadManifestPath, [Parameter(Mandatory = $true)][string]$ExpectedUserUploadManifestSha256, [Parameter(Mandatory = $true)][string]$UserUploadWriteFreezePath, [Parameter(Mandatory = $true)][string]$CloudflaredPath, [Parameter(Mandatory = $true)][string]$CloudflaredConfigPath, [Parameter(Mandatory = $true)][string]$PublicHealthUrl, [switch]$AllowEnabled ) if (@($Snapshot.entries).Count -ne 2) { throw "New public runtime task definition set is incomplete" } $contracts = @( Get-PublicRuntimeExpectedTaskActionContracts ` -StableSourceRoot $StableSourceRoot ` -ExpectedSourceCommit $ExpectedSourceCommit ` -ExpectedSourceTree $ExpectedSourceTree ` -PythonPath $PythonPath ` -UserUploadDir $UserUploadDir ` -UserUploadManifestPath $UserUploadManifestPath ` -ExpectedUserUploadManifestSha256 $ExpectedUserUploadManifestSha256 ` -UserUploadWriteFreezePath $UserUploadWriteFreezePath ` -CloudflaredPath $CloudflaredPath ` -CloudflaredConfigPath $CloudflaredConfigPath ` -PublicHealthUrl $PublicHealthUrl ) foreach ($entry in @($Snapshot.entries)) { if (-not $AllowEnabled -and [bool]$entry.enabled) { throw "New public runtime task definition must remain disabled until operational receipt" } $contract = @($contracts | Where-Object role -eq ([string]$entry.role)) if ( $contract.Count -ne 1 -or -not [string]::Equals( [string]$entry.action_execute, [string]$contract[0].execute, [System.StringComparison]::OrdinalIgnoreCase ) -or [string]$entry.action_arguments -cne [string]$contract[0].arguments -or -not [string]::Equals( [string]$entry.action_working_directory, [string]$contract[0].working_directory, [System.StringComparison]::OrdinalIgnoreCase ) ) { throw "New public runtime task action contract drift" } } return $Snapshot } function Invoke-PublicRuntimeTaskDefinitionInstallerPairDisabled { param( [Parameter(Mandatory = $true)][scriptblock]$BootInstaller, [Parameter(Mandatory = $true)][scriptblock]$WatchdogInstaller, [Parameter(Mandatory = $true)][object[]]$MaintenanceSnapshot, [int]$TimeoutSec = 30 ) try { $null = & $BootInstaller $null = & $WatchdogInstaller Assert-PublicRuntimeTasksDisabledAndIdle ` -Snapshot $MaintenanceSnapshot ` -TimeoutSec $TimeoutSec } catch { Suspend-PublicRuntimeTasks ` -Snapshot $MaintenanceSnapshot ` -TimeoutSec $TimeoutSec Assert-PublicRuntimeTasksDisabledAndIdle ` -Snapshot $MaintenanceSnapshot ` -TimeoutSec $TimeoutSec throw } } function Enable-NewPublicRuntimeTaskDefinitions { param([Parameter(Mandatory = $true)][object]$DisabledSnapshot) $null = Assert-PublicRuntimeTaskDefinitionSnapshotCurrent ` -ExpectedSnapshot $DisabledSnapshot $boot = @($DisabledSnapshot.entries | Where-Object role -eq "boot") $watchdog = @($DisabledSnapshot.entries | Where-Object role -eq "watchdog") if ($boot.Count -ne 1 -or $watchdog.Count -ne 1) { throw "Disabled public runtime task definition snapshot is invalid" } try { foreach ($entry in @($DisabledSnapshot.entries)) { Enable-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction Stop ` | Out-Null } } catch { foreach ($entry in @($DisabledSnapshot.entries)) { Disable-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction SilentlyContinue ` | Out-Null } $failedTruth = Get-PublicRuntimeTaskDefinitionSnapshot ` -BootTaskName ([string]$boot[0].task_name) ` -WatchdogTaskName ([string]$watchdog[0].task_name) if (@($failedTruth.entries | Where-Object enabled).Count -ne 0) { throw "Public runtime task enable failed and compensation could not disable both tasks" } throw } $operational = Get-PublicRuntimeTaskDefinitionSnapshot ` -BootTaskName ([string]$boot[0].task_name) ` -WatchdogTaskName ([string]$watchdog[0].task_name) ` -RequireEnabled foreach ($prior in @($DisabledSnapshot.entries)) { $current = @($operational.entries | Where-Object role -eq ([string]$prior.role)) if ( $current.Count -ne 1 -or [string]$current[0].contract_sha256 -cne [string]$prior.contract_sha256 -or [string]$current[0].action_arguments -cne [string]$prior.action_arguments -or -not [string]::Equals( [string]$current[0].action_execute, [string]$prior.action_execute, [System.StringComparison]::OrdinalIgnoreCase ) -or -not [string]::Equals( [string]$current[0].action_working_directory, [string]$prior.action_working_directory, [System.StringComparison]::OrdinalIgnoreCase ) ) { foreach ($entry in @($DisabledSnapshot.entries)) { Disable-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction SilentlyContinue ` | Out-Null } throw "Public runtime task definition drifted while becoming operational" } } return $operational }