Run a command as a different user in Powershell

Run a command as a different user in Powershell

There are three main ways to run a command as a different user in Powershell, besides the classing Right click shift. This article will show you how to do that, within the same Powershell session.
By the same Powershell session, I mean something like this:

  • You’re logged on as ITDroplets\UserA.
  • You have a powershell script/console running as UserA.
  • Within that powershell script/console, you want to run a command as ITDroplets\UserB.

In the Options below, I will consider the above example and I will run “Get-Process Explorer” as UserB. This is very handy when running elevated commands, for instance when UserA is a standard user account and UserB has local admin rights. Of course, Get-Process Explorer doesn’t really need elevation 🙂
Remember that the examples are super concentrated, which means I didn’t add any check to see if the command ran successfully etc. They’re there as pure examples, you can then shape them to fit your needs.

Option 1 – System.Diagnostics.ProcessStartInfo

#Get UserB credential
$Credential = Get-Credential itdroplets\UserB

#Use System.Diagnostics to start the process as UserB
$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo
#With FileName we're basically telling powershell to run another powershell process
$ProcessInfo.FileName = "powershell.exe"
#CreateNoWindow helps avoiding a second window to appear whilst the process runs
$ProcessInfo.CreateNoWindow = $true
#Note the line below contains the Working Directory where the script will start from
$ProcessInfo.WorkingDirectory = $env:windir
$ProcessInfo.RedirectStandardError = $true 
$ProcessInfo.RedirectStandardOutput = $true 
$ProcessInfo.UseShellExecute = $false
#The line below is basically the command you want to run and it's passed as text, as an argument
$ProcessInfo.Arguments = "Get-Process Explorer"
#The next 3 lines are the credential for UserB, as you can see, we can't just pass $Credential
$ProcessInfo.Username = $Credential.GetNetworkCredential().username
$ProcessInfo.Domain = $Credential.GetNetworkCredential().Domain
$ProcessInfo.Password = $Credential.Password
#Finally start the process and wait for it to finish
$Process = New-Object System.Diagnostics.Process 
$Process.StartInfo = $ProcessInfo 
$Process.Start() | Out-Null 
$Process.WaitForExit() 
#Grab the output
$GetProcessResult = $Process.StandardOutput.ReadToEnd()
#Print the Job results
$GetProcessResult

Option 1 is the one that seems to be working better than others. It’s similar to Option 2 as it also starts a new process, but handles the output way better than Option 2. It basically starts a new process as UserB and then grab the output, stored in $GetProcessResult. The comments should explain everything on what the script is doing.

Option 2 – Start-Process

#Get UserB credential
$Credential = Get-Credential itdroplets\UserB

#Define a result file where to temporarily store the output of the process.
#Remove the file if it already exists
$ResultFile = "$($env:temp)\_tmpresult.txt"
If (Test-Path $ResultFile)  {
  Remove-Item $ResultFile
}

#Set the Arguments for the process
$ProcessArguments = "Get-Process Explorer"
#Start the process and specify %WINDIR% as working directory
(Start-Process -FilePath "powershell.exe" -Credential $Credential -ArgumentList $ProcessArguments -WorkingDirectory $env:windir -NoNewWindow -PassThru -RedirectStandardOutput $ResultFile).WaitForExit()

#Read the file, if it exists and remove it afterwards
If (Test-Path $ResultFile)  {
  $Result = Get-Content $ResultFile
  Remove-Item $ResultFile
  $Result
}Else  {
  write-error "File $($ResultFile) does not exist!"
}

Very similar to Option 1, this way to run a command as a different user in Powershell has one flow: it’s not possible to grab the output from Start-Process, unless it’s redirected to a file! So that means we need to store the output somewhere and then grab it back. Thankfully, the file is created as UserA, which means we can target UserA’s %temp% folder to store it.  The above script will also delete it afterwards and at the beginning of it, should the file already exist for some reason.

Option 3 – Start-Job

#Get UserB credential
$Credential = Get-Credential itdroplets\UserB
#Start the Job as UserB
$GetProcessJob = Start-Job -ScriptBlock {Get-Process Explorer} -Credential $Credential
#Wait until the job is completed
Wait-Job $GetProcessJob
#Get the Job results
$GetProcessResult = Receive-Job -Job $GetProcessJob
#Print the Job results
$GetProcessResult

This one’s my favorite to run a command as a different user in Powershell, however it does have an issue and you may receive the error below in case you’ve got a mapped drive on UserA’s profile: An error occurred while starting the background process. Error reported: The directory name is invalid.

So, there are a couple of ways to fix this but unfortunately, I don’t like them and I don’t think the’re good solutions.

Solution 1

The first solution would be changin the “Start In” setting in the Powershell shortcut (in Windows 10 it’s located here: %appdata%\Microsoft\Windows\Start Menu\Programs\Windows PowerShell). You must change %HOMEDRIVE%%HOMEPATH% to something like %WinDir% for example. If you don’t want o edit the original shortcut, you can just clone it and edit the cloned one.

Once done, you can just double click on it as UserA and you should be good. This won’t require admin rights, but it requires a restart of the console if you have it opened!

Solution 2

This one is a bit of an overkill if you ask me and it does require UserA to have local admin rights, or at least write access to %windir%\System32\WindowsPowerShell\v1.0. So basically you want to create a file named profile.ps1 and add something like this in it: Set-Location -Path $($env:windir)
This has to be done before running the script as UserB so that when the script tries to start the job as a different user, it’ll be able to launch the script as it’ll load the right location. If you want to script this, I would do something like this:

$ProfilePS1 = "$($env:windir)\System32\WindowsPowerShell\v1.0\profile.ps1"
$Profile = @"
Set-Location -Path $($env:windir)
"@
$ProfileExisted = 0

If (Test-Path $ProfilePS1)  {
  #If a profile already exist, back it up, then remove it
  Rename-Item $ProfilePS1 "$($env:windir)\System32\WindowsPowerShell\v1.0\profile.ps1.bck"
  Remove-Item $ProfilePS1
  $ProfileExisted = 1
}
Set-Content -Path "$($env:windir)\System32\WindowsPowerShell\v1.0\profile.ps1" -Value $Profile

#Add whatever you want the script to do.. like:
$Credential = Get-Credential itdroplets\UserB
$GetProcessJob = Start-Job -ScriptBlock {Get-Process Explorer} -Credential $Credential
Wait-Job $GetProcessJob
$GetProcessResult = Receive-Job -Job $GetProcessJob
$GetProcessResult

#Now, revert back the profile, if it existed
Remove-Item $ProfilePS1
If (Test-Path $ProfileExisted)  {
  Rename-Item "$($env:windir)\System32\WindowsPowerShell\v1.0\profile.ps1.bck" $ProfilePS1
}

So basically, the script checks if the file already exists, if it does, it renames it, creates a new one which includes $ProfilePS1’s content, finally runs Option 3’s script, deletes profile.ps1 and reverts back to the old one (if existed). If UAC is enabled, you must make sure you’re running this as Administrator!

Unfortunately, using Set-Location in the current session before hand, won’t work.

IT Droplets

IT Droplets