Public/Windows/Request-WindowsAdminRights.ps1
# Original Script Created by mklement0 on StackOverflow # # REFACTOR: Code quality. function Request-WindowsAdminRights { [CmdletBinding()] param( [switch]$NoExit, [switch]$HiddenWindow ) $isWin = $env:OS -eq 'Windows_NT' # Simply return, if already elevated. if (($isWin -and (net.exe session 2>$null)) -or (-not $isWin -and 0 -eq (id -u))) { Write-Verbose "(Now) running as $(("superuser", "admin")[$isWin])." return } # Get the relevant variable values from the calling script's scope. $scriptPath = $PSCmdlet.GetVariableValue('PSCommandPath') $scriptBoundParameters = $PSCmdlet.GetVariableValue('PSBoundParameters') $scriptArgs = $PSCmdlet.GetVariableValue('args') Write-Verbose ("This script, `"$scriptPath`", requires " + ("superuser privileges, ", "admin privileges, ")[$isWin] + ("re-invoking with sudo...", "re-invoking in a new window with elevation...")[$isWin]) # Note: # * On Windows, the script invariably runs in a *new window*, and by design we let it run asynchronously, in a stay-open session. # * On Unix, sudo runs in the *same window, synchronously*, and we return to the calling shell when the script exits. # * -inputFormat xml -outputFormat xml are NOT used: # * The use of -encodedArguments *implies* CLIXML serialization of the arguments; -inputFormat xml presumably only relates to *stdin* input. # * On Unix, the CLIXML output created by -ouputFormat xml is not recognized by the calling PowerShell instance and passed through as text. # * On Windows, the elevated session's working dir. is set to the same as the caller's (happens by default on Unix, and also in PS Core on Windows - but not in *WinPS*) # Determine the full path of the PowerShell executable running this session. # Note: The (obsolescent) ISE doesn't support the same CLI parameters as powershell.exe, so we use the latter. $psExe = (Get-Process -Id $PID).Path -replace '_ise(?=\.exe$)' if (0 -ne ($scriptBoundParameters.Count + $scriptArgs.Count)) { # ARGUMENTS WERE PASSED, so the CLI must be called with -encodedCommand and -encodedArguments, for robustness. # !! To work around a bug in the deserialization of [switch] instances, replace them with Boolean values. foreach ($key in @($scriptBoundParameters.Keys)) { if (($val = $scriptBoundParameters[$key]) -is [switch]) { $null = $scriptBoundParameters.Remove($key); $null = $scriptBoundParameters.Add($key, $val.IsPresent) } } # Note: If the enclosing script is non-advanced, *both* # !! Be sure to pass @() when $args is $null (advanced script), otherwise a scalar $null will be passed on reinvocation. # Use the same serialization depth as the remoting infrastructure (1). $serializedArgs = [System.Management.Automation.PSSerializer]::Serialize(($scriptBoundParameters, (@(), $scriptArgs)[$null -ne $scriptArgs]), 1) $NoExitStr = If ($NoExit) {'-noexit '} Else {''} $HideWindowStr = If ($HiddenWindow) {'-windowstyle hidden '} Else {''} # The command that receives the (deserialized) arguments. # Note: Since the new window running the elevated session must remain open, we do *not* append `exit $LASTEXITCODE`, unlike on Unix. $cmd = 'param($bound, $positional) Set-Location "{0}"; & "{1}" @bound @positional' -f (Get-Location -PSProvider FileSystem).ProviderPath, $scriptPath if ($isWin) { Start-Process -Verb RunAs $psExe ($HideWindowStr+$NoExitStr+'-encodedCommand {0} -encodedArguments {1}' -f [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd)), [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($serializedArgs))) #Start-Process -Verb RunAs $psExe ('-noexit -encodedCommand {0} -encodedArguments {1}' -f [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd)), [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($serializedArgs))) } else { sudo $psExe -encodedCommand ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))) -encodedArguments ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($serializedArgs))) } } else { # NO ARGUMENTS were passed - simple reinvocation of the script with -c (-Command) is sufficient. # Note: While -f (-File) would normally be sufficient, it leaves $args undefined, which could cause the calling script to break. # Also, on WinPS we must set the working dir. if ($isWin) { Start-Process -Verb RunAs $psExe ($HideWindowStr+$NoExitStr+'-c Set-Location "{0}"; & "{1}"' -f (Get-Location -PSProvider FileSystem).ProviderPath, $scriptPath) } else { # Note: On Unix, the working directory is always automatically inherited. sudo $psExe -c "& `"$scriptPath`"; exit $LASTEXITCODE" } } # EXIT after reinvocation, passing the exit code through, if possible: # On Windows, since Start-Process was invoked asynchronously, all we can report is whether *it* failed on invocation. exit ($LASTEXITCODE, (1, 0)[$?])[$isWin] } |