I am trying to launch an elevated CMD window from PowerShell but I am running into some issues. Below is the Code I have now. There is an admin account on the machine that has the username of "test" and a Password of "test"
$username = "test"
$password = ConvertTo-SecureString "test" -AsPlainText -Force
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password
Start-Process "cmd.exe" -Credential $cred
This is all working fine for running an application from the user profile with no administrator rights that this script sits in, but when calling the cmd.exe it launches as expected with elevated rights but then immediately closes.
I have also tried calling it with the following:
Start-Process "cmd.exe" -Credential $cred -ArgumentList '/k'
This also is not working.
I tested the elevated permissions by passing in an argument as follows and this works fine.
Start-Process "cmd.exe" -Credential $cred -ArgumentList 'dir > dir.txt'
This will write out a dir.txt file to the C:\Windows\System32\WindowsPowerShell\v1.0 directory which is blocked on the user account but not for the administrator account test.
Any help on getting a persistent cmd window to show would be greatly appreciated.
Thanks
Note: SomeShinyObject came up with the fundamentals of the approach in his answer, but his parameter-passing technique is not robust (update: since corrected) - do not use script blocks in lieu of strings - see bottom.
-Verb RunAs
is what makes Start-Process
launch a process elevated.
However, -Verb RunAs
cannot be combined with the -Credential
parameter, so you cannot directly control under what user account the elevation happens - but that is generally not necessary:
Security caveats:
If you still want to implement your script as specified, a workaround requires nesting 2 Start-Process
calls:
The 1st one runs an (invariably) non-elevated command invisibly in the context of the specified user - assumed to be an administrative user - using -Credential
.
-WorkingDir
argument that the specified user is known to have permissions to access[1] - otherwise, the call fails with a The directory name is invalid.
error; -WorkingDirectory C:\
is usually a safe bet.The 2nd one, embedded in the 1st, then uses -Verb RunAs
to run the target command elevated, which then happens in the context of the specified user.
Note: Even with a credentials object that includes the password, you will still get the yes/no UAC prompt to confirm the intent to elevate - unless UAC has been turned off (which is not advisable).
The working directory will invariably be $env:SYSTEMROOT\System32
; -Verb RunAs
ignores even a -WorkingDirectory
value; if you want to change to a specific directory, embed a cd
command in the command passed to cmd.exe
; the bottom section of this related answer shows this technique with a powershell.exe
/ Set-Location
call.
This command does exactly what you asked for - please note the security caveat:
# Construct the credentials object
$username = "jdoe"
# CAVEAT: Storing a password as plain text is a security risk in general.
# Additionally, if you let non-administrative users execute this
# code with a stored password, you're effectively giving them
# administrative rights.
$password = ConvertTo-SecureString "test" -AsPlainText -Force
$cred = New-Object PSCredential -Args $username, $password
# Start an elevated Command Prompt (cmd) as user $username.
# -WorkingDirectory C:\ is used to ensure that the target user has
# a valid working dir.
Start-Process powershell.exe -Credential $cred -WorkingDirectory C:\ -WindowStyle Hidden `
'-noprofile -command "Start-Process cmd.exe -Verb RunAs"'
Note that the embedded, 2nd command is passed as a single string to the (implied)
-ArgumentList
(a.k.a. -Args
) parameter.
In this simple case, with only 1 level of embedded quoting - the "
instances inside the '...'
string - and no need for expansions (string interpolation), passing a single string is a viable option, but with more sophisticated commands quoting gets tricky.
-ArgumentList
is defined as type [string[]]
, i.e., an array of string arguments. If you pass multiple, ,
-separated arguments, it is PowerShell that synthesizes the command line for you:
The following command demonstrates this technique: It is a variant that passes a command for cmd.exe
to execute through, and uses a variable reference in that command:
$msg = 'This is an elevated Command Prompt.'
Start-Process powershell.exe -Credential $cred -WindowStyle Hidden -Args `
'-noprofile', '-command', "Start-Process cmd.exe -Verb RunAs -Args /k, echo, '$msg'"
The cmd.exe
command that is ultimately executed (with elevation) is:
cmd /k echo This is an elevated Command Prompt.
tl;dr
At first glance, script blocks ({ ... }
) seem like a convenient option:
Start-Process cmd -ArgumentList { /k echo hi! }
The above executes cmd /k echo hi!
in a new console window, as expected.
The syntax is convenient, because the { ... }
seemingly provide a context in which quoting is easy: you're free to use embedded "
and '
instances to construct your command line.
However, what happens behind the scenes is that a script block is converted to a string, because that's the type of argument(s) -ArgumentList
expects, and when a script block is converted to a string, its literal contents - everything between {
and }
- is used.
This means that no string interpolation takes place, so you cannot use variables or subexpressions.
Take this attempt to pass a command based on a variable:
Start-Process cmd -ArgumentList { /k echo Honey, I`'m $HOME! }
What this will execute is: cmd /k echo Honey, I'm $HOME!
- $HOME
was not expanded.
By contrast, passing either an interpolated string or the arguments individually works as intended:
# As a single string (argument list):
Start-Process cmd -ArgumentList "/k echo Honey, I'm $HOME!"
# As an array of arguments:
Start-Process cmd -ArgumentList /k, echo, "Honey, I'm $HOME!"
$HOME
is expanded (interpolated) in both cases, and something like
cmd /k echo Honey, I'm C:\Users\jdoe
is executed.
[1] Note that while administrators usually have access to the same directories that a given user has, this only applies if the process running with an administrator user identity is already elevated, which is by definition not the case here; of necessity, a non-elevated process with as the target admin user must be created first, before elevation can be requested as that user.