param( [Parameter(Mandatory = $true)] [ValidateSet("Notifier", "BackupOnLogoff")] [string]$Task, [string]$TaskFolder = "BakRest", [switch]$RunAsCurrentUser, [switch]$RunAsStoredCredentials, [string]$UserName, [switch]$NoShutdown ) $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $PSScriptRoot $pythonExe = (& python -c "import sys; print(sys.executable)").Trim() if (-not $pythonExe -or -not (Test-Path -LiteralPath $pythonExe)) { $pythonCommand = Get-Command python -ErrorAction Stop $pythonExe = $pythonCommand.Source } if (-not (Test-Path -LiteralPath $pythonExe)) { throw "Python executable not found. Run this script from the same shell where 'python' works." } if ($Task -eq "Notifier") { $taskName = "$TaskFolder\ServerNotifierEvery30Minutes" $xmlPath = Join-Path $repoRoot "tasks\BakRestServerNotifierEvery30Minutes.xml" } else { $taskName = "$TaskFolder\BackupOnLogoff" $xmlPath = Join-Path $repoRoot "tasks\BakRestBackupOnLogoff.xml" } if (-not (Test-Path -LiteralPath $xmlPath)) { throw "Task XML not found: $xmlPath" } $xml = Get-Content -LiteralPath $xmlPath -Raw $xml = $xml -replace 'encoding="UTF-8"', 'encoding="UTF-16"' $xml = $xml -replace '.*?', ('{0}' -f [System.Security.SecurityElement]::Escape($pythonExe)) $xml = $xml -replace '.*?', ('{0}' -f [System.Security.SecurityElement]::Escape($repoRoot)) if ($NoShutdown -and $Task -eq "BackupOnLogoff") { $xml = $xml -replace '-m bakrest\.tray_app --nogui', '-m bakrest.tray_app --nogui --no-shutdown' } if ($RunAsStoredCredentials -and $RunAsCurrentUser) { throw "Use either -RunAsCurrentUser or -RunAsStoredCredentials, not both." } if ($RunAsCurrentUser) { $currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $currentSid = $currentIdentity.User.Value $principalXml = @" $currentSid InteractiveToken LeastPrivilege "@ $xml = [regex]::Replace( $xml, '.*?', $principalXml, [System.Text.RegularExpressions.RegexOptions]::Singleline ) } $plainPassword = $null $runAsName = $null if ($RunAsStoredCredentials) { if (-not $UserName) { $UserName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name } $securePassword = Read-Host -Prompt "Password for $UserName" -AsSecureString $passwordPtr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword) try { $plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($passwordPtr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passwordPtr) } $principalXml = @" $([System.Security.SecurityElement]::Escape($UserName)) Password LeastPrivilege "@ $xml = [regex]::Replace( $xml, '.*?', $principalXml, [System.Text.RegularExpressions.RegexOptions]::Singleline ) $runAsName = $UserName } $tempXml = Join-Path $env:TEMP ("bakrest-task-{0}.xml" -f ([Guid]::NewGuid())) [System.IO.File]::WriteAllText($tempXml, $xml, [System.Text.Encoding]::Unicode) try { if ($RunAsStoredCredentials) { schtasks.exe /Create /TN $taskName /XML $tempXml /RU $UserName /RP $plainPassword /F } else { schtasks.exe /Create /TN $taskName /XML $tempXml /F } if ($LASTEXITCODE -ne 0) { throw "schtasks failed with exit code $LASTEXITCODE" } Write-Host "Registered task: $taskName" Write-Host "Python: $pythonExe" Write-Host "WorkingDirectory: $repoRoot" if ($RunAsCurrentUser) { Write-Host "RunAs: $($currentIdentity.Name)" } if ($RunAsStoredCredentials) { Write-Host "RunAs: $runAsName (stored credentials)" } if ($NoShutdown -and $Task -eq "BackupOnLogoff") { Write-Host "BackupOnLogoff registered in debug mode: --no-shutdown" } } finally { $plainPassword = $null Remove-Item -LiteralPath $tempXml -Force -ErrorAction SilentlyContinue }