Carbon.Environment.psm1
|
# Copyright WebMD Health Services # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License #Requires -Version 5.1 Set-StrictMode -Version 'Latest' # Functions should use $script:moduleDirPath as the relative root from which to find things. A published module has its # function appended to this file, while a module in development has its functions in the Functions directory. $script:moduleDirPath = $PSScriptRoot if (-not (Test-Path -Path 'variable:IsWindows')) { $script:IsWindows = $true $script:IsLinux = $script:IsMacOS = $false } # Store each of your module's functions in its own file in the Functions directory. On the build server, your module's # functions will be appended to this file, so only dot-source files that exist on the file system. This allows # developers to work on a module without having to build it first. Grab all the functions that are in their own files. $functionsPath = Join-Path -Path $script:moduleDirPath -ChildPath 'Functions\*.ps1' if( (Test-Path -Path $functionsPath) ) { foreach( $functionPath in (Get-Item $functionsPath) ) { . $functionPath.FullName } } function Assert-Scope { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] [Object] $Scope, [String] $Message ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $writeError = $false $receivedCount = 0 $seenScopes = [Collections.Generic.Hashset[EnvironmentVariableTarget]]::New() } process { if ($null -eq $Scope) { return } if ($Scope -isnot [EnvironmentVariableTarget]) { $msg = "Failed to validate scope ""${Scope}"" because it is a [$($Scope.GetType().FullName)] object, but " + 'we expected [EnvironmentVariableTarget].' Write-Error -Message $msg -ErrorAction Stop return } $receivedCount += 1 # PowerShell and .NET do not support user-level and computer-level environment variables on Linux and macOS. if (-not $IsWindows -and $Scope -ne [EnvironmentVariableTarget]::Process) { $writeError = $true return } if ($seenScopes.Contains($Scope)) { return } $Scope | Write-Output [void]$seenScopes.Add($Scope) } end { # Operate on process-level environment variables by default, if the user specifies no scope. if ($receivedCount -eq 0) { return [EnvironmentVariableTarget]::Process } if ($writeError) { if (-not $Message) { $Message = 'PowerShell and .NET only support user-level and computer-level environment variables on ' + 'Windows.' } Write-Error -Message $Message -ErrorAction $ErrorActionPreference } } } function Remove-CEnvVariable { <# .SYNOPSIS Removes an environment variable or items from an environment variable that's a list. .DESCRIPTION The `Remove-CEnvVariable` function deletes environment variables or items from an environment variable that is a list. To delete an environment variable, pass the names of the environment variables to delete to the `Name` parameter (or pipe the names into the function). If an environment variable does not exist at that scope, the function writes an error. Otherwise, the environment variable is deleted. To delete an item from an environment variable that is a list (e.g. `PATH`, `PSModulePath`, etc.), pass the environment variable's name to the `Name` parameter and the items to remove from the environment variable's list to the `Item` parameter. If an item doesn't exist in the list, the function deletes items that are in the list and writes an error if any items to remove are missing. By default, creates the list by splitting the environment variable's value using `[IO.Path]::PathSeparator` (`;` on Windows, `:` on Linux and macOS). To use a different separator, pass it to the `Separator` parameter. By default, operates on the current process's environment variables. PowerShell and .NET do not support user-level and computer-level environment variables. On Windows, use the `Scope` parameter to remove user-level and/or machine-level environment variables. Multiple scopes are accepted. Changes to environment variables are not reflected in running processes, including the current PowerShell session. If you want the removal of the user-level or machine-level environment variable to be reflected in the current process, include `Process` in the list of scopes passed to the `Scope` parameter. To remove a user-level environment variable for a specific user on Windows, pass that user's credentials to the `-Credential` parameter. A PowerShell process is run as that user to remove the environment variable. On Windows, environment variable names are case-insensitive. On Linux and macOS, environment variable names are case-sensitive. .LINK Set-CEnvVariable .LINK Test-CEnvVariable .LINK Uninstall-CEnvVariable .EXAMPLE Remove-CEnvVariable -Name 'MyEnvironmentVariable' Demonstrates how to remove an environment variable from the current process. In this example, the `MyEnvironmentVariable` is removed. If it doesn't exist, an error is written. .EXAMPLE Remove-CEnvVariable -Name 'SomeComputerVariable' -Scope Machine Demonstrates how to remove a computer-level environment variable. In this example, the `SomeComputerVariable` environment variable is removed from the computer's environment variables. If that computer-level variable doesn't exist, an error is written. .EXAMPLE Remove-CEnvVariable -Name 'SomeUsersVariable' -Scope User Demonstrates how to remove a user-level environment variable for the current user. In this example, the `SomeUsersVariable` environment variable is removed from the current user's environment variables. If it doesn't exist at the user scope, an error is written. .EXAMPLE Remove-CEnvVariable -Name 'SomeUsersVariable' -Scope Process,User Demonstrates how to have the change to a user-level or machine-level environment variable reflected in the current process by including `Process` in the list of scopes passed to `Scope`. .EXAMPLE Remove-CEnvVariable -Name 'SomeUsersVariable' -Credential $user Demonstrates how to remove a user-level environment variable for a specific user. In this example, the `SomeUsersVariable` environment variable is removed from the `$user` user's environment variables. If that user doesn't have a `SomeUsersVariable` environment variable, an error is written. .EXAMPLE 'Var1','Var2' | Remove-CEnvVariable Demonstrates that you can pipe the environment variables to delete to `Remove-CEnvVariable`. .EXAMPLE Remove-CEnvVariable -Name 'PATH' -Item 'C:\Some\Obsolete\Path' Demonstrates how to remove items from an environment variable whose value is a list. In this example, the `C:\Some\Obsolete\Path` path is removed from the `PATH` enviornment variable. .EXAMPLE Remove-CEnvVariable -Name 'PATH' -Item 'C:\Some\Obsolete\Path','C:\Some\Other\Obsolete\Path' Demonstrates that you can pass multiple items to the `Item` parameter to remove multiple items from an environment variable. .EXAMPLE Remove-CEnvVariable -Name 'MyPipeVar' -Item 'a' -Separator '|' Demonstrates how to remove items from an environment variable whose value is a list that uses a custom separator. In this case the `MyPipeVar` environment variable is split using a `|` character, `a` is removed, the list is joined with `|` character, and the environment variable is set to the new value. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='Single_ForCurrentUser')] param( # The environment variable to remove. Case-insensitive on Windows, case-sensitive on Linux and macOS. [Parameter(Mandatory, Position=0, ParameterSetName='Single_ForCurrentUser', ValueFromPipeline)] [Parameter(Mandatory, Position=0, ParameterSetName='Single_ForSpecificUser', ValueFromPipeline)] [Parameter(Mandatory, Position=0, ParameterSetName='List_ForCurrentUser')] [Parameter(Mandatory, Position=0, ParameterSetName='List_ForSpecificUser')] [String[]] $Name, # Items to remove from the environment variable. [Parameter(Mandatory, ParameterSetName='List_ForCurrentUser')] [Parameter(Mandatory, ParameterSetName='List_ForSpecificUser')] [String[]] $Item, # The separator for the items in the environment variable. Default is `[IO.Path]::PathSeparator`, `;` on # Windows, `:` on Linux and macOS. [Parameter(ParameterSetName='List_ForCurrentUser')] [Parameter(ParameterSetName='List_ForSpecificUser')] [String] $Separator, # The scopes at which to remove the environment variable. Default is the current process. [Parameter(ParameterSetName='Single_ForCurrentUser')] [Parameter(ParameterSetName='List_ForCurrentUser')] [EnvironmentVariableTarget[]] $Scope, # Remove an environment variable for a specific user. [Parameter(Mandatory, ParameterSetName='Single_ForSpecificUser')] [Parameter(Mandatory, ParameterSetName='List_ForSpecificUser')] [pscredential] $Credential, [Parameter(ParameterSetName='List_ForCurrentUser')] [Parameter(ParameterSetName='List_ForSpecificUser')] [switch] $Sensitive ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $userEnvVars = [Collections.Generic.List[string]]::new() $Scope = $Scope | Assert-Scope if (-not $Separator) { $Separator = [IO.Path]::PathSeparator } } process { if ($Credential) { $userEnvVars.AddRange( $Name ) return } foreach ($_name in $Name) { foreach ($_scope in $Scope) { $target = "$($_scope.ToString().ToLowerInvariant())-level environment variable ""${_name}""" if ($Item) { if (-not (Test-CEnvVariable -Name $_name -Scope $_scope)) { $msg = "Failed to remove items from ${target} because it does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } $currentItems = Split-CEnvVariable -Name $_name -Scope $_scope -Separator $Separator $itemsMissing = $Item | Where-Object { $_ -notin $currentItems } if ($itemsMissing) { $suffix = '' if (($itemsMissing | Measure-Object).Count -gt 1) { $suffix = 's' } $itemsMsg = """$($itemsMissing -join $Separator)"" item${suffix}" $thoseThe = 'those' if ($Sensitive) { $itemsMsg = "sensitive item${suffix}" $thoseThe = 'the' } $msg = "Failed to remove ${itemsMsg} from ${target} because ${thoseThe} item${suffix} do not " + 'exist.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference } $itemsToRemove = $Item | Where-Object { $_ -in $currentItems } $suffix = '' if (($itemsToRemove | Measure-Object).Count -gt 1) { $suffix = 's' } $itemsMsg = "item${suffix} ""$($itemsToRemove -join $Separator)""" if ($Sensitive) { $itemsMsg = "sensitive item${suffix}" } $newItems = $currentItems | Where-Object { $_ -notin $itemsToRemove } $newValue = $newItems -join $Separator if (-not $PSCmdlet.ShouldProcess($target, ("remove ${itemsMsg}" -replace '"', ''''))) { continue } Write-Information "Removing ${itemsMsg} from ${target}." [Environment]::SetEnvironmentVariable($_name, $newValue, $_scope) continue } if (-not (Test-CEnvVariable -Name $_name -Scope $_scope)) { $msg = "Failed to delete ${target} because it does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } if (-not $PSCmdlet.ShouldProcess($target, "remove")) { continue } Write-Information "Removing ${target}." [Environment]::SetEnvironmentVariable($_name, [NullString]::Value, $_scope) } } } end { if (-not $Credential -or -not $userEnvVars.Count) { return } if (-not $IsWindows) { $msg = 'PowerShell and .NET only support user-level environment variables on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $parameters = $PSBoundParameters [void]$parameters.Remove('Credential') [void]$parameters.Remove('Name') Start-Job -ScriptBlock { Import-Module -Name (Join-Path -Path $using:moduleDirPath -ChildPath 'Carbon.Environment.psm1') $VerbosePreference = $using:VerbosePreference $ErrorActionPreference = $using:ErrorActionPreference $DebugPreference = $using:DebugPreference $WhatIfPreference = $using:WhatIfPreference $InformationPreference = $using:InformationPreference Remove-CEnvVariable -Name $using:userEnvVars @using:parameters -Scope User } -Credential $Credential | Receive-Job -Wait -AutoRemoveJob } } function Set-CEnvVariable { <# .SYNOPSIS Creates or sets an environment variable. .DESCRIPTION The `Set-CEnvVariable` function creates or sets an environment variable. Pass the name of the environment variable to the `Name` parameter and the value to the `Value` parameter. An environment variable with that name and value is set for the current process. Use the `Scope` parameter to set user-level and/or machine-level variables. Uses `[Environment]::SetEnvironmentVariable` to create the variable if it doesn't exist, or update its value if the variable exists and its value is different from the value being set. For environment variable's that are lists (e.g. `PATH`, `PSModulesPath`, etc.), `Set-CEnvVariable` can add items to the beginning or end of the list. Pass the item(s) to add to the list to the `Item` parameter. Any item not already in the list is added to the beginning. To append items instead, use the `Append` switch. By default, uses `[IO.Path]::PathSeparator` as the item separator. Use the `Separator` parameter to use a custom separator. By default, creates and sets the current process's environment variables. PowerShell and .NET do not support user-level and computer-level environment variables. On Windows, use the `Scope` parameter to remove user-level and/or machine-level environment variables. Multiple scopes are accepted. Changes to environment variables are not reflected in running processes, including the current PowerShell session. If you want a new or changed user-level or machine-level environment variable to be reflected in the current process, include `Process` in the list of scopes passed to the `Scope` parameter. To create or set an environment variable for a specific user on Windows, pass that user's credentials to the `-Credential` parameter. This will run a PowerShell process that creates or sets the environment variable. Writes an information message for each environment variable created or updated. The message includes the value being set. Use the `Sensitive` switch to omit the value from the information message. On Windows, environment variable names are case-insensitive. On Linux and macOS, environment variable names are case-sensitive. In PowerShell 7.4 and earlier, setting `Value` to an empty string deletes the variable. In newer versions of PowerShell, the variable is set to an empty value. .LINK Remove-CEnvVariable .LINK Test-CEnvVariable .LINK Uninstall-CEnvVariable .EXAMPLE Set-CEnvVariable -Name 'MyEnvironmentVariable' -Value 'Value1' Demonstrates how to create or set an environemnt variable for the current process. In this example, the current process's `MyEnvironmentVariable` variable is created or set with a value of `Value1`. .EXAMPLE Set-CEnvVariable -Name 'MyEnvironmentVariable' -Value 'Value1' -Scope Machine Demonstrates how to create a computer-level environment variable by including `Machine` in the list of scopes passed to the `Scope` parameter. The current process's environment variables will not have the new or updated environment variable. .EXAMPLE Set-CEnvVariable -Name 'MyEnvironmentVariable' -Value 'Value1' -Scope User Demonstrates how to create a user environment variable by including `User` in the list of scopes passed to the `Scope` parameter. The current process's environment variables will not have the new or updated environment variable. .EXAMPLE Set-CEnvVariable -Name 'MyEnvironmentVariable' -Value 'Value1' -Scope Process,User Demonstrates how to have a change to a user or machine-level environment variable reflected in the current process by including `Process` in the list of scopes passed to the `Scope` parameter. .EXAMPLE Set-CEnvVariable -Name 'SomeUsersEnvironmentVariable' -Value 'SomeValue' -Credential $userCreds Demonstrates how to set an environment variable for a specific user by passing that user's credentials to the `Credential` parameter. .EXAMPLE Set-CEnvVariable -Name 'MySensitiveEnvironmentVariable' -Value 'SecretValue' -Sensitive Demonstrates how to omit the environment variable's value from the information message output by this function. #> [CmdletBinding(SupportsShouldProcess)] param( # The name of environment variable to add/set. Case-insensitive on Windows. Case-sensitive on Linux and macOS. [Parameter(Mandatory, Position=0)] [String] $Name, # The environment variable's value. In PowerShell 7.4 and earlier, setting this to an empty string deletes the # variable. In newer versions, the variable is created with an empty value. [Parameter(Mandatory, ParameterSetName='Single_CurrentUser')] [Parameter(Mandatory, ParameterSetName='Single_ForSpecificUser')] [AllowEmptyString()] [String] $Value, # Adds an item in an environment variable this is a list of items. [Parameter(Mandatory, ParameterSetName='List_CurrentUser')] [Parameter(Mandatory, ParameterSetName='List_ForSpecificUser')] [String[]] $Item, # The separator between items in the list. Default is `[IO.Path]::PathSeparator` (`;` on Windows; `:` on Linux # and macOS). [Parameter(ParameterSetName='List_CurrentUser')] [Parameter(ParameterSetName='List_ForSpecificUser')] [String] $Separator, # When adding an item to an environment variable that is a list, add it to the end of the list. By default, it # is added to the beginning. [Parameter(ParameterSetName='List_CurrentUser')] [Parameter(ParameterSetName='List_ForSpecificUser')] [switch] $Append, # The scopes at which to set the variable. Default is the current process. Changes to user-level and # computer-level variables are not reflected in the current process's environment variables unless `Process` is # in this list. [Parameter(ParameterSetName='Single_CurrentUser')] [Parameter(ParameterSetName='List_CurrentUser')] [EnvironmentVariableTarget[]] $Scope, [Parameter(Mandatory,ParameterSetName='List_ForSpecificUser')] [Parameter(Mandatory,ParameterSetName='Single_ForSpecificUser')] # Set an environment variable for a specific user. [pscredential] $Credential, # Don't output the variable's value in information messages. [switch] $Sensitive ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($Credential) { if (-not $IsWindows) { $msg = 'PowerShell and .NET only support user-level environment variables on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $parameters = $PSBoundParameters [void]$parameters.Remove('Credential') $job = Start-Job -ScriptBlock { Import-Module -Name (Join-Path -path $using:moduleDirPath -ChildPath 'Carbon.Environment.psm1' -Resolve) $VerbosePreference = $using:VerbosePreference $ErrorActionPreference = $using:ErrorActionPreference $DebugPreference = $using:DebugPreference $WhatIfPreference = $using:WhatIfPreference $InformationPreference = $using:InformationPreference Set-CEnvVariable @using:parameters -Scope User } -Credential $Credential $job | Wait-Job | Receive-Job $job | Remove-Job -Force -ErrorAction Ignore return } $Scope = $Scope | Assert-Scope if (-not $Separator) { $Separator = [IO.Path]::PathSeparator } foreach ($_scope in $Scope) { $target = "$($_scope.ToString().ToLowerInvariant())-level environment variable ""${Name}""" $action = 'set' $actionMsg = 'Setting' # Are we adding an item to an environment variable that is a list? if ($Item) { $items = Split-CEnvVariable -Name $Name -Scope $_scope -Separator $Separator $itemsToAdd = $Item | Where-Object { $items -notcontains $_ } if (-not $itemsToAdd) { continue } if ($itemsToAdd) { $newItems = $itemsToAdd -join $Separator $location = 'beginning' if ($Append) { $location = 'end' } $action = "add ""${newItems}""" $actionMsg = "Adding ""${newItems}"" to ${location} of" if ($Sensitive) { $itemCount = ($itemsToAdd | Measure-Object).Count $suffix = '' if ($itemCount -gt 1) { $suffix = 's' } $action = "adding ${itemCount} item${suffix}" $actionMsg = "Adding ${itemCount} item${suffix} to ${location} of" } $items = & { if (-not $Append) { $newItems | Write-Output } $items | Write-Output if ($Append) { $newItems | Write-Output } } $Value = $items -join $Separator } } # Only set the variable if its value has changed. if ($Value -eq [Environment]::GetEnvironmentVariable($Name, $_scope)) { continue } if (-not $PSCmdlet.ShouldProcess($target, $action)) { continue } $valueMsg = " to ""${Value}""" if ($Sensitive -or $Item) { $valueMsg = '' } Write-Information "${actionMsg} ${target}${valueMsg}." [Environment]::SetEnvironmentVariable($Name, $Value, $_scope) } } function Split-CEnvVariable { <# .SYNOPSIS Splits an environment variable value into a list. .DESCRIPTION The `Split-CEnvVariable` function splits an environment variable into a list. Pass the name of the environment variable to the `Name` parameter. By default, will split the environment variable using the `[IO.Path]::PathSeparator` character (`;` on Windows, `:` on Linux and macOS). Pass a custom separator to the `Separator` parameter. By default, splits process-level environment variables. To operate on user-level and computer-level environment variables, use the `Scope` parameter. Note that user-level and computer-level environment variables are only supported on Windows. If the environment variable doesn't exist, writes an error and returns an empty array. Environment variable names are case-insensitive on Windows and are case-sensitive on Linux and macOS. .EXAMPLE Split-CEnvVariable -Name 'PATH' Demonstrates how to split the current process's `PATH` environment variable using the `[IO.Path]::PathSeparator`. .EXAMPLE Split-CEnvVariable -Name 'PATH' -Scope Machine Demonstrates how to operate on a machine-level environment variable by passing `Machine` to the `Scope` parameter. .EXAMPLE Split-CEnvVariable -Name 'MyPipeVar' -Separator '|' Demonstrates how to split an environment variable using a custom separator. #> [CmdletBinding()] param( # The name of the environmen variable whose value to split. If the variable doesn't exist, writes an error. [Parameter(Mandatory)] [String] $Name, # The scope/level of environment variable to split. By default, uses process-level environment variables. Pass # `User` or `Machine` to split a user-level or machine-level environment variable. [EnvironmentVariableTarget] $Scope, # The string to use that separates items in the environment variable's values. By default, splits the # environment variable's value using `[IO.Path]::PathSeparator` (`;` on Windows, `:` on Linux and macOS). [String] $Separator ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $Scope = $Scope | Assert-Scope if (-not (Test-CEnvVariable -Name $Name -Scope $Scope)) { $msg = "Failed to split $($Scope.ToString().ToLowerInvariant())-level environment variable ""${Name}"" " + 'it doesn''t exist.' Write-Error -Message $msg -ErrorAction Ignore return @() } $value = [Environment]::GetEnvironmentVariable($Name, $Scope) if ($null -eq $value) { return @() } if (-not $Separator) { $Separator = [IO.Path]::PathSeparator } return $value.Split($Separator, [StringSplitOptions]::None) } function Test-CEnvVariable { <# .SYNOPSIS Tests if an environment variable exists or contains an item. .DESCRIPTION The `Test-CEnvVariable` function tests if an environment variable exists or, if an environment variable is a list, if the list contains an item. To check if an environment variable exits, pass the name of the variable to the `Name` parameter (or pipe in multiple names). If a variable with that name exists in the current process, returns `$true`. Otherwise, returns `$false`. To check if an item exists in an environment variable that is a list (e.g. `PATH`, `PSModulePath`, etc.), pass the name of the environment variable to the `Name` parameter and the item to check to the `Item` parameter. Splits the environment variable using `[IO.Path]::PathSeparator` (`;` on Windows, `:` on Linux and macOS) and returns true if the item is in that list. Returns false if the environment variable doesn't exist or doesn't have the item. Use the `Separator` parameter to use custom separator. By default, checks in the current process's environment variables. PowerShell and .NET do not support user-level and computer-level environment variables. On Windows, use the `Scope` parameter to check user-level or computer-level environment variables. To check if a specific user has an environment variable on Windows, pass that user's credentials to the `Credential` parameter. On Windows, environment variable names are case-insenstive. On Linux and macOS, they are case-sensitive. .LINK Remove-CEnvVariable .LINK Set-CEnvVariable .LINK Uninstall-CEnvVariable .EXAMPLE Test-CEnvVariable -Name 'PATH' Demonstrates how to check that an environment variable exists. In this case, will return `$true` if the `PATH` environment variable exists at any scope. .EXAMPLE Test-CEnvVariable -Name 'MY_VAR' -Scope User Demonstrates how to check that an environment variable exists at a specific scope. In this case, will return `$true` if the user has a `MY_VAR` environment variable. .EXAMPLE 'PATH' | Test-CEnvVariable Demonstrates that you can pipe environment variable names to `Test-CEnvVariable`. .EXAMPLE Test-CEnvVariable -Name 'PATH' -Item 'C:\Some\path' Demonstrates how to test if an environment variable that is a list contains an item. In this example, tests if the `PATH` environment variable contains `C:\Some\path`. #> [CmdletBinding(DefaultParameterSetName='CurrentUser')] param( # The name of the environment variable to check. Case-insensitive on Windows. Cse-sensitive on Linux and MacOS. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String] $Name, # The item in the environment variable to test for. [String] $Item, # The separator to use to split the environment variable into a list. By default, uses # `[IO.Path]::PathSeparator` (`;` on Windows, `:` on Linux and macOS). [String] $Separator, # The specific scope to check. By default, checks the current process. [Parameter(ParameterSetName='CurrentUser')] [EnvironmentVariableTarget] $Scope, # The credential of the user whose user-level environment variables to check. [Parameter(Mandatory, ParameterSetName='ForUser')] [pscredential] $Credential ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $userEnvVars = [Collections.Generic.List[String]]::New() $validScope = $Scope | Assert-Scope } process { if ($Credential) { $userEnvVars.Add($Name) return } if ($null -eq $validScope) { return } if ($Item) { return $Item -in (Split-CEnvVariable -Name $Name -Scope $validScope -Separator $Separator) } return ($null -ne [Environment]::GetEnvironmentVariable($Name, $validScope)) } end { if (-not $Credential -or -not $userEnvVars.Count) { return } if (-not $IsWindows) { $msg = 'PowerShell and .NET only support user-level environment variables on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $parameters = $PSBoundParameters [void]$parameters.Remove('Credential') [void]$parameters.Remove('Name') Start-Job -ScriptBlock { Import-Module -Name (Join-Path -path $using:moduleDirPath -ChildPath 'Carbon.Environment.psm1' -Resolve) $VerbosePreference = $using:VerbosePreference $ErrorActionPreference = $using:ErrorActionPreference $DebugPreference = $using:DebugPreference $WhatIfPreference = $using:WhatIfPreference $InformationPreference = $using:InformationPreference $using:userEnvVars | Test-CEnvVariable @using:parameters -Scope User } -Credential $Credential | Receive-Job -Wait -AutoRemoveJob | Write-Output } } function Uninstall-CEnvVariable { <# .SYNOPSIS Removes an environment variable or an item from an environment variable, if it exists. .DESCRIPTION The `Uninstall-CEnvVariable` function deletes environment variables or items from an environment variable. When deleting an environment variable, ignores if the environment variable no longer exists. When deleting an item from an environment variables, ignores if the item is no longer in the environment variable. To delete environment variables, pass their names to the `Name` parameter (or pipe in the names). Each environment variable that exists is deleted. To delete an item from an environment variable that is a list (e.g. `PATH`, `PSModulePath`, etc.), pass the name of the environment variable to the `Name` parameter, and the items to remove from the environment variable to the `Item` parameter. Each item that exists in the environment variable is removed. By default, the environment variable is split using `[IO.Path]::PathSeparator` (`;` on Windows, `:` on Linux and macOS). Pass a custom separator to the `Separator` parameter. By default, removes the current process's environment variables. PowerShell and .NET do not support user-level and computer-level environment variables. On Windows, use the `Scope` parameter to remove user-level and/or machine-level environment variables. Multiple scopes are accepted. Changes to environment variables are not reflected in running processes, including the current PowerShell session. If you want the removal of user-level and/or machine-level environment variable to be reflected in the current process, include `Process` in the list of scopes passed to the `Scope` parameter. To remove a specific user's user-level environment variable on Windows, pass that user's credentials to the `-Credential` parameter. On Windows, environment variable names are case-insensitive. On Linux and macOS, environment variable names are case-sensitive. .LINK Remove-CEnvVariable .LINK Set-CEnvVariable .LINK Test-CEnvVariable .EXAMPLE Uninstall-CEnvVariable -Name 'MyEnvironmentVariable' Demonstrates how to remove an environment variable from the current process if it exists. In this case, will remove the `MyEnvironmentVariable` environment variable, if it exists. .EXAMPLE Uninstall-CEnvVariable -Name 'MyUserVariable' -Scope User Demonstrates how to remove a user-level environment by including `User` in the list of scopes. .EXAMPLE Uninstall-CEnvVariable -Name 'MyComputerVariable' -Scope Machine Demonstrates how to remove a computer-level environment by including `Machine` in the list of scopes. .EXAMPLE Uninstall-CEnvVariable -Name 'MyComputerVariable' -Scope User,Machine Demonstrates that you can pass multiple scopes to the `Scope` parameter. .EXAMPLE Uninstall-CEnvVariable -Name 'MyComputerVariable' -Scope Process,Machine Demonstrates how to have the removal of a computer-level environment reflected in the current process by including `Process` in the list of scopes. .EXAMPLE 'Var1','Var2' | Uninstall-CEnvVariable Demonstrates that you can pipe names to `Uninstall-CEnvVariable`. .EXAMPLE Uninstall-CEnvVariable Name 'Var1','Var2' Demonstrates that you can pass an array of names to the `Name` parameter. .EXAMPLE Uninstall-CEnvVariable -Name 'SomeUsersVariable' -Credential $credential Demonstrates that you can remove another user's user-level environment variable by passing its credentials to the `Credential` parameter. This runs a separate PowerShell process as that user to remove the variable. .EXAMPLE Uninstall-CEnvVariable -Name 'PATH' -Item 'C:\Some\Obsolete\Path' Demonstrates how to remove items from an environment variable whose value is a list. In this example, the `C:\Some\Obsolete\Path` path is removed from the `PATH` enviornment variable. .EXAMPLE Uninstall-CEnvVariable -Name 'PATH' -Item 'C:\Some\Obsolete\Path','C:\Some\Other\Obsolete\Path' Demonstrates that you can pass multiple items to the `Item` parameter to remove multiple items from an environment variable. .EXAMPLE Uninstall-CEnvVariable -Name 'MyPipeVar' -Item 'a' -Separator '|' Demonstrates how to remove items from an environment variable whose value is a list that uses a custom separator. In this case the `MyPipeVar` environment variable is split using a `|` character, `a` is removed, the list is joined with `|` character, and the environment variable is set to the new value. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ForCurrentUser')] param( # The environment variable to remove. Case-insensitive on Windows, case-sensitive on Linux and macOS. [Parameter(Mandatory, ValueFromPipeline)] [String[]] $Name, # Items to remove from the environment variable. By default, the entire environment variable is removed if it # exists. If one or more items are specified, the environment variable's value is split using # `[IO.Path]::PathSeparator` (`;` on Windows, `:` on Linux and macOS), and each item in the list is removed. The # list is joined with the path separator, and the environment variable's value is set. # # Use the `Separator` parameter to customize the separator to use use. [String[]] $Item, # The separator for items in the list. Ignored unless `Item` has a value. [String] $Separator, # If set and removing items from an environment variable's value, omits the values being removed from # information messages. [switch] $Sensitive, # The scopes at which to remove the environment variable. Defaults to the current process. [Parameter(ParameterSetName='ForCurrentUser')] [EnvironmentVariableTarget[]] $Scope, # Remove an environment variable for a specific user. [Parameter(Mandatory, ParameterSetName='ForSpecificUser')] [pscredential] $Credential ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $userEnvVarsToDelete = [Collections.Generic.List[String]]::New() $Scope = $Scope | Assert-Scope if (-not $PSBoundParameters.ContainsKey('Separator')) { $Separator = [IO.Path]::PathSeparator } } process { if ($PSCmdlet.ParameterSetName -eq 'ForSpecificUser') { $userEnvVarsToDelete.AddRange($Name) return } foreach ($_name in $Name) { foreach ($_scope in $Scope) { if (-not (Test-CEnvVariable -Name $_name -Scope $_scope)) { continue } $target = "$($_scope.ToString().ToLowerInvariant())-level environment variable ""${_name}""" if ($Item) { $currentItems = Split-CEnvVariable -Name $_name -Scope $_scope -Separator $Separator $itemsToRemove = $currentItems | Where-Object { $_ -in $Item } if (-not $itemsToRemove) { continue } $newItems = $currentItems | Where-Object { $_ -notin $itemsToRemove } $newValue = $newItems -join $Separator $itemsToRemoveMsg = $itemsToRemove -join $Separator $infoItemsMsg = 'items' $targetItemsMsg = '' if (-not $Sensitive) { $infoItemsMsg = """${itemsToRemoveMsg}""" $targetItemsMsg = " '${itemsToRemoveMsg}'" } $suffix = '' if (($itemsToRemove | Measure-Object).Count -gt 1) { $suffix = 's' } if (-not $PSCmdlet.ShouldProcess($target, "remove item${suffix}${targetItemsMsg}")) { continue } Write-Information "Removing ${infoItemsMsg} from ${target}." [Environment]::SetEnvironmentVariable($_name, $newValue, $_scope) continue } if (-not $PSCmdlet.ShouldProcess($target, 'remove')) { continue } Write-Information "Removing ${target}." [Environment]::SetEnvironmentVariable($_name, [NullString]::Value, $_scope) } } } end { if (-not $userEnvVarsToDelete.Count) { return } if (-not $IsWindows) { $msg = 'PowerShell and .NET only support user-level environment variables on Windows.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $uninstallArgs = $PSBoundParameters [void]$uninstallArgs.Remove('Credential') [void]$uninstallArgs.Remove('Name') Start-Job -ScriptBlock { Import-Module -Name (Join-Path -Path $using:moduleDirPath -ChildPath 'Carbon.Environment.psm1') $VerbosePreference = $using:VerbosePreference $ErrorActionPreference = $using:ErrorActionPreference $DebugPreference = $using:DebugPreference $WhatIfPreference = $using:WhatIfPreference $InformationPreference = $using:InformationPreference Uninstall-CEnvVariable -Name $using:userEnvVarsToDelete -Scope User @using:uninstallArgs } -Credential $Credential | Receive-Job -Wait -AutoRemoveJob } } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` # attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the # `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken # from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |