I am new to PowerShell and recently started learning about some of its commands.
Now I am looking to try adding, editing and deleting any path entry from the environment Path variable (System).
But I am concerned about the case if anything goes wrong, so I want to take backup of all environment variables in case I need to restore.
I have the following cmdlet to display specific variable onto the screen, but can anyone let me know how to take backup of all environment variables (both system and user) to a text file through a .ps1
PowerShell script?
Get-ChildItem Env:Path
Note: PowerShell's env:
drive only reflects the environment variables of the current process - to persistently change the system's or the current user's environment variables, you must use the .NET framework directly - see this answer of mine.
The following snippet, which uses env:
, therefore only shows how to save and restore all environment variables of the current process:
# Save all the process' environment variables in CLIXML format.
Get-ChildItem env: | Export-CliXml ./env-vars.clixml
# ... modify the env. variables
# Restore the previously saved env. variables.
Import-CliXml ./env-vars.clixml | % { Set-Item "env:$($_.Name)" $_.Value }
Note that this will not remove any new env. variables you may have created in the meantime, however - extra work would be needed to eliminate those.
Note: Export-CliXml
and Import-CliXml
are being used, because they enable robust round-tripping (serialization / deserialization) of values (although you cannot generally recreate objects of the very same type, only deserialized look-alikes).
By contrast, using something like Get-ChildItem env: > ./env-vars.txt
or its equivalent, Get-ChildItem env: | Out-File ./env-vars.txt
simply saves the Get-ChildItem
output as plain text the way it displays in the console, which is not a format suitable for machine parsing (deserialization).
Optional reading: why Import-CliXml ./env-vars.clixml | Set-Item
should work, but doesn't:
Set-Item
is designed to bind parameter values via the pipeline, notably -LiteralPath
(alias -PSPath
) and -Value
.
Therefore, if Set-Item
receives objects through the pipeline that have properties by those names (.PSPath
, .Value
), they should automatically bind to the -PSPath
(-LiteralPath
) and -Value
parameters, enabling invocation such as ... | Set-Item
- so there should be no need for explicit enumeration and explicit parameter-passing via %
(ForEach-Object
), as used above.
Unfortunately, however, as of Windows PowerShell v5.1 / PowerShell Core v6.0.1, the -Value
parameter is defined in a way that also binds the input object as a whole to -Value
(ValueFromPipeline
), which - given that -Value
is defined generically as type [System.Object]
- invariably takes precedence over looking for the object's .Value
property (ValueFromPipelineByPropertyName
) - in other words: no input object, irrespective of its type, is ever bound by its .Value
property - any input object is bound as itself.
In the case at hand, the objects output by Import-CliXml
are of type [Deserialized.System.Collections.DictionaryEntry]
; they are bound to -Value
, and then converted to a string when the environment variable is set (environment variables can only ever contain string values); the string representation of that type is System.Collections.DictionaryEntry
- irrespective of its .Value
property - and that generic string is therefore - uselessly - set as the environment variable's value.
This problematic behavior has been reported on GitHub.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With