powershellpowershell-2.0powershell-3.0powershell-4.0

Launch Elevated CMD.exe from Powershell


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


Solution

  • 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.

    Security caveats:

    If you still want to implement your script as specified, a workaround requires nesting 2 Start-Process calls:

    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.


    Optional Reading: Why using script blocks in lieu of strings is ill-advised

    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.