Search code examples
powershellbatch-filecmd

CD command in batch file does not work in PowerShell


Here is my batch file:

CD c:/
PAUSE

When run in Windows PowerShell, I get this output:

PS C:\some\path\here> .\myscript.bat

C:\some\path\here>CD c:/

c:\>PAUSE
Press any key to continue . . .
PS C:\some\path\here>

After the program terminates, I'm back where I started!

Meanwhile, when run in cmd:

C:\some\path\here>.\myscript.bat

C:\some\path\here>CD c:/

c:\>PAUSE
Press any key to continue . . .

c:\>

I get the behavior I want, where I remain in the C:\ directory after the program ends.

Why is this the case, and how can I get this behavior in PowerShell too?


Solution

    • Running a batch file (*.cmd or *.bat) from PowerShell of necessity involves a cmd.exe child process. (PowerShell itself doesn't understand batch files, only the batch-file interpreter, cmd.exe does; as a separate executable, it invariably runs in a child process.)

    • A child process fundamentally cannot set its parent's working directory (nor can it modify its environment variables). Thus, whatever a batch file sets as its working directory has no effect on a PowerShell caller.

    • It follows that changing the caller's working directory is only possible via code that runs in-process.

      • Both cmd.exe and PowerShell run their script files - batch files (*.cmd, *.bat) and PowerShell scripts (*.ps1), respectively - in-process, enabling script files to change the process' working directory session-globally in their native shell - this is what you saw when you called your batch file from a cmd.exe session.

      • In PowerShell, you can create a *.ps1 file with the same content as your batch file (in this case), and calling it (e.g. .\myscript.ps1) will change the PowerShell session's working directory.

    • Conversely, if you want to scope (limit) the working-directory change to a given script file, so that the original working directory is restored on exiting the script:

      • In cmd.exe, you can achieve this by enclosing batch-file code in setlocal and endlocal statements (the latter is often omitted, because it is implied upon exiting a batch file), which not only restores the original working directory, but also the original environment variables.

      • PowerShell offers no analogous mechanism,[1] so the previous state must be saved and restored manually:

        • To restore the previous working directory (location), you must use the following idiom:

          $oldPwd = $PWD
          try { 
            # ... script code that changes the working dir. goes here 
          } finally { Set-Location -LiteralPath $oldPwd }
          
        • That said, it is for this reason that Set-Location (cd) calls are best avoided in PowerShell scripts.


    [1] However, PowerShell by default scopes its shell variables (and other definitions, such as functions) defined in a script to that script. Shell variables (e.g. $foo) are PowerShell-specific variables that are distinct from environment variables (which in PowerShell are referenced with prefix $env:); modifying the latter does take effect process-globally (and affects any child processes, which inherit copies of them) and - unlike in batch files - there's no scoping mechanism.
    Note that cmd.exe makes no distinction between shell and environment variables: any variable that is defined is implicitly an environment variable.