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