Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Created December 23, 2016 15:44
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save CMCDragonkai/a02d77c2d7c0799dd42fd2aab26a3cd5 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/a02d77c2d7c0799dd42fd2aab26a3cd5 to your computer and use it in GitHub Desktop.
CLI: Adding paths to PATH environment variable in Windows
#!/usr/bin/env powershell
function Prepend-Idempotent {
# the delimiter is expected to be just 1 unique character
# otherwise there may be problems with trimming
param (
[string]$InputString,
[string]$OriginalString,
[string]$Delimiter = '',
[bool]$CaseSensitive = $false
)
if ($CaseSensitive -and ("$OriginalString" -cnotlike "*${InputString}*")) {
"$InputString".TrimEnd("$Delimiter") + "$Delimiter" + "$OriginalString".TrimStart("$Delimiter")
} elseif (! $CaseSensitive -and ("$OriginalString" -inotlike "*${InputString}*")) {
"$InputString".TrimEnd("$Delimiter") + "$Delimiter" + "$OriginalString".TrimStart("$Delimiter")
} else {
"$OriginalString"
}
}
function Append-Idempotent {
# the delimiter is expected to be just 1 unique character
# otherwise there may be problems with trimming
param (
[string]$InputString,
[string]$OriginalString,
[string]$Delimiter = '',
[bool]$CaseSensitive = $false
)
if ($CaseSensitive -and ("$OriginalString" -cnotlike "*${InputString}*")) {
"$OriginalString".TrimEnd("$Delimiter") + "$Delimiter" + "$InputString".TrimStart("$Delimiter")
} elseif (! $CaseSensitive -and ("$OriginalString" -inotlike "*${InputString}*")) {
"$OriginalString".TrimEnd("$Delimiter") + "$Delimiter" + "$InputString".TrimStart("$Delimiter")
} else {
"$OriginalString"
}
}
function Add-Path {
param (
[string]$NewPath,
[ValidateSet('Prepend','Append')]$Style = 'Prepend',
[ValidateSet('User', 'System')]$Target = 'User'
)
try {
# we need to do this to make sure not to expand the environment variables already inside the PATH
if ($Target -eq 'User') {
$Key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true)
} elseif ($Target -eq 'System') {
$Key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true)
}
$Path = $Key.GetValue('Path', $null, 'DoNotExpandEnvironmentNames')
# note that system path can only expand system environment variables and vice versa for user environment variables
# in order to make sure this method is idempotent, we need to check if the new path already exists, this requires having a semicolon at the very end
if ($Style -eq 'Prepend') {
$key.SetValue('Path', (Prepend-Idempotent ("$NewPath".TrimEnd(';') + ';') ("$Path".TrimEnd(';') + ';') ';' $false), 'ExpandString')
} elseif ($Style -eq 'Append') {
$key.SetValue('Path', (Append-Idempotent ("$NewPath".TrimEnd(';') + ';') ("$Path".TrimEnd(';') + ';') ';' $false), 'ExpandString')
}
# update the path for the current process as well
$Env:Path = $key.GetValue('Path', $null)
} finally {
$key.Dispose()
}
}
@chrisoldwood
Copy link

chrisoldwood commented Aug 6, 2020

Thanks for publishing this as most examples appear to ignore the fact that the tokens will be expanded and I wanted to leave them intact.

we need to check if the new path already exists, this requires having a semicolon at the very end

Can I just confirm though that the reason you're appending the ; to $NewPath is so that if $NewPath is a substring of one of the existing paths it won't accidentally match? For example if I wanted to add C:\, which is substring of most other paths in PATH, it should still be added because the -notlike "*${InputString}*" has to match the full C:\; and not simply C:\?

I was originally thinking of using a comparison like this as it feels more readable (if less performant):

if ( ($Path -split ';') -notcontains $NewPath )

but I'm wondering if there is an edge case that this misses which your approach solves?

(I've seen a few other examples which get this wrong and I wasn't sure about your version until I read it a little closer.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment