#Requires -RunAsAdministrator $ErrorActionPreference = "Stop" $Target = "C:\MinerOpsAgent" $BackupRoot = Join-Path $Target "backups" $BackupTimestamp = Get-Date -Format "yyyyMMdd-HHmmss" $BackupSuffix = [guid]::NewGuid().ToString("N").Substring(0, 8) $Backup = Join-Path $BackupRoot "$BackupTimestamp-$BackupSuffix" $RuntimeFiles = @( "MinerOpsAgentService.ps1", "MinerOpsAgentTray.ps1", "MinerOpsAgentGUI.ps1", "MinerOpsApi3EnrichmentPoller.ps1", "MinerOpsPoolPoller.ps1", "MinerOpsPoolPollerTask.ps1", "EnrichOneMiner.ps1", "MinerOpsControl.ps1", "VERSION" ) function Get-MinerOpsScheduledTask { param([Parameter(Mandatory)][string]$Name) return Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue } function Stop-MinerOpsScheduledTaskForUpgrade { param( [Parameter(Mandatory)][string]$Name, [TimeSpan]$Timeout = [TimeSpan]::FromSeconds(30) ) Stop-ScheduledTask -TaskName $Name -ErrorAction Stop $deadline = [DateTime]::UtcNow.Add($Timeout) do { Start-Sleep -Milliseconds 500 $task = Get-MinerOpsScheduledTask -Name $Name if ($null -eq $task) { throw "Scheduled Task $Name disappeared while stopping it." } if ([string]$task.State -ne "Running") { return } } while ([DateTime]::UtcNow -lt $deadline) throw "Scheduled Task $Name did not stop within $($Timeout.TotalSeconds) seconds." } function Start-MinerOpsScheduledTask { param([Parameter(Mandatory)][string]$Name) Start-ScheduledTask -TaskName $Name -ErrorAction Stop } # Validate the complete source before creating directories, stopping tasks, or # replacing any target file. foreach ($name in $RuntimeFiles) { $source = Join-Path $PSScriptRoot $name if (!(Test-Path -LiteralPath $source -PathType Leaf)) { throw "Deployment source is missing $name" } } $mainTask = Get-MinerOpsScheduledTask -Name "MinerOpsAgent" $mainTaskExisted = $null -ne $mainTask $mainTaskWasRunning = $mainTaskExisted -and [string]$mainTask.State -eq "Running" $mainTaskStoppedForUpgrade = $false $deploymentStarted = $false $existing = @() $createdTaskNames = [System.Collections.Generic.List[string]]::new() try { New-Item -ItemType Directory -Force -Path $Target, $BackupRoot | Out-Null $existing = @($RuntimeFiles | Where-Object { Test-Path -LiteralPath (Join-Path $Target $_) -PathType Leaf }) if ($existing.Count -gt 0) { New-Item -ItemType Directory -Force -Path $Backup | Out-Null foreach ($name in $existing) { Copy-Item -LiteralPath (Join-Path $Target $name) ` -Destination (Join-Path $Backup $name) -Force } } if ($mainTaskWasRunning) { $mainTaskStoppedForUpgrade = $true Stop-MinerOpsScheduledTaskForUpgrade -Name "MinerOpsAgent" } $deploymentStarted = $true foreach ($name in $RuntimeFiles) { Copy-Item -LiteralPath (Join-Path $PSScriptRoot $name) ` -Destination (Join-Path $Target $name) -Force } $powerShell = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" $system = New-ScheduledTaskPrincipal -UserId "SYSTEM" ` -LogonType ServiceAccount -RunLevel Highest $startup = New-ScheduledTaskTrigger -AtStartup $fiveMinutes = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) ` -RepetitionInterval (New-TimeSpan -Minutes 5) $commonSettings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew ` -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` -ExecutionTimeLimit (New-TimeSpan -Hours 72) $agentSettings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew ` -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` -ExecutionTimeLimit (New-TimeSpan -Hours 72) -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 1) $definitions = @( @{ Name = "MinerOpsAgent"; Script = "MinerOpsAgentService.ps1" Trigger = $startup; Principal = $system; Settings = $agentSettings ExtraArguments = "" }, @{ Name = "MinerOpsAgentTray"; Script = "MinerOpsAgentTray.ps1" Trigger = (New-ScheduledTaskTrigger -AtLogOn); Principal = $null Settings = $commonSettings; ExtraArguments = "-WindowStyle Hidden" }, @{ Name = "MinerOpsApi3Enrichment" Script = "MinerOpsApi3EnrichmentPoller.ps1" Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings ExtraArguments = "-Limit 0 -TimeoutMs 8000" }, @{ Name = "MinerOpsPoolPoller"; Script = "MinerOpsPoolPollerTask.ps1" Trigger = $fiveMinutes; Principal = $system; Settings = $commonSettings ExtraArguments = "" } ) foreach ($definition in $definitions) { if ($null -ne (Get-MinerOpsScheduledTask -Name $definition.Name)) { Write-Host "Preserving existing Scheduled Task $($definition.Name)." continue } $arguments = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f ` (Join-Path $Target $definition.Script) if ($definition.ExtraArguments) { $arguments = "$arguments $($definition.ExtraArguments)" } $action = New-ScheduledTaskAction -Execute $powerShell -Argument $arguments $registration = @{ TaskName = $definition.Name Action = $action Trigger = $definition.Trigger Settings = $definition.Settings } if ($definition.Principal) { $registration.Principal = $definition.Principal } Register-ScheduledTask @registration | Out-Null $createdTaskNames.Add($definition.Name) } if ($mainTaskWasRunning) { Start-MinerOpsScheduledTask -Name "MinerOpsAgent" $mainTaskStoppedForUpgrade = $false } Write-Host "MinerOps Windows Agent installed. Existing task definitions, config, state, logs, and backups were preserved." } catch { $deploymentError = $_ $rollbackErrors = [System.Collections.Generic.List[string]]::new() foreach ($taskName in $createdTaskNames) { try { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false ` -ErrorAction Stop } catch { $rollbackErrors.Add("could not remove new task ${taskName}: $($_.Exception.Message)") } } if ($deploymentStarted) { foreach ($name in $RuntimeFiles) { try { $targetPath = Join-Path $Target $name if ($existing -contains $name) { Copy-Item -LiteralPath (Join-Path $Backup $name) ` -Destination $targetPath -Force } elseif (Test-Path -LiteralPath $targetPath) { Remove-Item -LiteralPath $targetPath -Force } } catch { $rollbackErrors.Add("could not restore ${name}: $($_.Exception.Message)") } } } if ($mainTaskWasRunning -and $mainTaskStoppedForUpgrade) { try { Start-MinerOpsScheduledTask -Name "MinerOpsAgent" $mainTaskStoppedForUpgrade = $false } catch { $rollbackErrors.Add("could not restart restored MinerOpsAgent: $($_.Exception.Message)") } } if ($rollbackErrors.Count -gt 0) { $rollbackSummary = $rollbackErrors -join "; " throw "Deployment failed: $($deploymentError.Exception.Message). Rollback errors: $rollbackSummary" } throw $deploymentError }