# Elevation.ps1 — elevation helpers # Requires $InternalRoot to be defined in the calling script's scope before dot-sourcing. function Test-Administrator { <# .SYNOPSIS Returns $true if the current process has administrator privileges. #> $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object System.Security.Principal.WindowsPrincipal($id) return $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } function Request-Elevation { <# .SYNOPSIS If not running as admin, spawns an elevated PowerShell process to re-run the current script. The calling script should exit after this call (the elevated process takes over). Returns $true if elevation was requested (parent process should exit). Returns $false if already elevated. #> param( [Parameter(Mandatory)] [string]$ScriptPath, [string[]]$ScriptArguments = @(), [string]$BootstrapLogPath = $null, [switch]$Wait, [int]$TimeoutSeconds = 120 ) if (Test-Administrator) { return @{ Requested = $false Completed = $true ExitCode = 0 Error = $null } } Write-Host '' Write-Host ' Elevation required. Requesting administrator privileges...' -ForegroundColor Yellow Write-Host '' try { $logPath = if ($BootstrapLogPath) { $BootstrapLogPath } else { Join-Path (Split-Path $ScriptPath -Parent) 'internal\data\logs\elevated-bootstrap.log' } $scriptPathEsc = $ScriptPath.Replace("'", "''") $logPathEsc = $logPath.Replace("'", "''") $argInvocationText = if ($ScriptArguments.Count -gt 0) { ($ScriptArguments | ForEach-Object { # Keep parameter-like tokens (e.g. -AutoRegisterRunner) unquoted so # PowerShell binds them as named/switch parameters. if ($_ -match '^-[A-Za-z]') { $_ } else { "'" + $_.Replace("'", "''") + "'" } }) -join ' ' } else { '' } $invokeLine = if ([string]::IsNullOrWhiteSpace($argInvocationText)) { "& '$scriptPathEsc'" } else { "& '$scriptPathEsc' $argInvocationText" } $bootstrap = @" `$ErrorActionPreference = 'Stop' function Write-BootstrapLog([string]`$message) { try { `$dir = Split-Path '$logPathEsc' -Parent if (-not (Test-Path `$dir)) { New-Item -Path `$dir -ItemType Directory -Force | Out-Null } `$line = "[{0}] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), `$message Add-Content -Path '$logPathEsc' -Value `$line -Encoding UTF8 } catch { } } Write-BootstrapLog "Elevated bootstrap started. ScriptPath=$scriptPathEsc" try { $invokeLine `$code = if (`$LASTEXITCODE -is [int]) { `$LASTEXITCODE } else { 0 } Write-BootstrapLog "Elevated bootstrap finished with ExitCode=`$code" exit `$code } catch { Write-BootstrapLog ("Elevated bootstrap unhandled error: " + `$_.Exception.Message) Write-BootstrapLog ("Stack: " + `$_.ScriptStackTrace) exit 1 } "@ $encoded = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($bootstrap)) $proc = Start-Process ` -FilePath 'powershell.exe' ` -ArgumentList "-NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded" ` -Verb RunAs ` -PassThru ` -ErrorAction Stop if ($Wait) { if ($TimeoutSeconds -gt 0) { $null = $proc.WaitForExit($TimeoutSeconds * 1000) } else { $proc.WaitForExit() } if (-not $proc.HasExited) { return @{ Requested = $true Completed = $false ExitCode = $null Error = $null } } return @{ Requested = $true Completed = $true ExitCode = $proc.ExitCode Error = $null } } return @{ Requested = $true Completed = $null ExitCode = $null Error = $null } } catch { Write-Host " Failed to request elevation: $_" -ForegroundColor Red return @{ Requested = $false Completed = $false ExitCode = $null Error = $_ } } }