I am looking for a method to save all environment variables of the currently running process to a variable and restore them back from that saved state.
This is how far I got:
$OldEnvironment = Get-ChildItem env:
try
{
# Manipulate a variable
$env:PATH="C:\My\Utilities\Path;$env:PATH"
# Remove some
Remove-Item -Path Env:ALLUSERSPROFILE
Remove-Item -Path Env:APPDATA
Remove-Item -Path Env:LOCALAPPDATA
# Set a new one which we presume did not exist before
Set-Item -Path env:NEWSHINYVARIABLE "FOOBAR"
# Output current environment
Get-ChildItem env:|ft
}
finally
{
# Restore ...
$OldEnvironment|%{ Set-Item -Path "Env:$($_.Name)" "$($_.Value)" }
# ... alas there's a glitch here:
Get-ChildItem -Path env:NEWSHINYVARIABLE|ft
# NEWSHINYVARIABLE _still_ exists
}
Now my issue is to make sure that variables set inside the try
block don't leak out (like NEWSHINYVARIABLE
does). Removed variables aren't an issue, because they will simply get created anew.
How can also remove variables currently contained in env:
but not stored in $OldEnvironment
with a concise one-liner? (Please note: I do not want to resort to modules and background jobs which - as I understand - contain their own private environment. I want to stay with the try .. finally
method structurally.)
Use Compare-Object
to compare the content of the Env:
PowerShell drive before and after, and take appropriate actions to restore the values of preexisting or remove newly added environment variables:
# Set some sample env. vars.
$env:foo='original foo'
$env:bar='original bar'
# Get the current list of env. vars. and their values
$envVarsBefore = Get-ChildItem Env:
try {
# Perform sample operations that add, update, and delete environment variables.
$env:foo='changed foo'
$env:bar=$null # Remove $env:bar
$env:goaway='yes' # define a new var.
} finally {
# Restore the original environment, including removal of newly added variables.
Compare-Object -PassThru $envVarsBefore (Get-ChildItem Env:) -Property { '[{0}, {1}]' -f $_.Key, $_.Value } |
ForEach-Object {
# A newly added or updated variable: remove it.
# (If it was updated, it'll be restored with its original value below.)
if ($_.SideIndicator -eq '=>') {
Remove-Item Env:$($_.Name)
}
else { # $_.SideIndicator -eq '<='
# Restore a deleted variable or a modified's variable original value.
Set-Item Env:$($_.Name) $($_.Value)
}
}
}
# Check if the original environment was restored:
Get-Item Env:foo, Env:bar, Env:goaway
The last command in the code above prints the original (restored) values of $env:foo
and $env:bar
and - as expected - reports an error for the no-longer-extant $env:goaway
variable.
Note:
Get-ChildItem Env:
emits System.Collections.DictionaryEntry
instances, each representing an env. var. as a key(name)-value pair.
When Compare-Object
processes objects that do not implement the System.IComparable
interface, it falls back to comparing objects by their .ToString()
values.
In PowerShell (Core) 7, fortunately, System.Collections.DictionaryEntry
instances have meaningful string representations that reflect both the entry key (the env. var name) and value (the env. var. value); e.g.:
# -> e.g. (on Windows): '[OS, Windows_NT]'
(Get-Item Env:OS).ToString()
In Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1), such instances - unhelpfully - stringify to the full type name only, which the -Property { '[{0}, {1}]' -f $_.Key, $_.Value }
argument passed to Compare-Object
compensates for, which is an instance of a calculated property.
In other words:
-Property { '[{0}, {1}]' -f $_.Key, $_.Value }
argument.