Search code examples
powershellbatch-filecmd

Running Bat From PowerShell


I've got a script that eventually needs to start a batch file. The command I'm running is:

Start-Process "cmd.exe" ".\Batch.bat" -Verb runas

When I run this, the Command Prompt window opens, and it opens to the correct folder, but doesn't actually start the batch file. Once the window opens, I can manually enter:

.\Batch.bat

and that seems to work from there. Also, if I try to bypass CMD entirely and run the batch file in PowerShell, it gives me the first three lines that display when I run it in CMD, but doesn't give me the first prompt I need to enter, so that's why I'm trying to do this in a new CMD window. Any idea what I'm missing?


Solution

  • tl;dr

    To run the batch file in an elevated session in a new window that uses the same working directory as the caller and stays open:

    Start-Process cmd.exe "/k cd `"$PWD`" & .\Batch.bat" -Verb RunAs
    
    • Note the use of /k, which instructs cmd.exe to execute the commands that come after it, and then keeps the session open (by contrast, /c would exit the session after the commands complete, which would automatically close the window - run cmd /? for details).

    • If you try to pass a command without preceding it with either /c or /k, cmd.exe effectively ignores the command and enters an interactive session, which is what you saw.

    To invoke the batch file in an auto-closing window in the C:\Windows\System32 directory, you can invoke it directly:

    Start-Process '.\Batch.bat' -Verb RunAs
    

    Background information:

    Windows treats batch files (*.cmd, *.bat) as if they were executables, so you don't necessarily need to invoke them via cmd.exe explicitly:

    Start-Process '.\Batch.bat' -Verb RunAs
    

    The above is the equivalent of cmd.exe /c .\Batch.bat, which means that the batch file executes and the cmd.exe session then exits, i.e. the window automatically closes.


    If you want to execute the batch file in a cmd.exe session that stays open, you must use an explicit invocation of cmd.exe with /k (run cmd /? for details):

    Start-Process cmd.exe "/k `"$PWD\Batch.bat`"" -Verb RunAs
    

    Note the use of $PWD to refer to the caller's current directory, which is necessary, because -Verb RunAs defaults to C:\Windows\System32 as the working directory when -Verb RunAs is used to invoke unmanaged (non-.NET) applications.

    That is, Start-Process with -Verb RunAs:

    • never preserves the caller's working directory and never honors a -WorkingDirectory argument when invoking unmanaged (non-.NET) executables, notably including cmd.exe / a batch file or powershell.exe, the Windows PowerShell CLI (even though the latter is built on top of .NET Framework, the .exe file itself is unmanaged)

    • does preserve it / does honor -WorkingDirectory for managed (.NET) executables, notably including pwsh.exe, the PowerShell (Core) 7+ CLI (though conceivably there are other executables that set their own working dir. on startup).


    If you additionally want to preserve the caller's working directory, you must therefore include a cd command in your cmd.exe call:

    Start-Process cmd.exe "/k cd `"$PWD`" & .\Batch.bat" -Verb RunAs
    

    Note:

    • Using the automatic $PWD variable to refer to PowerShell's current directory (location) typically works fine, but won't work in two cases:

      • If PowerShell's current location isn't a file-system location; e.g., if it is a location on the HKLM: registry drive.

      • If the current location is a file-system location that is based on a PowerShell-only drive (established with New-PSDrive), which the outside world doesn't know about.

    • If either case applies, use the following instead of $PWD, which always works:

        $((Get-Location -PSProvider FileSystem).ProviderPath)