Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the value of the PATH environment variable without expanding tokens?

I'm writing a PowerShell script to convert folder names to short names in the PATH environment variable and save the changes. It works correctly for explicit paths, but it expands tokens, so when I save my changes the tokens are replaced with explicit paths. I'd like to preserve the tokens.

For example, if my PATH is this: %SystemRoot%;%SystemRoot%\system32;C:\Program Files\TortoiseSVN\bin;C:\ProgramData\chocolatey\bin

I want this result: %SystemRoot%;%SystemRoot%\system32;C:\PROGRA~1\TORTOI~1\bin;C:\PROGRA~3\CHOCOL~1\bin

But instead I get this: C:\Windows;C:\Windows\system32;C:\PROGRA~1\TORTOI~1\bin;C:\PROGRA~3\CHOCOL~1\bin

Here's the full script that illustrates the problem.

# get the current path.
# I ended up not using either of these approaches because they appear to
# be contextual; on my system, at least, they include paths that aren't
# seen when I view the PATH value from the System Properties -> Environment
# Variables dialog box.
$current_path = $env:Path
$current_path = [System.Environment]::GetEnvironmentVariable("Path")

# Instead, I get the PATH value directly from the registry, like so:
$current_path = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path

# The problem is that PowerShell expands the tokens even when reading from the 
# registry, so I can't detect the tokens and ignore them.  What I really want
# is just the literal string value, with no token evaluation.
# Here's a sample value; this is what I see in regedt32 and the system dialogs,
# but it's not what I get from the line above.
$current_path = '%SystemRoot%;%SystemRoot%\system32;C:\Program Files\TortoiseSVN\bin;C:\ProgramData\chocolatey\bin'

$new_path = ''

# the FileSystemObject has a method to convert the path using short names for the folders.
$fso = New-Object -ComObject Scripting.FileSystemObject

# Individual paths are delimited by a semicolon.  Skip paths with tokens, 
# and preserve trailing slashes in each path.
$current_path.Split(';') |
    ForEach-Object { 
        if ($_.StartsWith('%'))
            { $_ }
        elseif ($_.EndsWith('\'))
            { "$($fso.GetFolder($_).ShortPath)\" }
        else
            { "$($fso.GetFolder($_).ShortPath)" } 
    } |
    ForEach-Object { $new_path += "$_;" }

# remove the extra semicolon from the end the new PATH.
$new_path = $new_path.TrimEnd(";")

"Current PATH length: $($current_path.Length)"
"New PATH length: $($new_path.Length)"

$new_path

# commented out so you don't accidentally update your path if you try out this script
#[System.Environment]::SetEnvironmentVariable("Path", $new_path, "Machine")

It seems like it should be easy enough if I could just get the literal string value from the registry, but I so far have not been able to figure out how to do that.

like image 868
Matt Avatar asked Jul 21 '15 18:07

Matt


People also ask

How do I find the value of a PATH environment variable?

Select Start select Control Panel. double click System and select the Advanced tab. Click Environment Variables. In the section System Variables find the PATH environment variable and select it.

How do you show the PATH environment variable value in Linux?

In order to print out the PATH information using the printenv command, you need to type printenv and then the name of the environment variable that you would like to display.

How do I find the value of an environment variable in Windows?

On the Windows taskbar, right-click the Windows icon and select System. In the Settings window, under Related Settings, click Advanced system settings. On the Advanced tab, click Environment Variables.

What is the default value of PATH environment variable?

A modern equivalent of/replacement for %SystemRoot%. This directory is where Windows will install. The default directory path for most versions of Windows is c:\Windows (for Windows NT 4 and 2000, it is c:\WinNT).


2 Answers

This isn't PowerShell per se, but a "feature" of the underlying Windows registry. The Path variable is of type REG_EXPAND_SZ which automatically expands environment variables on retrieval. I don't think you can get around it with the built-in cmdlets, but you can with the .NET Microsoft.Win32.Registry APIs. Use the RegistryKey.GetValue overload with RegistryValueOptions.DoNotExpandEnvironmentNames:

$regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true)
$regKey.GetValue('Path', $null, "DoNotExpandEnvironmentNames")

when it is time to save the variable, use the overload of SetValue with RegistryValueKind.ExpandString to save it with the correct type:

$regKey.SetValue("Path", $new_path, "ExpandString")
like image 106
Mike Zboray Avatar answered Sep 28 '22 06:09

Mike Zboray


Without using the .NET [Microsoft.Win32.Registry] type: one-liners for user and system Path entries.

(Get-Item -path "HKCU:\Environment" ).GetValue('Path', '', 'DoNotExpandEnvironmentNames')


(Get-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" ).
  GetValue('Path', '', 'DoNotExpandEnvironmentNames')
like image 34
Christopher Plewright Avatar answered Sep 28 '22 07:09

Christopher Plewright