1 # This PS module contains functions for Desired State Configuration (DSC) "Environment" provider
3 # Fallback message strings in en-US
7 ConvertFrom-StringData @'
8 EnvVarCreated = (CREATE) Environment variable '{0}' with value '{1}'
9 EnvVarSetError = (ERROR) Failed to set environment variable '{0}' to value '{1}'
10 EnvVarPathSetError = (ERROR) Failed to add path '{0}' to environment variable '{1}' holding value '{2}'
11 EnvVarRemoveError = (ERROR) Failed to remove environment variable '{0}' holding value '{1}'
12 EnvVarPathRemoveError = (ERROR) Failed to remove path '{0}' from variable '{1}' holding value '{2}'
13 EnvVarUnchanged = (UNCHANGED) Environment variable '{0}' with value '{1}'
14 EnvVarUpdated = (UPDATE) Environment variable '{0}' from value '{1}' to value '{2}'
15 EnvVarPathUnchanged = (UNCHANGED) Path environment variable '{0}' with value '{1}'
16 EnvVarPathUpdated = (UPDATE) Environment variable '{0}' from value '{1}' to value '{2}'
17 EnvVarNotFound = (NOT FOUND) Environment variable '{0}'
18 EnvVarFound = (FOUND) Environment variable '{0}' with value '{1}'
19 EnvVarFoundWithMisMatchingValue = (FOUND MISMATCH) Environment variable '{0}' with value '{1}' mismatched the specified value '{2}'
20 EnvVarRemoved = (REMOVE) Environment variable '{0}'
23 Import-LocalizedData LocalizedData -filename MSFT_EnvironmentResource.strings.psd1
26 #-------------------------------------
27 # Script-level Constants and Variables
28 #-------------------------------------
29 $EnvVarRegPathMachine = "HKLM:\\System\\CurrentControlSet\\Control\\Session Manager\\Environment"
30 $EnvVarRegPathUser = "HKCU:\\Environment"
32 $EnvironmentVariableTarget = @{ Process = 0; User = 1; Machine = 2 }
33 $MaxSystemEnvVariableLength = 1024
34 $MaxUserEnvVariableLength = 255
36 Function Throw-InvalidArgumentException
43 $exception = new-object System.ArgumentException $Message,$ParamName
44 $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,$ParamName,"InvalidArgument",$null
48 function GetEnvironmentVariable
52 [parameter(Mandatory = $true)]
53 [ValidateNotNullOrEmpty()]
56 [parameter(Mandatory = $true)]
60 if ($Target -eq $EnvironmentVariableTarget.Process)
62 return [System.Environment]::GetEnvironmentVariable($Name);
65 if ($Target -eq $EnvironmentVariableTarget.Machine)
67 $retVal = Get-ItemProperty $EnvVarRegPathMachine -Name $Name -ErrorAction SilentlyContinue
71 if ($Target -eq $EnvironmentVariableTarget.User)
73 $retVal = Get-ItemProperty $EnvVarRegPathUser -Name $Name -ErrorAction SilentlyContinue
78 function SetEnvironmentVariable
82 [parameter(Mandatory = $true)]
83 [ValidateNotNullOrEmpty()]
88 [parameter(Mandatory = $true)]
92 if ($Target -eq $EnvironmentVariableTarget.Process)
94 [System.Environment]::SetEnvironmentVariable($Name, $Value);
97 if ($Target -eq $EnvironmentVariableTarget.Machine)
99 if ($Name.Length -ge $MaxSystemEnvVariableLength) {
100 Throw-InvalidArgumentException -Message "Argument is too long." -ParamName $Name
102 $Path = $EnvVarRegPathMachine
104 elseif ($Target -eq $EnvironmentVariableTarget.User)
106 if ($Name.Length -ge $MaxUserEnvVariableLength) {
107 Throw-InvalidArgumentException -Message "Argument is too long." -ParamName $Name
109 $Path = $EnvVarRegPathUser
112 $environmentKey = Get-ItemProperty $Path -Name $Name -ErrorAction SilentlyContinue
117 Remove-ItemProperty $Path -Name $Name -ErrorAction SilentlyContinue
121 Set-ItemProperty $Path -Name $Name -Value $Value -ErrorAction SilentlyContinue
127 #------------------------------
128 # The Get-TargetResource cmdlet
129 #------------------------------
130 FUNCTION Get-TargetResource
134 [parameter(Mandatory = $true)]
135 [ValidateNotNullOrEmpty()]
140 $retVal = GetItemProperty $EnvVarRegPathMachine -Name $Name -Expand:$false -ErrorAction SilentlyContinue
142 if($retVal -eq $null)
144 Write-Verbose ($localizedData.EnvVarNotFound -f $Name)
146 return @{Ensure='Absent'; Name=$Name}
149 Write-Verbose ($localizedData.EnvVarFound -f $Name, $retVal.$Name)
151 return @{Ensure='Present'; Name=$Name; Value=$retVal.$Name}
158 [parameter(Mandatory = $true)]
159 [ValidateNotNullOrEmpty()]
165 $Value = [String]::Empty
168 $err = Set-ItemProperty $EnvVarRegPathMachine -Name $Name -Value $Value 2>&1
172 Write-Verbose ($localizedData.EnvVarSetError -f $Name, $Value)
181 SetEnvironmentVariable -Name $Name -Value $Value -Target $EnvironmentVariableTarget.Machine
182 SetEnvironmentVariable -Name $Name -Value $Value -Target $EnvironmentVariableTarget.Process
187 Write-Verbose ($localizedData.EnvVarSetError -f $Name, $Value)
193 function Remove-EnvVar
197 [parameter(Mandatory = $true)]
198 [ValidateNotNullOrEmpty()]
203 $curVarProperties = Get-ItemProperty $EnvVarRegPathMachine -Name $Name -ErrorAction SilentlyContinue
204 $currentValueFromEnv = GetEnvironmentVariable -Name $name -Target $EnvironmentVariableTarget.Process
206 if($curVarProperties -ne $null)
208 $err = Remove-ItemProperty $EnvVarRegPathMachine -Name $Name 2>&1
212 Write-Log -Message ($localizedData.EnvVarRemoveError -f $Name, $Value)
218 if($currentValueFromEnv -ne $null)
222 SetEnvironmentVariable -Name $Name -Value $null -Target $EnvironmentVariableTarget.Machine
223 SetEnvironmentVariable -Name $Name -Value $null -Target $EnvironmentVariableTarget.Process
227 Write-Verbose ($localizedData.EnvVarRemoveError -f $Name, $Value)
235 #------------------------------
236 # The Set-TargetResource cmdlet
237 #------------------------------
238 FUNCTION Set-TargetResource
240 [CmdletBinding(SupportsShouldProcess=$true)]
243 [parameter(Mandatory = $true)]
244 [ValidateNotNullOrEmpty()]
250 $Value = [String]::Empty,
252 [ValidateSet("Present", "Absent")]
260 $ValueSpecified = $PSBoundParameters.ContainsKey("Value")
262 $curVarProperties = GetItemProperty $EnvVarRegPathMachine -Name $Name -Expand:(-not $Path) -ErrorAction SilentlyContinue
263 $currentValueFromEnv = GetEnvironmentVariable -Name $name -Target $EnvironmentVariableTarget.Process
267 if ($Ensure -ieq "Present")
269 if (($curVarProperties -eq $null) -or (($currentValueFromEnv -eq $null) -and ($curVarProperties.$Name -ne [string]::Empty))) # The specified variable doesn't exist already
271 # Given the specified $Name environment variable doesn't exist already,
272 # simply create one with the specified value and return. If no $Value is
273 # specified, the default value is set to empty string "" (per spec).
274 # Both path and non-path cases are covered by this.
276 $successMessage = $localizedData.EnvVarCreated -f $Name, $Value
278 if ($PSCmdlet.ShouldProcess($successMessage, $null, $null))
280 Set-EnvVar -Name $Name -Value $Value
286 # If the control reaches here, the specified variable exists already
288 if (!$ValueSpecified)
290 # Given no $Value was specified to be set and the variable exists,
291 # we'll leave the existing variable as is.
292 # This covers both path and non-path variables.
294 Write-Log -Message ($localizedData.EnvVarUnchanged -f $Name, $curVarProperties.$Name)
299 # If the control reaches here: the specified variable exists already and a $Value has been specified to be set.
303 # For non-path variables, simply set the specified $Value as the new value of the specified
304 # variable $Name, then return.
306 $successMessage = $localizedData.EnvVarUpdated -f $Name, $curVarProperties.$Name, $Value
307 if ($Value -ceq $curVarProperties.$Name)
309 $successMessage = $localizedData.EnvVarUnchanged -f $Name, $curVarProperties.$Name
312 if ($PSCmdlet.ShouldProcess($successMessage, $null, $null) -and ($Value -cne $curVarProperties.$Name))
314 Set-EnvVar -Name $Name -Value $Value
320 # If the control reaches here: the specified variable exists already, it is a path variable and a $Value has been specified to be set.
322 # Check if an empty, whitespace or semi-colon only string has been specified. If yes, return unchanged.
323 $trimmedValue = $Value.Trim(";"," ")
324 if ([String]::IsNullOrEmpty($trimmedValue))
326 Write-Log -Message ($localizedData.EnvVarPathUnchanged -f $Name, $curVarProperties.$Name)
332 $setValue = $curVarProperties.$Name + ";"
333 $specifiedPaths = $trimmedValue -split ";"
334 $currentPaths = $curVarProperties.$Name -split ";"
337 foreach ($specifiedPath in $specifiedPaths)
339 if (FindSubPath -QueryPath $specifiedPath -PathList $currentPaths)
341 # Found this $specifiedPath as one of the $currentPaths, no need to add this again, skip/continue to the next $specifiedPath
346 # If the control reached here, we didn't find this $specifiedPath in the $currentPaths, add it
347 # and mark the environment variable as updated.
350 $setValue += $specifiedPath + ";"
353 # Remove any extraneous ";" at the end (and potentially start - as a side-effect) of the value to be set
354 $setValue = $setValue.Trim(";")
356 # Set the expected success message
357 $successMessage = $localizedData.EnvVarPathUnchanged -f $Name, $curVarProperties.$Name
360 $successMessage = $localizedData.EnvVarPathUpdated -f $Name, $curVarProperties.$Name, $setValue
363 if ($PSCmdlet.ShouldProcess($successMessage, $null, $null))
365 # Finally update the existing environment path variable
367 Set-EnvVar -Name $Name -Value $setValue
373 elseif ($Ensure -ieq "Absent")
375 if(($curVarProperties -eq $null) -and ($currentValueFromEnv -eq $null))
377 # Variable not found, condition is satisfied and there is nothing to set/remove, return
379 Write-Log -Message ($localizedData.EnvVarNotFound -f $Name)
384 if(!$ValueSpecified -or !$Path)
386 # If no $Value specified to be removed, simply remove the environment variable (holds true for both path and non-path variables
388 # Regardless of $Value, if the target variable is a non-path variable, simply remove it to meet the absent condition
390 $successMessage = $localizedData.EnvVarRemoved -f $Name
392 if ($PSCmdlet.ShouldProcess($successMessage, $null, $null))
394 Remove-EnvVar -Name $Name
400 # If the control reaches here: target variable is an existing environment path-variable and a specified $Value needs be removed from it
402 # Check if an empty string or semi-colon only string has been specified as $Value. If yes, return unchanged as we don't need to remove anything.
403 $trimmedValue = $Value.Trim(";")
404 if ([String]::IsNullOrEmpty($trimmedValue))
406 Write-Log -Message ($localizedData.EnvVarPathUnchanged -f $Name, $curVarProperties.$Name)
412 $specifiedPaths = $trimmedValue -split ";"
413 $currentPaths = $curVarProperties.$Name -split ";"
416 foreach ($subpath in $currentPaths)
418 if (FindSubPath -QueryPath $subpath -PathList $specifiedPaths)
420 # Found this $subpath as one of the $specifiedPaths, skip adding this to the final value/path of this variable
421 # and mark the variable as altered.
427 # If the control reaches here, the current $subpath was not part of the $specifiedPaths (to be removed),
428 # so keep this $subpath in the finalPath
430 $finalPath += $subpath + ";"
433 # Remove any extraneous ";" at the end (and potentially start - as a side-effect) of the $finalPath
434 $finalPath = $finalPath.Trim(";")
437 # Set the expected success message
438 $successMessage = $localizedData.EnvVarPathUnchanged -f $Name, $curVarProperties.$Name
441 $successMessage = $localizedData.EnvVarPathUpdated -f $Name, $curVarProperties.$Name, $finalPath
443 if ([String]::IsNullOrEmpty($finalPath))
445 $successMessage = $localizedData.EnvVarRemoved -f $Name
449 # Handle WhatIf case and update resource as appropriate
450 if ($PSCmdlet.ShouldProcess($successMessage, $null, $null))
452 # Finally, update the environment path-variable
454 if ([String]::IsNullOrEmpty($finalPath))
456 Remove-EnvVar -Name $Name
460 Set-EnvVar -Name $Name -Value $finalPath
465 Write-Log -Message ($localizedData.EnvVarPathRemoveError -f $Value, $Name, $curVarProperties.$Name)
474 #-------------------------------
475 # The Test-TargetResource cmdlet
476 #-------------------------------
477 FUNCTION Test-TargetResource
481 [parameter(Mandatory = $true)]
482 [ValidateNotNullOrEmpty()]
490 [ValidateSet("Present", "Absent")]
498 $ValueSpecified = $PSBoundParameters.ContainsKey("Value")
499 $curVarProperties = GetItemProperty $EnvVarRegPathMachine -Name $Name -Expand:(-not $Path) -ErrorAction SilentlyContinue
500 $currentValueFromEnv = GetEnvironmentVariable -Name $name -Target $EnvironmentVariableTarget.Process
504 if ($Ensure -ieq "Present")
506 if (($curVarProperties -eq $null) -or (($currentValueFromEnv -eq $null) -and ($curVarProperties.$Name -ne [string]::Empty)) )
508 # Variable not found, return failure
510 Write-Verbose ($localizedData.EnvVarNotFound -f $Name)
515 if (!$ValueSpecified)
517 # No value has been specified for test, so the existence of the variable means success
519 Write-Verbose ($localizedData.EnvVarFound -f $Name, $curVarProperties.$Name)
526 # For this non-path variable, make sure that the specified $Value matches the current value.
527 # Success if it matches, failure otherwise
529 if ($Value -ceq $curVarProperties.$Name)
531 Write-Verbose ($localizedData.EnvVarFound -f $Name, $curVarProperties.$Name)
537 Write-Verbose ($localizedData.EnvVarFoundWithMisMatchingValue -f $Name, $curVarProperties.$Name, $Value)
543 # If the control reaches here, the expected environment variable exists, it is a path variable and a $Value is specified to test against
545 if (FindPath -ExistingPaths $curVarProperties.$Name -QueryPaths $Value -FindCriteria All)
547 # The specified path was completely present in the existing environment variable, return success
549 Write-Verbose ($localizedData.EnvVarFound -f $Name, $curVarProperties.$Name)
554 # If the control reached here some part of the specified path ($Value) was not found in the existing variable, return failure
556 Write-Verbose ($localizedData.EnvVarFoundWithMisMatchingValue -f $Name, $curVarProperties.$Name, $Value)
563 elseif ($Ensure -eq "Absent")
565 if(($curVarProperties -eq $null) -and ($currentValueFromEnv -eq $null))
567 # Variable not found (path/non-path and $Value both do not matter then), return success
569 Write-Verbose ($localizedData.EnvVarNotFound -f $Name)
574 if (!$ValueSpecified)
576 # Given no value has been specified for test, the mere existence of the variable fails the test
578 Write-Verbose ($localizedData.EnvVarFound -f $Name, $curVarProperties.$Name)
583 # If the control reaches here: the variable exists and a value has been specified to test against it
587 # For this non-path variable, make sure that the specified value doesn't match the current value
588 # Success if it doesn't match, failure otherwise
590 if ($Value -cne $curVarProperties.$Name)
592 Write-Verbose ($localizedData.EnvVarFoundWithMisMatchingValue -f $Name, $curVarProperties.$Name, $Value)
598 Write-Verbose ($localizedData.EnvVarFound -f $Name, $curVarProperties.$Name)
604 # If the control reaches here: the variable exists, it is a path variable, and a value has been specified to test against it
606 if (FindPath -ExistingPaths $curVarProperties.$Name -QueryPaths $Value -FindCriteria Any)
608 # One of the specified paths in $Value exists in the environment variable path, thus the test fails
610 Write-Verbose ($localizedData.EnvVarFound -f $Name, $curVarProperties.$Name)
615 # If the control reached here, none of the specified paths were found in the existing path-variable, return success
617 Write-Verbose ($localizedData.EnvVarFoundWithMisMatchingValue -f $Name, $curVarProperties.$Name, $Value)
624 #----------------------------------------
625 # Utility to write WhatIf or Verbose logs
626 #----------------------------------------
629 [CmdletBinding(SupportsShouldProcess=$true)]
632 [parameter(Mandatory = $true)]
633 [ValidateNotNullOrEmpty()]
638 if ($PSCmdlet.ShouldProcess($Message, $null, $null))
640 Write-Verbose $Message
645 #-----------------------------------
646 # Utility to match environment paths
647 #-----------------------------------
658 [parameter(Mandatory = $true)]
659 [ValidateSet("Any", "All")]
664 $existingPathList = $ExistingPaths -split ";"
665 $queryPathList = $QueryPaths -split ";"
667 switch ($FindCriteria)
671 foreach ($queryPath in $queryPathList)
673 if (FindSubPath -QueryPath $queryPath -PathList $existingPathList)
675 # Found this $queryPath in the existing paths, return $true
680 # If the control reached here, none of the $QueryPaths were found as part of the $ExistingPaths, return $false
686 foreach ($queryPath in $queryPathList)
691 if (!(FindSubPath -QueryPath $queryPath -PathList $existingPathList))
693 # The current $queryPath wasn't found in any of the $existingPathList, return failure
699 # If the control reached here, all of the $QueryPaths were found as part of the $ExistingPaths, return $true
706 #---------------------------------------
707 # Utility to search a path in a pathlist
708 #---------------------------------------
720 foreach ($path in $PathList)
722 if($QueryPath -ieq $path)
724 # If the query path matches any of the paths in $PathList, return $true
732 #---------------------------------------------------------------
733 # Utility to get item property without expanding it if necessary
734 #---------------------------------------------------------------
735 FUNCTION GetItemProperty
739 [parameter(Mandatory = $true)]
740 [ValidateNotNullOrEmpty()]
754 return (Get-ItemProperty $EnvVarRegPathMachine -Name $Name -ErrorAction SilentlyContinue)
758 if (!(Test-Path -Path $Path))
763 $PathTokens = $Path.Split('\',[System.StringSplitOptions]::RemoveEmptyEntries)
764 $Division = $PathTokens[0].Replace(':', '')
765 $Entry = $PathTokens[1..($PathTokens.Count-1)] -join '\'
767 # Since the target registry path coming to this function is hardcoded for local machine
768 $Hive = [Microsoft.Win32.Registry]::LocalMachine
770 $NoteProperties = @{}
773 $Key = $Hive.OpenSubKey($Entry)
775 $ValueNames = $Key.GetValueNames()
776 if ($ValueNames -inotcontains $Name)
781 [string] $Value = $Key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
782 $NoteProperties.Add($Name, $Value)
792 [System.Management.Automation.PSObject] $PropertyResults = New-Object -TypeName System.Management.Automation.PSObject -Property $NoteProperties
794 return $PropertyResults
798 Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource