Environment variables contain important pieces of information about the system. For example, the SystemRoot environment variable tells you the Windows OS installation path (e.g., C:\Windows) and the Path environment variable lists the directories that Windows will search for executable programs and DLL files. Unlike Windows PowerShell, the Cmd.exe command shell doesn't have its own variables—the only variables available are environment variables. In Cmd.exe, you access environment variables by using the %variablename% syntax. For example, Cmd.exe replaces %Path% with the content of the Path environment variable.
PowerShell also provides access to environment variables through the Env: drive. (PowerShell extends the concept of drive to store more kinds of data than just files.) For example, rather than using the Set command in Cmd.exe, you use the Get-ChildItem cmdlet to get a list of environment variables. For example, the command
returns all environment variables (i.e., returns the items in the Env: drive). You can also use the Get-Item cmdlet to retrieve a particular environment variable. For example, the command
outputs the name of the environment variable (e.g., Path) and its value. Like Cmd.exe, PowerShell supports string replacement of an environment variable name with its value, but PowerShell doesn't use the %variablename% syntax. PowerShell uses the syntax $Env:variablename instead (e.g., $Env:Path).
Environment variables are separate from PowerShell variables. PowerShell variables are subject to PowerShell scopes, but environment variables aren't. Scope refers to where a variable is visible in PowerShell. (For more information about scopes, see the about_Scopes PowerShell Help topic.) For example, when you run a PowerShell script or function, PowerShell variables created within the script or function disappear when the script or function finishes running. Figure 1 shows an example. The $myName variable doesn't exist outside of the script. The Test1.ps1 script defines the variable and uses it, but the variable disappears, or "falls out of scope," after the script completes.
Figure 2 illustrates how environment variables aren't subject to PowerShell scopes. Another way to say this is that environment variables have a global scope. The environment variable TEST doesn't exist, gets set inside a script, and is still available after the script completes.
What's the Problem?
These demonstrations reveal two different Cmd.exe behaviors, and a problem associated with each behavior, that you need to keep in mind when using environment variables in PowerShell.
Cmd.exe behavior 1. Environment variables in a Cmd.exe shell script (batch file) don't have a scope by default. This means that all changes a shell script makes to environment variables, no matter whether it adds a new environment variable or changes an existing one, persist after the script completes. This behavior is by design, because many shell scripts rely on it. For example, Microsoft Visual Studio 2013 uses a Cmd.exe shell script named vcvarsall.bat to set a number of environment variables that are required for the command-line C/C++ compiler.
Problem 1. PowerShell can't run a Cmd.exe shell script that wants to set or modify environment variables. PowerShell can run a Cmd.exe shell script, but any environment variable changes made by the script are lost when the script completes. This happens because environment variable changes don't propagate from the child process (i.e., the Cmd.exe instance that runs the shell script) to the parent process (PowerShell).
Cmd.exe behavior 2. You can create a scope for environment variables in a Cmd.exe shell script by using the Setlocal command. This means that when a shell script uses the Setlocal command, any changes it makes to environment variables are reversed when the script completes. For example, variables added by the script are removed and variables changed by the script are reverted to their previous values.
Problem 2. PowerShell doesn't have a Setlocal command equivalent for creating a scope for environment variables. All environment variables have a global scope in PowerShell.
Solving Both Problems
I wrote a set of three PowerShell functions that overcome both these problems. The Invoke-CmdScript function overcomes problem 1, and the Get-Environment and Set-Environment functions overcome problem 2. All three functions are defined in the Environment.ps1 script shown in Listing 1. (You can download this script by clicking the Download the Code button near the top of the page.) Let's take a look the functions.
Solving problem 1 with the Invoke-CmdScript function. The Invoke-CmdScript function runs a Cmd.exe shell script and captures the environment variables the script sets or changes. It does this by running Cmd.exe to execute the shell script followed by the Set command, which outputs a list of all environment variables and values. The function parses the shell script's output using a regular expression and uses the Set-Item cmdlet to set each environment variable in PowerShell. Table 1 describes the patterns in the regular expression used by the function to parse the shell script's output.
|^||Beginning of line.|
|([^=]*)||Not a = character, zero or more times. Environment variable names can contain any character except the equals sign. The parentheses group the environment variable's name.|
|=||The = character. The environment variable's name and value are always separated by the = character.|
|(.*)||Any character, zero or more times. The parentheses group the environment variable's value. I use .* rather than [^=] because an environment variable's value can contain the = character.|
|$||End of line.|
Solving problem 2 with the Get-Environment and Restore-Environment functions. The Get-Environment function outputs the current environment as a collection of DictionaryEntry objects. You could replace this function with the single command it runs (Get-ChildItem Env:), but I wrote it as a function to make its intent clear and to provide an inverse operation to the Restore-Environment function.
The Restore-Environment function requires a parameter that contains a collection of DictionaryEntry objects (i.e., the list of environment variables returned by the Get-Environment function) to compare with the current list of environment variables. The function uses the Compare-Object cmdlet twice: Once to determine if any environment variables have been added (so it can use the Remove-Item cmdlet to remove them) and once to determine if any environment variables have been changed (so it can restore the previous values).
Seeing the Functions in Action
Figure 3 contains a PowerShell session that shows the functions in action.
First, the session "dot-sources" the Environment.ps1 script so that the three functions are available. Adding a dot at the beginning of a command, like in the first command in Figure 3, is called dot-sourcing the command. Dot-sourcing runs a script in the current scope instead of creating a new scope.
Next, the session sets the ENV1 environment variable to contain the string Original value. After this, it saves the current environment using the Get-Environment function. Then, it updates the ENV1 variable to the string Modified value.
At this point, the session uses the Get-Content cmdlet to show the content of a Cmd.exe shell script that sets the ENV2 environment variable. It then uses the Invoke-CmdScript function to run that script.
Finally, the session uses the Restore-Environment function to restore the environment to its previous state. The last two commands in the session show that the Restore-Environment removed the ENV2 environment variable and restored the ENV1 environment variable to its previous value.
Figure 4 shows another PowerShell session that uses the functions to save, set, and restore the environment variables set by the vcvarsall.bat shell script included with Visual Studio 2013.
This example shows how the Get-Environment and Save-Environment functions provide scope for environment variables and how the Invoke-CmdScript function can run a Cmd.exe shell script to update environment variables.
Give PowerShell Power Over Environment Variables
PowerShell doesn't have a built-in way to run a Cmd.exe shell script (batch file) and retain the environment variables it sets or changes. In addition, there's no Setlocal command equivalent in PowerShell to revert environment variable changes. The Invoke-CmdScript, Get-Environment, and Save-Environment functions remove these limitations and give PowerShell more power over environment variables.