Last active
December 20, 2019 09:59
-
-
Save pcrockett-pathway/0aa804f9564fc60668218b6de940241f to your computer and use it in GitHub Desktop.
Run commands or start interactive PowerShell sessions as any user, elevated or not
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[CmdletBinding()] | |
param( | |
[Parameter()] | |
[string]$UserName, | |
[Parameter()] | |
[string]$Command, | |
[Parameter()] | |
[string]$WorkingDirectory, | |
[Parameter()] | |
[switch]$Elevate | |
) | |
$ErrorActionPreference = "Stop" | |
Set-StrictMode -Version 4.0 | |
$killPowerShellWhenFinished = $false | |
if ($WorkingDirectory) { | |
$WorkingDirectory = (Resolve-Path $WorkingDirectory).ProviderPath | |
} else { | |
$WorkingDirectory = (Get-Location).ProviderPath | |
} | |
$processStartArgs = @{ | |
FilePath = "powershell.exe"; | |
PassThru = $true; | |
} | |
function getCreds([string]$userName) { | |
$password = Read-Host -AsSecureString "Enter password for $userName" | |
return New-Object System.Management.Automation.PSCredential $userName, $password | |
} | |
function encodeCommand([string]$originalCommand) { | |
$commandBits = [Text.Encoding]::Unicode.GetBytes($originalCommand) | |
return [Convert]::ToBase64String($commandBits) | |
} | |
function wrapCommand([string]$originalCommand) { | |
if (!$Elevate) { | |
throw "You don't need to call wrapCommand if you're not elevating to admin." | |
} | |
$encodedCommand = encodeCommand $originalCommand | |
if ($Command) { | |
# The user specified a command. This should run and immediately exit afterward. | |
return @" | |
Start-Process -FilePath powershell.exe -Verb runas -Wait -ArgumentList "-EncodedCommand", "$encodedCommand" | |
"@ | |
} else { | |
# This user has NOT specified a command. They want to run an interactive shell. Include the -NoExit parameter. | |
return @" | |
Start-Process -FilePath powershell.exe -Verb runas -Wait -ArgumentList "-NoExit", "-EncodedCommand", "$encodedCommand" | |
"@ | |
} | |
} | |
function setupNoninteractiveShell() { | |
# Execute the command in a new shell, and make the shell exit when finished. | |
$commandToExec = "Set-Location ""$WorkingDirectory"";" + $Command | |
if ($UserName) { | |
$processStartArgs["Credential"] = getCreds $UserName | |
$processStartArgs["WorkingDirectory"] = $env:windir # Set working directory to something every user has permission to see | |
if ($Elevate) { | |
# We can't elevate to admin AND change user accounts at the same time. | |
# We have to launch a PowerShell window as the new user first, and then | |
# make that shell launch ANOTHER elevated admin shell from there. | |
$wrapperCommand = wrapCommand $commandToExec | |
$encodedCommand = encodeCommand $wrapperCommand | |
} else { | |
$encodedCommand = encodeCommand $commandToExec | |
} | |
} else { | |
# We're not switching users, just elevating to admin and staying as the | |
# current user. Easy peasy, no wrapping tricks required. | |
if ($Elevate) { | |
$processStartArgs["Verb"] = "runas"; | |
} | |
$encodedCommand = encodeCommand $commandToExec | |
} | |
$processStartArgs["ArgumentList"] = "-EncodedCommand", $encodedCommand | |
} | |
function setupInteractiveShell() { | |
# Open a new interactive shell | |
$commandToExec = "Set-Location ""$WorkingDirectory""" | |
if ($UserName) { | |
$processStartArgs["Credential"] = getCreds $UserName | |
$processStartArgs["WorkingDirectory"] = $env:windir # Set working directory to something every user has permission to see | |
if ($Elevate) { | |
# We can't elevate to admin AND change user accounts at the same time. | |
# We have to launch a PowerShell window as the new user first, and then | |
# make that shell launch ANOTHER elevated admin shell from there. | |
$wrapperCommand = wrapCommand $commandToExec | |
$encodedCommand = encodeCommand $wrapperCommand | |
$processStartArgs["ArgumentList"] = "-EncodedCommand", $encodedCommand | |
# Notice we're leaving off the -NoExit parameter. This is because the | |
# wrapped command SHOULD exit immediately. We want the SECOND window to | |
# stay open, not the first window. | |
} else { | |
$encodedCommand = encodeCommand $commandToExec | |
$processStartArgs["ArgumentList"] = "-NoExit", "-EncodedCommand", $encodedCommand | |
# Unfortunately there is weirdness with input and output while both | |
# PowerShell windows stay open. We have to sacrifice this session | |
# in order to make the other session work. That's probably OK, as | |
# this is going to be an interactive shell so it's unlikely this | |
# will actually cause a script to stop working. | |
$script:killPowerShellWhenFinished = $true | |
} | |
} else { | |
# We're not switching users, just elevating to admin. Easy peasy, no tricks required. | |
$encodedCommand = encodeCommand $commandToExec | |
if ($Elevate) { | |
$processStartArgs["Verb"] = "runas"; | |
} | |
$processStartArgs["ArgumentList"] = "-NoExit", "-EncodedCommand", $encodedCommand | |
} | |
} | |
if ($Command) { | |
setupNoninteractiveShell | |
} else { | |
setupInteractiveShell | |
} | |
$process = Start-Process @processStartArgs | |
if ($killPowerShellWhenFinished) { | |
$Host.SetShouldExit(0) | |
} else { | |
$process.WaitForExit() | |
# Unfortunately we don't get an exit code back when the user specifies a | |
# username for the process to run under. | |
if ($null -ne $process.ExitCode -and 0 -ne $process.ExitCode) { | |
throw "powershell.exe exited with code $($process.ExitCode)." | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment