3

Is it possible to reuse environment variables in the Path environment variable from PowerShell?

There are some environment variables like %SystemRoot% defined and are used in Path variable ...;%SystemRoot%\system32;... viewed in the Advanced System Settings.

In PowerShell these are defined as $Env:SystemRoot and $Env:Path part of that path is resolved with ...;c:\windows\system32;...

How to create and use such a custom variable in the Path? e.g. $Env:MyPath = 'c:\mypath' adding it to Path like ...;%MyPath%\documents;... and get the same effect in both Advanced System Settings and PowerShell $Env:Path resolution?

1 Answer 1

4

My god, it's full of variables

There is three types of environment variables:

  1. Machine

    The environment variable is stored or retrieved from the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment key in the Windows operating system registry. When a user creates the environment variable in a set operation, the operating system stores the environment variable in the system registry, but not in the current process. If any user on the local machine starts a new process, the operating system copies the environment variable from the registry to that process. When the process terminates, the operating system destroys the environment variable in that process. However, the environment variable in the registry persists until a user removes it programmatically or by means of an operating system tool.

  2. User

    The environment variable is stored or retrieved from the HKEY_CURRENT_USER\Environment key in the Windows operating system registry. When the user creates the environment variable in a set operation, the operating system stores the environment variable in the system registry, but not in the current process. If the user starts a new process, the operating system copies the environment variable from the registry to that process. When the process terminates, the operating system destroys the environment variable in that process. However, the environment variable in the registry persists until the user removes it programmatically or by means of an operating system tool.

  3. Process

    The environment variable is stored or retrieved from the environment block associated with the current process. The user creates the environment variable in a set operation. When the process terminates, the operating system destroys the environment variable in that process.

Newly created with $env: environment variables in PowerShell are of Process type. For variable to display in the Advanced System Settings it has be the Machine or User level variable. To create variable with specific level use Environment.SetEnvironmentVariable method:

[Environment]::SetEnvironmentVariable('MyPath', 'c:\mypath', 'User')

[Environment]::SetEnvironmentVariable('MyPath', 'c:\mypath', 'Machine')

Note, that setting Machine level environment variables requires elevation.

Do you read me, HAL?

So you want to embed one environment variable inside another and have it expanded? After all, Microsoft does it all the way, for example per user TEMP and TMP variables contain USERPROFILE env.var. Unfortunately, there is a number of quirks attached:

  1. The underlying registry entry for such variable has to be REG_EXPAND_SZ type

  2. The containee env. variable has to be alphabetically less than container env. variable:

    If the definition of an environment variable var1 contains another environment variable var2 and the name of var1 is alphabetically less than the name of var2 (i.e. strcmp(var1, var2) < 0), then var2 won't get expanded. This seems to be because when Windows first sets up the environment variables, they are created in alphabetical order, so var2 does not exist until after var1 has already been created (and so the expansion can't be done).

  3. For PATH variable, there should be no spaces between entries:

    Incorrect: c:\path1; c:\Maven\bin\; c:\path2\
    Correct: c:\path1;c:\Maven\bin\;c:\path2\

Furthermore, if you try to make use of Environment.SetEnvironmentVariable method like this:

$Path = [Environment]::GetEnvironmentVariable('Path', 'Machine')
    
[Environment]::SetEnvironmentVariable('MyPath', 'c:\mypath', 'Machine')
[Environment]::SetEnvironmentVariable('Path', "%MyPath%;$Path", 'Machine')

It will not yield to a desired result, because a newly created PATH variable will not have have type REG_EXPAND_SZ, but REG_SZ.

Open the pod bay doors, HAL

Given that SetEnvironmentVariable has no means to control resulting registry entry type, you have to employ alternative: directly modify registry to create an entry of the REG_EXPAND_SZ type.

$Path = [Environment]::GetEnvironmentVariable('Path','Machine')

[Environment]::SetEnvironmentVariable('MyPath', 'c:\mypath', 'Machine')

[Microsoft.Win32.Registry]::SetValue(
    'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
    'Path',
    "%MyPath%;$Path",
    [Microsoft.Win32.RegistryValueKind]::ExpandString
)

The disadvantages of this method is that it doesn't broadcast a WM_SETTINGCHANGE message to all windows in the system, so that any interested applications (such as Windows Explorer, Program Manager, Task Manager, Control Panel, and so forth) can perform an update.

To mitigate that, you can broadcast message yourself:

if (-not ('Win32.NativeMethods' -as [type])) {
    # import SendMessageTimeout from Win32
    Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @'

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessageTimeout(
            IntPtr hWnd,
            uint Msg,
            UIntPtr wParam,
            string lParam,
            uint fuFlags,
            uint uTimeout,
            out UIntPtr lpdwResult);
'@
}

$HWND_BROADCAST = [System.IntPtr]0xffff
$WM_SETTINGCHANGE = 0x1a
$result = [System.UIntPtr]::Zero

# Notify all windows of environment block change
[Win32.NativeMethods]::SendMessageTimeout(
    $HWND_BROADCAST, $WM_SETTINGCHANGE,
    [System.UIntPtr]::Zero,
    'Environment',
    2,
    5000,
    [ref]$result
)
3
  • thank you for extensive comment, is it possible to reuse one variable inside some part of one of the paths in $Env:Path? Mar 1, 2015 at 7:54
  • Yes, but some assembly required, See update. Mar 1, 2015 at 15:32
  • OMGoodness, TY. Feb 22 at 21:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .