Search code examples
powershellcmdvbscriptquotingprocess-elevation

Launching a batch file with elevation via a PowerShell command from VBS


any idea to fix the following that I have with my script?

Set objShell = CreateObject("Wscript.Shell")
 objShell.Run("powershell -Command "Start-Process 'cmd' -Verb RunAs -ArgumentList '/c "C:\Temp\CAL.bat"'"")

Error: Expected ')'

I took the idea from Running Powershell from vbs with command as parameter, but my little experience with these things does not help me.


Solution

    • You need to satisfy VBScript's own syntax rules first:

      • " chars. embedded inside "..." must be escaped as ""
    • The resulting string must satisfy PowerShell's syntax, which has two aspects:

      • Needing to \-escape " chars. that should be considered a verbatim part of the command(s) passed to the -Command (-c) parameter of powershell.exe, the Windows PowerShell CLI.

        • Unescaped " chars. are assumed to have purely syntactic function on the command line, and are removed during command-line parsing.
      • Needing to ensure that the resulting string - with the \ before " removed - is valid PowerShell syntax.

    • Thus, use \""

      • Note: In case the process launched via Start-Process is another PowerShell instance launched with (possibly implied) -Command (rather than a batch file, as in this case), two layers of escaping are required in the inner call: \\\""

    Therefore:

    Set objShell = CreateObject("Wscript.Shell")
    objShell.Run "powershell -Command ""Start-Process cmd -Verb RunAs -ArgumentList '/c \""C:\Temp\CAL.bat\""'"""
    

    Note:

    • Since objShell.Run() does not call via cmd.exe, there is no strict need to enclose the -Command argument in ""..."" overall, which makes the above command a bit more readable.

    • However, not doing so subjects what are then technically multiple arguments to -Command to whitespace normalization (runs of multiple spaces are folded into a single space); while that is typically not a problem, it can be, namely if such multi-space runs must be preserved as part of what PowerShell should see as a string literal.

    • In your specific case, since you're calling a batch file, you can further simplify your command for that reason: Start-Process accepts batch-file paths directly as executables, i.e. as the arguments to the (positionally implied) -FilePath parameter; therefore, omitting all optional quoting, the simplest formulation of your specific command is:

      Set objShell = CreateObject("Wscript.Shell")
      objShell.Run "powershell -Command Start-Process C:\Temp\CAL.bat -Verb RunAs"
      

    If you want the entire invocation to run invisibly, there are two aspects:

    • To hide the immediately launched process, pass 0 as the value of the 2nd parameter (intWindowStyle) to objShell.Run().

    • To hide the indirectly launched process, via Start-Process, pass -WindowStyle Hidden to it.

    Therefore:

    Set objShell = CreateObject("Wscript.Shell")
    objShell.Run "powershell -Command Start-Process C:\Temp\CAL.bat -Verb RunAs -WindowStyle Hidden", 0
    

    Separately, if you want the processes to run synchronously, i.e. if you want the object.Run() call to wait until all launched processes have finished:

    • Pass true as the value of the 3rd parameter (bWaitOnReturn) to objShell.Run() to make the immediately launched process execution synchronous.

      • Note: Doing so makes object.Run() return the exit code of that process; to capture it in VBScript code, use (...) around the arguments and assign the return value to a variable:

        exitCode = objShell.Run(..., ..., true)
        
    • To also execute the indirectly launched process synchronously, i.e. the elevated one launched via Start-Process -Verb RunAs, pass -Wait to it.

      • To also capture the exit code of the elevated process, additionally pass the -PassThru switch, which makes Start-Process output a process-information object whose .ExitCode property you can access, and which you can pass to exit

    Therefore, if you want to combine invisible and synchronous execution, as well as capture the exit code:

    Set objShell = CreateObject("Wscript.Shell")
    exitCode = objShell.Run("powershell -Command exit (Start-Process C:\Temp\CAL.bat -Verb RunAs -WindowStyle Hidden -Wait -PassThru).ExitCode", 0, true)