$taskDefinitionCutoverContract = Join-Path $PSScriptRoot "public-runtime-task-definition-cutover.ps1" if (-not (Test-Path -LiteralPath $taskDefinitionCutoverContract -PathType Leaf)) { throw "Public runtime task definition cutover contract is unavailable" } . $taskDefinitionCutoverContract function Assert-PublicRuntimeTasksDisabledAndIdle { param( [Parameter(Mandatory = $true)][object[]]$Snapshot, [int]$TimeoutSec = 30 ) $deadline = (Get-Date).AddSeconds($TimeoutSec) do { $allDisabledAndIdle = $true foreach ($entry in @($Snapshot)) { if (-not [bool]$entry.exists) { $unexpectedTask = Get-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction SilentlyContinue if ($null -ne $unexpectedTask) { throw "A coordinated public runtime task appeared during maintenance: $($entry.task_name)" } continue } $task = Get-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction Stop if ([bool]$task.Settings.Enabled -or [string]$task.State -eq "Running") { $allDisabledAndIdle = $false break } } if ($allDisabledAndIdle) { return } Start-Sleep -Milliseconds 250 } while ((Get-Date) -lt $deadline) throw "Public runtime scheduled tasks did not become disabled and idle" } function Get-PublicRuntimeTaskMaintenanceState { param([Parameter(Mandatory = $true)][object[]]$Snapshot) $allDisabledAndIdle = $true $restoredToSnapshot = $true $verified = $true foreach ($entry in @($Snapshot)) { if (-not [bool]$entry.exists) { try { $unexpectedTask = Get-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction SilentlyContinue if ($null -ne $unexpectedTask) { $allDisabledAndIdle = $false $restoredToSnapshot = $false } } catch { $verified = $false $allDisabledAndIdle = $false $restoredToSnapshot = $false } continue } try { $task = Get-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction Stop $isEnabled = [bool]$task.Settings.Enabled $isRunning = [string]$task.State -eq "Running" if ($isEnabled -or $isRunning) { $allDisabledAndIdle = $false } if ($isEnabled -ne [bool]$entry.was_enabled) { $restoredToSnapshot = $false } } catch { $verified = $false $allDisabledAndIdle = $false $restoredToSnapshot = $false } } return [pscustomobject][ordered]@{ verified = $verified all_disabled_and_idle = $allDisabledAndIdle restored_to_snapshot = $restoredToSnapshot } } function Suspend-PublicRuntimeTasks { param( [Parameter(Mandatory = $true)][object[]]$Snapshot, [int]$TimeoutSec = 30 ) foreach ($entry in @($Snapshot)) { if (-not [bool]$entry.exists) { continue } Disable-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction Stop ` | Out-Null } Assert-PublicRuntimeTasksDisabledAndIdle ` -Snapshot $Snapshot ` -TimeoutSec $TimeoutSec } function Enter-PublicRuntimeTaskMaintenance { param( [Parameter(Mandatory = $true)][string[]]$TaskNames, [int]$TimeoutSec = 30 ) $snapshot = @() try { # 모든 원상복구 정보를 먼저 캡처한다. disable 도중 두 번째 task에서 오류가 나도 # 첫 번째 task를 포함한 완전한 snapshot으로 되돌릴 수 있어야 한다. foreach ($taskName in @($TaskNames | Sort-Object -Unique)) { if ([string]::IsNullOrWhiteSpace($taskName)) { throw "Public runtime scheduled task name is empty" } $task = Get-ScheduledTask ` -TaskName $taskName ` -TaskPath "\" ` -ErrorAction SilentlyContinue if ($null -eq $task) { $snapshot += [pscustomobject][ordered]@{ task_name = $taskName exists = $false was_enabled = $false } continue } $wasEnabled = [bool]$task.Settings.Enabled $snapshot += [pscustomobject][ordered]@{ task_name = $taskName exists = $true was_enabled = $wasEnabled } } $result = @($snapshot) Suspend-PublicRuntimeTasks ` -Snapshot $result ` -TimeoutSec $TimeoutSec return $result } catch { foreach ($entry in @($snapshot)) { if ([bool]$entry.exists -and [bool]$entry.was_enabled) { Enable-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction SilentlyContinue ` | Out-Null } } foreach ($entry in @($snapshot)) { if (-not [bool]$entry.exists) { continue } $task = Get-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction Stop if ([bool]$task.Settings.Enabled -ne [bool]$entry.was_enabled) { throw "Public runtime task maintenance entry rollback failed: $($entry.task_name)" } } throw } } function Exit-PublicRuntimeTaskMaintenance { param([Parameter(Mandatory = $true)][object[]]$Snapshot) try { foreach ($entry in @($Snapshot)) { if (-not [bool]$entry.exists -or -not [bool]$entry.was_enabled) { continue } Enable-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction Stop ` | Out-Null } foreach ($entry in @($Snapshot)) { if (-not [bool]$entry.exists -or -not [bool]$entry.was_enabled) { continue } $task = Get-ScheduledTask ` -TaskName ([string]$entry.task_name) ` -TaskPath "\" ` -ErrorAction Stop if (-not [bool]$task.Settings.Enabled) { throw "Public runtime scheduled task was not re-enabled: $($entry.task_name)" } } $restoredTruth = Get-PublicRuntimeTaskMaintenanceState -Snapshot $Snapshot if ( -not [bool]$restoredTruth.verified -or -not [bool]$restoredTruth.restored_to_snapshot ) { throw "Public runtime scheduled task set drifted during maintenance" } } catch { Suspend-PublicRuntimeTasks -Snapshot $Snapshot -TimeoutSec 30 throw } }