Skip to content

Instantly share code, notes, and snippets.

@pcrockett-pathway
Last active December 20, 2019 09:59
Show Gist options
  • Save pcrockett-pathway/0aa804f9564fc60668218b6de940241f to your computer and use it in GitHub Desktop.
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
[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