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