systempath.ps1
|
class SystemPathLocation { [string] $Scope [ValidateNotNullOrEmpty()] [string] $Location SystemPathLocation($Scope, $Location) { $this.Scope = $Scope $this.Location = $Location } <# .SYNOPSIS A folder location on the system path and the scope it belongs to. .DESCRIPTION Holds a folder location on the system path together with its scope: 'Machine' (local machine), 'User' (current user) or 'Process' (local to the current shell). .EXAMPLE $location = [SystemPathLocation]::new("Machine", "C:\Program Files\Git\bin") #> } function Backup-SystemPath { <# .SYNOPSIS Backs up the system path to a file in the temp folder. .DESCRIPTION Backs up the system path to a file in the temp folder. .EXAMPLE Backup-SystemPath #> [CmdletBinding(SupportsShouldProcess)] param () $backupFile = "$env:TEMP\PATH-$(Get-Timestamp).txt" if ($PSCmdlet.ShouldProcess($backupFile, "Backup system path")) { $env:PATH > $backupFile } } function local:Add-PathLocation { <# .SYNOPSIS Adds a location to a semicolon-separated path. .DESCRIPTION Permanently adds the specified location to the specified semicolon-separated path and returns the path. Adding is idempotent: if the path already contains the location and -First is not specified, the path is returned unchanged. If the path already contains the location and -First is specified, the existing location is moved to the beginning of the path. .PARAMETER Path Semiocolon separated path to add the location to. .PARAMETER Location Folder location to add to the path. .PARAMETER First If specified, the location is added to the beginning of the path. Otherwise, it is added to the end. If the location already exists, -First moves it to the beginning. .OUTPUTS Modified path. .EXAMPLE Add-PathLocation -Path "C:\Windows;C:\Windows\System32" -Location "C:\Program Files\Git\bin" -First .EXAMPLE Add-PathLocation -Path "C:\Windows;C:\Windows\System32" -Location "C:\Program Files\Git\bin" #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $true)] [Alias("Folder")] [string] $Location, [Parameter(Mandatory = $true)] [bool] $First ) $oldLocations = $Path -split ";" $alreadyPresent = $oldLocations | Where-Object { $_.TrimEnd("\") -ieq $Location.TrimEnd("\") } if ($alreadyPresent) { if (-not $First) { # idempotent: the location is already present, leave the path unchanged return $Path } # move the existing location to the front $remaining = Remove-PathLocation -Path $Path -Location $Location return $remaining ? "$Location;$remaining" : $Location } $pathWithoutSemicolon = $Path.TrimEnd(";") return $First ? "$Location;$pathWithoutSemicolon" : "$pathWithoutSemicolon;$Location" } function local:Remove-PathLocation { <# .SYNOPSIS Removes a location from a semicolon-separated path and returns the path. .DESCRIPTION Removes each occurence of location from the specified semicolon-separated path. Removing is idempotent: if the path does not contain the location, the path is returned unchanged. .PARAMETER Path Semiocolon separated path to remove the location from. .PARAMETER Location Folder location to remove from the path. Trailing backslashes on the location argument and within the path are ignored. .OUTPUTS The path with the location removed. .EXAMPLE $newPath = Remove-PathLocation -Path "C:\Windows;C:\Program Files\Git\bin\" -Location "C:\Program Files\Git\bin" # -> "C:\Windows" # Note the missing trailing backslash .EXAMPLE $newPath = Remove-PathLocation -Path "C:\Windows;C:\Program Files\Git\bin\" -Location "C:\Program Files\Git\bin" # -> "C:\Windows" .EXAMPLE $newPath = Remove-PathLocation -Path "C:\Windows;C:\Windows\System32" -Location "C:\Program Files\Git\bin" # -> "C:\Windows;C:\Windows\System32" #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $true)] [Alias("Folder")] [string] $Location ) $newPath = $Path -split ";" ` | Where-Object { $_.TrimEnd("\") -ine $Location.TrimEnd("\") } ` | Join-String -Separator ";" return $newPath } function local:Remove-DuplicatePathLocation { <# .SYNOPSIS Removes duplicate locations from a semicolon-separated path. .DESCRIPTION Returns the path with duplicate locations removed, keeping the first occurrence of each location. Comparison is case-insensitive and ignores trailing backslashes. Empty locations are dropped. .PARAMETER Path Semicolon-separated path to deduplicate. .OUTPUTS The path with duplicate locations removed. .EXAMPLE Remove-DuplicatePathLocation -Path "C:\A;C:\B;C:\a\" # -> "C:\A;C:\B" #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowEmptyString()] [string] $Path ) $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $unique = $Path -split ";" ` | Where-Object { $_ -and $seen.Add($_.TrimEnd("\")) } return $unique -join ";" } function local:Get-PathScopeCounts { <# .SYNOPSIS Returns how often each location occurs on a persisted scope path. .DESCRIPTION Reads the Path environment variable for the given scope and returns a case-insensitive dictionary mapping each trailing-backslash-trimmed location key to the number of times it occurs. Used to tag the effective path's locations by consuming these counts in order. .PARAMETER Scope The scope to read, either "Machine" or "User". .OUTPUTS A case-insensitive hashtable of location key to occurrence count. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateSet("Machine", "User")] [string] $Scope ) # a PowerShell hashtable literal is case-insensitive and yields $null (not an error) for absent keys $counts = @{} $context = @{ $Scope = $true } (Get-EnvironmentVariable @context -Name Path -ErrorAction SilentlyContinue) -split ";" ` | Where-Object { $_ } ` | ForEach-Object { $key = $_.TrimEnd("\") $counts[$key] = [int] $counts[$key] + 1 } return $counts } function local:Test-LocationCriteria { <# .SYNOPSIS Tests a location against the exact, substring, wildcard and regex criteria. .DESCRIPTION Returns $true when the location satisfies every given criterion. Criteria of different kinds, and multiple values of the same kind, are combined with AND. An absent criterion is not applied; when no criterion is given at all, every location satisfies them. Matching is case-insensitive throughout. Trailing backslashes are ignored on the location and on the -Exact, -Contains and -Filter criteria; the -Match patterns are applied as given, since a trailing backslash is meaningful in a regular expression. .PARAMETER Location The location to test. .PARAMETER Exact Location the tested location must equal. .PARAMETER Contains Substrings the location must contain. Taken literally: wildcard and regex characters carry no meaning. .PARAMETER Filter Wildcard patterns the location must match. .PARAMETER Match Regular expressions the location must match. .OUTPUTS Boolean indicating whether the location satisfies every given criterion. .EXAMPLE Test-LocationCriteria -Location "C:\Program Files\Git\bin" -Contains "Git" .EXAMPLE Test-LocationCriteria -Location "C:\Program Files\Git\bin" -Contains "Git", "bin" -Match "\\bin$" #> [CmdletBinding()] [OutputType([bool])] param ( [Parameter(Mandatory = $true)] [string] $Location, [Parameter(Mandatory = $false)] [string] $Exact, [Parameter(Mandatory = $false)] [string[]] $Contains, [Parameter(Mandatory = $false)] [string[]] $Filter, [Parameter(Mandatory = $false)] [string[]] $Match ) $trimmedLocation = $Location.TrimEnd("\") if ($Exact -and $trimmedLocation -ine $Exact.TrimEnd("\")) { return $false } foreach ($substring in $Contains) { if (-not $trimmedLocation.Contains($substring.TrimEnd("\"), [System.StringComparison]::OrdinalIgnoreCase)) { return $false } } foreach ($pattern in $Filter) { if ($trimmedLocation -inotlike $pattern.TrimEnd("\")) { return $false } } foreach ($pattern in $Match) { if ($trimmedLocation -inotmatch $pattern) { return $false } } return $true } function Get-SystemPath { <# .SYNOPSIS Retrieves the system path. .DESCRIPTION Retrieves the system path, either for the current user, for the local machine or the system path in effect in the current context. The path is returned as an array of SystemPathLocation objects by default, each carrying its Location and Scope. For the effective path (the default) each location is tagged with its origin scope: 'Machine' or 'User' when the location is on the corresponding persisted path, or 'Process' when it is only on the current shell's path. For -Machine or -User every location carries that scope. If the -Join switch is specified, the path is returned as a semicolon-separated string of locations instead. The -Contains, -Filter and -Match criteria select locations. Multiple criteria, of the same kind or of different kinds, must all be satisfied. Without any criterion, every location is returned. .PARAMETER Machine If specified, the system path for the local machine is returned. .PARAMETER User If specified, the system path for the current user is returned. .PARAMETER Effective Default; if specified, the effective system path is returned. The effective system path is the path in effect in the current shell. .PARAMETER Join If specified, the system path is returned as a semicolon-separated string. Otherwise, it is returned as an array of SystemPathLocation objects. .PARAMETER Contains Substrings, positional; only locations containing all of them are returned. Taken literally: wildcard and regex characters carry no meaning. Matching is case-insensitive and ignores trailing backslashes. .PARAMETER Filter Wildcard patterns; only locations matching all of them are returned. Matching is case-insensitive and ignores trailing backslashes. .PARAMETER Match Regular expressions; only locations matching all of them are returned. Matching is case-insensitive. .OUTPUTS SystemPathLocation objects with a Location and a Scope property, or a semicolon-separated string when -Join is specified. .NOTES Alias: path .EXAMPLE Get-SystemPath .EXAMPLE Get-SystemPath -Machine .EXAMPLE Get-SystemPath -User -Join .EXAMPLE Get-SystemPath Git .EXAMPLE Get-SystemPath Git bin .EXAMPLE Get-SystemPath -Filter "*\Git\*" .EXAMPLE Get-SystemPath -Match "\\Git\\(cmd|bin)$" .EXAMPLE Get-SystemPath Git -Filter "*\bin" -Machine #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = "Machine")] [switch] $Machine, [Parameter(Mandatory = $true, ParameterSetName = "User")] [switch] $User, [Parameter(Mandatory = $false, ParameterSetName = "Effective")] [switch] $Effective, [Parameter(Mandatory = $false)] [switch] $Join, [Parameter(Mandatory = $false, Position = 0, ValueFromRemainingArguments = $true)] [string[]] $Contains, [Parameter(Mandatory = $false)] [string[]] $Filter, [Parameter(Mandatory = $false)] [string[]] $Match ) $allLocations = if ($Machine) { (Get-EnvironmentVariable -Machine -Name Path) -split ";" ` | Where-Object { $_ } ` | ForEach-Object { [SystemPathLocation]::new("Machine", $_) } } elseif ($User) { (Get-EnvironmentVariable -User -Name Path) -split ";" ` | Where-Object { $_ } ` | ForEach-Object { [SystemPathLocation]::new("User", $_) } } else { # effective: the live shell path, each location tagged with the persisted scope it originates from. # The process path lists machine locations before user locations, so consume the machine occurrences # first, then user; a location on both scopes therefore appears once as Machine and once as User. $machineRemaining = Get-PathScopeCounts -Scope Machine $userRemaining = Get-PathScopeCounts -Scope User $env:PATH -split ";" ` | Where-Object { $_ } ` | ForEach-Object { $key = $_.TrimEnd("\") $scope = if ([int] $machineRemaining[$key] -gt 0) { $machineRemaining[$key]--; "Machine" } elseif ([int] $userRemaining[$key] -gt 0) { $userRemaining[$key]--; "User" } else { "Process" } [SystemPathLocation]::new($scope, $_) } } $criteria = @{ Contains = $Contains Filter = $Filter Match = $Match } $selectedLocations = $allLocations | Where-Object { Test-LocationCriteria -Location $_.Location @criteria } return $Join ` ? (($selectedLocations | ForEach-Object { $_.Location }) -join ";") ` : $selectedLocations } New-Alias -Name path -Value Get-SystemPath -ErrorAction SilentlyContinue | Out-Null function local:Set-SystemPath { <# .SYNOPSIS Modifies the system path. .DESCRIPTION Sets the system path to the specified path, either for the current user or for the local machine. .PARAMETER Path Semiocolon separated path to set. .PARAMETER Machine If specified, the system path for the local machine is used. .PARAMETER User If specified, the system path for the current user is used. .EXAMPLE Set-SystemPath -Path "C:\Windows;C:\Windows\System32" .EXAMPLE Set-SystemPath -Path "C:\Windows;C:\Windows\System32" -Machine .EXAMPLE Set-SystemPath -Path "C:\Windows;C:\Windows\System32" -User #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $true, ParameterSetName = "Machine")] [switch] $Machine, [Parameter(Mandatory = $true, ParameterSetName = "User")] [switch] $User ) if ($Machine) { Assert-Administrator } Backup-SystemPath $context = $Machine ? @{ Machine = $true } : @{ User = $true } $params = @{ Name = "Path" Value = $Path } Set-EnvironmentVariable @context @params } function Add-SystemPathLocation { <# .SYNOPSIS Adds a location to the system path. .DESCRIPTION Adds the specified location to the system path, either for the current user or for the local machine. Adding is idempotent: if the location is already present, the path is left unchanged and a warning is reported. If the location is already present and -First is specified, it is moved to the beginning of the path. .PARAMETER Location Folder location to add to the system path. .PARAMETER Machine If specified, the system path for the local machine is used. .PARAMETER User If specified, the system path for the current user is used. (Default.) .PARAMETER First If specified, the location is added to the beginning of the path. Otherwise, it is added to the end. If the location is already present, -First moves it to the beginning. Alias: Front. .NOTES Alias: addpath Default scope is User. .EXAMPLE Add-SystemPathLocation -Location "C:\Program Files\Git\bin" .EXAMPLE Add-SystemPathLocation -Location "C:\Program Files\Git\bin" -Machine .EXAMPLE Add-SystemPathLocation -Location "C:\Program Files\Git\bin" -User .EXAMPLE Add-SystemPathLocation -Location "C:\Program Files\Git\bin" -First #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Position = 0, Mandatory = $true)] [Alias("Folder")] [string] $Location, [Parameter(Mandatory = $false)] [Alias("Front")] [switch] $First, [Parameter(Mandatory = $true, ParameterSetName = "Machine")] [switch] $Machine, [Parameter(Mandatory = $false, ParameterSetName = "User")] [switch] $User ) $context = $Machine ? @{ Machine = $true } : @{ User = $true } $currentPath = Get-SystemPath @context -Join $newPath = Add-PathLocation -Path $currentPath -Location $Location -First:$First # idempotent: nothing changed means the location is already present if ($newPath -eq $currentPath) { Write-Warning "Location is already on the system path: '$Location'" return } if ($PSCmdlet.ShouldProcess($Location, "Add location to system path")) { Set-SystemPath @context -Path $newPath # enable new location immediately $env:PATH = Add-PathLocation -Path "$env:PATH" -Location $Location -First:$First } } function Remove-SystemPathLocation { <# .SYNOPSIS Removes a location from the system path. .DESCRIPTION Removes the specified location from the system path, either for the current user or for the local machine. Removing is idempotent: if the location is not present, the path is left unchanged and a warning is reported. .PARAMETER Location Folder location to remove from the system path. .PARAMETER Machine If specified, the system path for the local machine is used. .PARAMETER User If specified, the system path for the current user is used. (Default.) .NOTES Alias: rmpath, removepath Default scope is User. .EXAMPLE Remove-SystemPathLocation -Location "C:\Program Files\Git\bin" .EXAMPLE Remove-SystemPathLocation -Location "C:\Program Files\Git\bin" -Machine .EXAMPLE Remove-SystemPathLocation -Location "C:\Program Files\Git\bin" -User #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Position = 0, Mandatory = $true)] [Alias("Folder")] [string] $Location, [Parameter(Mandatory = $true, ParameterSetName = "Machine")] [switch] $Machine, [Parameter(Mandatory = $false, ParameterSetName = "User")] [switch] $User ) $context = $Machine ? @{ Machine = $true } : @{ User = $true } $currentPath = Get-SystemPath @context -Join $newPath = Remove-PathLocation -Path $currentPath -Location $Location # idempotent: nothing changed means the location is not present if ($newPath -eq $currentPath) { Write-Warning "Location is not on the system path: '$Location'" return } if ($PSCmdlet.ShouldProcess($Location, "Remove location from system path")) { Set-SystemPath @context -Path $newPath # disable location immediately # TODO: remove only if not present in the other context $env:PATH = Remove-PathLocation -Path "$env:PATH" -Location $Location } } function Remove-DuplicateSystemPathLocations { <# .SYNOPSIS Removes duplicate locations from the system path. .DESCRIPTION Removes duplicate locations from the system path, for the local machine, for the current user, or both combined. Within a scope, only the first occurrence of each location is kept. When both scopes are cleaned (the default, when neither -Machine nor -User is specified), a location present on both scopes is kept on only one: the machine path by default, or the user path if -KeepUser is specified. Removing duplicates is idempotent: if there are no duplicates, the path is left unchanged. .PARAMETER Machine If specified, only the local machine system path is cleaned. .PARAMETER User If specified, only the current user system path is cleaned. .PARAMETER KeepMachine Default. When cleaning both scopes, a location present on both is kept on the machine path and removed from the user path. .PARAMETER KeepUser When cleaning both scopes, a location present on both is kept on the user path and removed from the machine path. .NOTES Alias: deduppath .EXAMPLE Remove-DuplicateSystemPathLocations .EXAMPLE Remove-DuplicateSystemPathLocations -Machine .EXAMPLE Remove-DuplicateSystemPathLocations -KeepUser #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $false)] [switch] $Machine, [Parameter(Mandatory = $false)] [switch] $User, [Parameter(Mandatory = $false)] [switch] $KeepMachine, [Parameter(Mandatory = $false)] [switch] $KeepUser ) if ($KeepMachine -and $KeepUser) { Write-Error "Specify only one of -KeepMachine and -KeepUser." -ErrorAction Stop } $changed = $false # clean both scopes when neither (or both) scope switches are given if ($Machine -eq $User) { $machinePath = Get-SystemPath -Machine -Join $userPath = Get-SystemPath -User -Join $machineDeduped = Remove-DuplicatePathLocation -Path $machinePath $userDeduped = Remove-DuplicatePathLocation -Path $userPath # cross-scope: drop from the non-kept scope every location present in the kept scope if ($KeepUser) { foreach ($location in ($userDeduped -split ";")) { if ($location) { $machineDeduped = Remove-PathLocation -Path $machineDeduped -Location $location } } } else { foreach ($location in ($machineDeduped -split ";")) { if ($location) { $userDeduped = Remove-PathLocation -Path $userDeduped -Location $location } } } if ($machineDeduped -ne $machinePath -and $PSCmdlet.ShouldProcess("machine", "Remove duplicate locations from system path")) { Set-SystemPath -Machine -Path $machineDeduped $changed = $true } if ($userDeduped -ne $userPath -and $PSCmdlet.ShouldProcess("user", "Remove duplicate locations from system path")) { Set-SystemPath -User -Path $userDeduped $changed = $true } } else { $context = $Machine ? @{ Machine = $true } : @{ User = $true } $scope = $Machine ? "machine" : "user" $currentPath = Get-SystemPath @context -Join $deduped = Remove-DuplicatePathLocation -Path $currentPath if ($deduped -ne $currentPath -and $PSCmdlet.ShouldProcess($scope, "Remove duplicate locations from system path")) { Set-SystemPath @context -Path $deduped $changed = $true } } # keep the current process path free of duplicates too if ($changed) { $env:PATH = Remove-DuplicatePathLocation -Path "$env:PATH" } } function Move-SystemPathLocation { <# .SYNOPSIS Moves a location between the machine and user system paths. .DESCRIPTION Moves the specified location from the machine system path to the user system path (-ToUser), or from the user system path to the machine system path (-ToMachine). The location is removed from the source path and added to the target path. If the location is not on the source path - whether it is already on the target path or on neither - nothing is moved and a warning is reported. Moving to or from the machine path requires administrator privileges. .PARAMETER Location Folder location to move, positional. .PARAMETER ToUser Move the location from the machine system path to the user system path. .PARAMETER ToMachine Move the location from the user system path to the machine system path. .NOTES Alias: movepath .EXAMPLE Move-SystemPathLocation "C:\Program Files\Git\bin" -ToUser .EXAMPLE Move-SystemPathLocation "C:\Program Files\Git\bin" -ToMachine #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Position = 0, Mandatory = $true)] [Alias("Folder")] [string] $Location, [Parameter(Mandatory = $true, ParameterSetName = "ToUser")] [switch] $ToUser, [Parameter(Mandatory = $true, ParameterSetName = "ToMachine")] [switch] $ToMachine ) if ($ToUser) { $source = @{ Machine = $true }; $sourceName = "machine" $target = @{ User = $true }; $targetName = "user" } else { $source = @{ User = $true }; $sourceName = "user" $target = @{ Machine = $true }; $targetName = "machine" } $sourcePath = Get-SystemPath @source -Join $newSource = Remove-PathLocation -Path $sourcePath -Location $Location # not on the source path: nothing to move if ($newSource -eq $sourcePath) { $onTarget = (Get-SystemPath @target -Join) -split ";" ` | Where-Object { $_ -and $_.TrimEnd("\") -ieq $Location.TrimEnd("\") } $reason = $onTarget ? "it is already on the $targetName path" : "it is not on the $sourceName path" Write-Warning "Nothing to move; $($reason): '$Location'" return } $targetPath = Get-SystemPath @target -Join $newTarget = Add-PathLocation -Path $targetPath -Location $Location -First:$false if (-not $PSCmdlet.ShouldProcess($Location, "Move location from the $sourceName to the $targetName system path")) { return } Set-SystemPath @source -Path $newSource if ($newTarget -ne $targetPath) { Set-SystemPath @target -Path $newTarget } } function Get-SystemPathLocation { <# .SYNOPSIS Finds a location on the system path. .DESCRIPTION Returns the locations on the system path that satisfy the given criteria, either for the current user, for the local machine or the system path in effect in the current context. Each result carries the matched location and the scope it was found in: 'Machine' or 'User', or - for the effective path - 'Process' when the location is only on the current shell's path. Multiple criteria, of the same kind or of different kinds, must all be satisfied. At least one of -Location, -Contains, -Filter and -Match is required. Matching is case-insensitive and ignores trailing backslashes. Nothing is returned when no location matches. .PARAMETER Location Exact folder location to look for. .PARAMETER Contains Substrings, positional; only locations containing all of them are returned. Taken literally: wildcard and regex characters carry no meaning. .PARAMETER Filter Wildcard patterns; only locations matching all of them are returned. .PARAMETER Match Regular expressions; only locations matching all of them are returned. .PARAMETER Machine If specified, the system path for the local machine is searched. .PARAMETER User If specified, the system path for the current user is searched. .OUTPUTS For each match, an object with a Location and a Scope property. .EXAMPLE Get-SystemPathLocation Git .EXAMPLE Get-SystemPathLocation -Location "C:\Program Files\Git\bin" .EXAMPLE Get-SystemPathLocation -Filter "*\Git\*" -Machine .EXAMPLE Get-SystemPathLocation Git -Match "\\(cmd|bin)$" #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [Alias("Folder")] [string] $Location, [Parameter(Mandatory = $false, Position = 0, ValueFromRemainingArguments = $true)] [string[]] $Contains, [Parameter(Mandatory = $false)] [string[]] $Filter, [Parameter(Mandatory = $false)] [string[]] $Match, [Parameter(Mandatory = $false)] [switch] $Machine, [Parameter(Mandatory = $false)] [switch] $User ) if ($Machine -and $User) { Write-Error "Specify only one of -Machine and -User." -ErrorAction Stop } if (-not $Location -and -not $Contains -and -not $Filter -and -not $Match) { Write-Error "Specify at least one of -Location, -Contains, -Filter and -Match." -ErrorAction Stop } $context = ` if ($Machine) { @{ Machine = $true } } ` elseif ($User) { @{ User = $true } } ` else { @{} } $criteria = @{ Exact = $Location Contains = $Contains Filter = $Filter Match = $Match } # Get-SystemPath already tags each location with its scope, so reuse that instead of recomputing it here Get-SystemPath @context | Where-Object { Test-LocationCriteria -Location $_.Location @criteria } } function Test-SystemPathLocation { <# .SYNOPSIS Tests whether a location is on the system path. .DESCRIPTION Returns $true if a location satisfying the given criteria is present on the system path, either for the current user, for the local machine or the system path in effect in the current context. Multiple criteria, of the same kind or of different kinds, must all be satisfied. At least one of -Location, -Contains, -Filter and -Match is required. Matching is case-insensitive and ignores trailing backslashes. .PARAMETER Location Exact folder location to look for. .PARAMETER Contains Substrings, positional; the location must contain all of them. Taken literally: wildcard and regex characters carry no meaning. .PARAMETER Filter Wildcard patterns the location must match. .PARAMETER Match Regular expressions the location must match. .PARAMETER Machine If specified, the system path for the local machine is searched. .PARAMETER User If specified, the system path for the current user is searched. .OUTPUTS Boolean indicating whether a matching location is present. .EXAMPLE Test-SystemPathLocation Git .EXAMPLE Test-SystemPathLocation -Location "C:\Program Files\Git\bin" .EXAMPLE Test-SystemPathLocation -Filter "*\Git\*" -User #> [CmdletBinding()] [OutputType([bool])] param ( [Parameter(Mandatory = $false)] [Alias("Folder")] [string] $Location, [Parameter(Mandatory = $false, Position = 0, ValueFromRemainingArguments = $true)] [string[]] $Contains, [Parameter(Mandatory = $false)] [string[]] $Filter, [Parameter(Mandatory = $false)] [string[]] $Match, [Parameter(Mandatory = $false)] [switch] $Machine, [Parameter(Mandatory = $false)] [switch] $User ) return @(Get-SystemPathLocation @PSBoundParameters).Count -gt 0 } New-Alias -Name addpath -Value Add-SystemPathLocation -ErrorAction SilentlyContinue | Out-Null New-Alias -Name rmpath -Value Remove-SystemPathLocation -ErrorAction SilentlyContinue | Out-Null New-Alias -Name removepath -Value Remove-SystemPathLocation -ErrorAction SilentlyContinue | Out-Null New-Alias -Name deduppath -Value Remove-DuplicateSystemPathLocations -ErrorAction SilentlyContinue | Out-Null New-Alias -Name movepath -Value Move-SystemPathLocation -ErrorAction SilentlyContinue | Out-Null |