Search code examples
powershellcmdelevated-privilegesstart-process

powershell.exe start-process -About "\" when using the Verb option


Thank you for your help.
I use Powershell in the Windows 10 command prompt and try to execute commands with administrative rights.
When I try the following three, a, b and c, I cannot start with "RunAs" as I think.
Does anyone know how to solve this problem?

(a) It can be executed successfully. The arguments are also passed correctly.
powershell.exe start-process -FilePath 'TestEnv.cmd' -ArgumentList '\"a\" \"B C\" \"d\"' -Verb Open

(b) Does not start after UAC confirmation.
powershell.exe start-process -FilePath 'TestEnv.cmd' -ArgumentList '\"a\" \"B C\" \"d\"' -Verb RunAs

(c) It is possible to invoke it, but the "B C" argument is not reflected in the "double quotes" argument, and "B C" is recognized separately.
powershell.exe start-process -FilePath 'TestEnv.cmd' -ArgumentList '"a" "B C" "d"' -Verb RunAs

Translated with www.DeepL.com/Translator (free version)


Solution

  • tl;dr

    REM From cmd.exe (Command Prompt) / a batch file:
    powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k {0} \"a\" \"B C\" \"d\"' -f ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))
    
    • Replace /k with /c to make the new window close automatically when the batch file exits.

    • The working directory for the batch file is C:\Windows\System32.

    If you also want to ensure that the batch file executes in the caller's current directory:

    REM From cmd.exe (Command Prompt) / a batch file:
    powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k cd \"{0}\" ^&^& {1} \"a\" \"B C\" \"d\"' -f $PWD.ProviderPath, ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))
    

    Read on for an explanation.


    The main problem is:

    • When you pass a batch-file path directly - as the executable - to Start-Process -Verb RunAs, there seems to be a problem with passing an argument list that contains double quotes (") - I don't know the reason, but the problem occurs in a layer below PowerShell, either in the underlying System.Diagnostics.ProcessStartInfo .NET API or possibly even in the underlying ShellExecuteExe WinAPI function.

    • (If you either need no arguments or none of them require ", you can pass the batch-file path directly as the executable; by default, because the underlying executable is cmd.exe, which is located in C:\Windows\System32, C:\Windows\System32 becomes the working directory, which you can override with -WorkingDirectory, but note that you must then refer to the batch file with a (possibly relative) path, if it is located elsewhere.)

    Workaround:

    • Instead of using the batch-file path directly as the executable to launch, use cmd.exe as the executable, and pass the batch-file path and all its arguments as arguments to the /c option (or /k, if you want to keep the new window open).

    • Unfortunately, with -Verb Runas, cmd.exe invariably uses C:\Windows\System32 as the new process' working directory - passing a startup directory with the -WorkingDirectory parameter is then ignored.

      • The same applies to powershell.exe, but, curiously, not to pwsh.exe (PowerShell [Core] v6+) and .NET-based executables, which (a) preserve the caller's working directory by default and (b) do honor a -WorkingDirectory argument.
    • Therefore, if your TestEnv.cmd batch file is located in the current directory - as opposed to a directory listed in the PATH environment variable ($env:PATH) - you must pass the batch file's full path to cmd.exe, which you can determine with Convert-Path.

      • Note: If you batch file is in the PATH, so to speak, this step isn't necessary.

      • Unfortunately, another workaround is then needed, in case the batch file's full path contains spaces: Because "..."-enclosing the batch-file path inexplicably fails, the individual spaces must be ^-escaped instead.

    From inside PowerShell this means your command must look like this (note that I'm using /k to keep the new window open, for diagnostic purposes; use /c to auto-close the window when the batch file exits):

    # From inside PowerShell:
    Start-Process cmd.exe -Verb RunAs (
      '/k {0} "a" "B C" "d"' -f ((Convert-Path 'TestEnv.cmd') -replace ' ', '^ ')
    )
    

    Caveat: Since cmd.exe is being invoked, C:\Windows\System32 is the working directory.
    If you also want to ensure that the batch file executes in the caller's current directory, you must prepend a cd command:

    # From inside PowerShell:
    Start-Process cmd.exe -Verb RunAs (
      '/k cd "{0}" && {1} "a" "B C" "d"' -f $PWD.ProviderPath, ((Convert-Path 'TestEnv.cmd') -replace ' ', '^ ')
    )
    

    Calling this from cmd.exe (Command Prompt) / a batch file, via Windows PowerShell's CLI, powershell.exe (note that in PowerShell [Core] v6+ it is now pwsh), adds additional complexity:

    • The " chars. must be escaped as \" so that PowerShell recognizes as them as part of the command to execute rather than as syntactic quoting around the CLI arguments.

    • ^ must be escaped as ^^ and, in the second command below, & as ^& to prevent cmd.exe from interpreting these characters (up front).

    REM From cmd.exe (Command Prompt) / a batch file:
    powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k {0} \"a\" \"B C\" \"d\"' -f ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))
    

    If you also want to ensure that the batch file executes in the caller's current directory:

    REM From cmd.exe (Command Prompt) / a batch file:
    powershell.exe -c Start-Process cmd.exe -Verb RunAs ('/k cd \"{0}\" ^&^& {1} \"a\" \"B C\" \"d\"' -f $PWD.ProviderPath, ((Convert-Path 'TestEnv.cmd') -replace ' ', '^^ '))