powershellcommand-lineenvironment-variablesbackupcommand-prompt

PowerShell script to take backup of all environment variables & save it in a file


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

Solution

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

    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 - extra work is needed to eliminate those: see this answer.


    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 in GitHub issue #5543.