What is the setlocal / endlocal equivalent for PowerShell?
In batch files, all shell variables are environment variables too; therefore, setlocal ... endlocal
provides a local scope for environment variables too.
By contrast, in PowerShell, shell variables (e.g., $var
) are distinct from environment variables (e.g., $env:PATH
) - a distinction that is generally beneficial.
Given that the smallest scope for setting environment variables is the current process - and therefore the entire PowerShell session, you must manage a smaller custom scope manually, if you want to do this in-process (which is what setlocal ... endlocal
does in cmd.exe
, for which PowerShell has no built-in equivalent; to custom-scope shell variables, use & { $var = ...; ... }
):
In-process approach: manual management of a custom scope:
To ease the pain somewhat, you can use a script block ({ ... }
) to provide a distinct visual grouping of the command, which, when invoked with &
also create a new local scope, so that any aux. variables you define in the script block automatically go out of scope (you can write this as a one-line with ;
-separated commands):
& {
$oldVal, $env:MYLANG = $env:MYLANG, 'EN'
my-cmd dostuff -o out.csv
$env:MYLANG = $oldVal
}
More simply, if there's no preexisting MYLANG
value that must be restored:
& { $env:MYLANG='EN'; my-cmd dostuff -o out.csv; $env:MYLANG=$null }
$oldVal, $env:MYLANG = $env:MYLANG, 'EN'
saves the old value (if any) of $env:MYLANG
in $oldVal
while changing the value to 'EN'
; this technique of assigning to multiple variables at once (known as destructuring assignment in some languages) is explained in Get-Help about_Assignment_Operators
, section "ASSIGNING MULTIPLE VARIALBES".
A more proper and robust but more verbose solution is to use try { ... } finally { ... }
:
try {
# Temporarily set/create $env:MYLANG to 'EN'
$prevVal = $env:MYLANG; $env:MYLANG = 'EN'
my-cmd dostuff -o out.csv # run the command(s) that should see $env:MYLANG as 'EN'
} finally { # remove / restore the temporary value
# Note: if $env:MYLANG didn't previously exist, $prevVal is $null,
# and assigning that back to $env:MYLANG *removes* it, as desired.
$env:MYLANG = $prevVal
}
Note, however, that if you only ever call external programs with the temporarily modified environment, there is no strict need for try
/ catch
, because external programs never cause PowerShell errors as of PowerShell 7.1, though that may change in the future.
To facilitate this approach, this answer to a related question offers convenience function
Invoke-WithEnvironment
, which allows you to write the same invocation as:
# Define env. var. $env:MYLANG only for the duration of executing the commands
# in { ... }
Invoke-WithEnvironment @{ MYLANG = 'EN' } { my-cmd dostuff -o out.csv }
Alternatives, using an auxiliary process:
By using an auxiliary process and only setting the transient environment variable there,
you avoid the need to restore the environment after invocation
but you pay a performance penalty, and invocation complexity is increased.
Using an aux. cmd.exe
process:
cmd /c "set `"MYLANG=EN`" & my-cmd dostuff -o out.csv"
Note:
Outer
"..."
quoting was chosen so that you can reference PowerShell variables in your command; embedded"
must then be escaped as`"
Additionally, the arguments to the target command must be passed according to
cmd.exe
's rules (makes no difference with the simple command at hand).
Using an aux. child PowerShell session:
# In PowerShell *Core*, use `pwsh` in lieu of `powershell`
powershell -nop -c { $env:MYLANG = 'EN'; my-cmd dostuff -o out.csv }
Note:
Starting another PowerShell session is expensive.
Output from the script block (
{ ... }
) is subject to serialization and later deserialization in the calling scope; for string output, that doesn't matter, but complex objects such as[System.IO.FileInfo]
deserialize to emulations of the originals (which may or may not be problem).