From 89407aa2edf60acfe1e31a9dccdf22d071bf71b3 Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Wed, 7 Sep 2016 11:43:27 -0700 Subject: [PATCH 1/6] Merge open Registry with in-box Registry --- .../MSFT_xRegistryResource.psm1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 index 32cade67d..c1067d53f 100644 --- a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 +++ b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 @@ -5,7 +5,7 @@ #> # Fallback message strings in en-US -DATA localizedData +data localizedData { # culture = "en-US" ConvertFrom-StringData @' @@ -40,7 +40,7 @@ DATA localizedData } # Commented-out until more languages are supported -# Import-LocalizedData LocalizedData -filename MSFT_xRegistryResource.strings.psd1 +# Import-LocalizedData LocalizedData -FileName MSFT_xRegistryResource.strings.psd1 <# .SYNOPSIS @@ -303,7 +303,7 @@ function Set-TargetResource [System.String[]] $ValueData = @(), - [ValidateSet('String', 'Binary', 'Dword', 'Qword', 'MultiString', 'ExpandString')] + [ValidateSet("String", "Binary", "DWord", "QWord", "MultiString", "ExpandString")] [System.String] $ValueType = 'String', @@ -604,7 +604,7 @@ function Test-TargetResource [System.String[]] $ValueData = @(), - [ValidateSet('String', 'Binary', 'Dword', 'Qword', 'MultiString', 'ExpandString')] + [ValidateSet("String", "Binary", "DWord", "QWord", "MultiString", "ExpandString")] [System.String] $ValueType = 'String', @@ -1287,7 +1287,7 @@ function Convert-ByteArrayToHexString ) $retString = '' - $Data | ForEach-Object { $retString += ('{0:x}' -f $_) } + $Data | ForEach-Object { $retString += ('{0:x2}' -f $_) } return $retString } @@ -1464,7 +1464,7 @@ function Compare-ValueData # Special case for binary comparison (do hex-string comparison) if ($ValueType -ieq 'Binary') { - $specifiedData = $ValueData[0] + $specifiedData = $ValueData[0].PadLeft($retrievedData.Length, '0') } # If the ValueType is not multistring, do a simple comparison @@ -1498,4 +1498,4 @@ function Compare-ValueData return $true } -Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource +Export-ModuleMember -Function *-TargetResource From 88479617e68c6e7e260104cb745361387c793f25 Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Fri, 9 Sep 2016 17:59:28 -0700 Subject: [PATCH 2/6] Add unit tests and update resource accordingly --- .../MSFT_xRegistryResource.psm1 | 19 +- Tests/MSFT_xRegistryResource.Tests.ps1 | 47 -- .../MSFT_xRegistryResource.TestHelper.psm1 | 466 ++++++++++++++++++ Tests/Unit/MSFT_xRegistryResource.Tests.ps1 | 337 +++++++++++++ 4 files changed, 807 insertions(+), 62 deletions(-) delete mode 100644 Tests/MSFT_xRegistryResource.Tests.ps1 create mode 100644 Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 create mode 100644 Tests/Unit/MSFT_xRegistryResource.Tests.ps1 diff --git a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 index c1067d53f..4c569f825 100644 --- a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 +++ b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 @@ -303,7 +303,7 @@ function Set-TargetResource [System.String[]] $ValueData = @(), - [ValidateSet("String", "Binary", "DWord", "QWord", "MultiString", "ExpandString")] + [ValidateSet('String', 'Binary', 'DWord', 'QWord', 'MultiString', 'ExpandString')] [System.String] $ValueType = 'String', @@ -474,12 +474,7 @@ function Set-TargetResource { try { - # Formulate hiveName and subkeyName compatible with .NET APIs - $hiveName = $keyInfo.Data.PSDrive.Root.Replace('_','').Replace('HKEY','') - $subkeyName = $keyInfo.Data.Name.Substring($keyInfo.Data.Name.IndexOf('\')+1) - - # Finally remove the subkeytree - [Microsoft.Win32.Registry]::$hiveName.DeleteSubKeyTree($subkeyName) + $null = Remove-Item -Path $Key -Recurse -Force } catch [System.Exception] { @@ -521,13 +516,7 @@ function Set-TargetResource { try { - # Formulate hiveName and subkeyName compatible with .NET APIs - $hiveName = $keyInfo.Data.PSDrive.Root.Replace('_','').Replace('HKEY','') - $subkeyName = $keyInfo.Data.Name.Substring($keyInfo.Data.Name.IndexOf('\')+1) - - # Finally open the subkey and remove the RegValue in subkey - $subkey = [Microsoft.Win32.Registry]::$hiveName.OpenSubKey($subkeyName, $true) - $subkey.DeleteValue($ValueName) + $null = Remove-ItemProperty -Path $Key -Name $ValueName -Force } catch [System.Exception] @@ -604,7 +593,7 @@ function Test-TargetResource [System.String[]] $ValueData = @(), - [ValidateSet("String", "Binary", "DWord", "QWord", "MultiString", "ExpandString")] + [ValidateSet('String', 'Binary', 'DWord', 'QWord', 'MultiString', 'ExpandString')] [System.String] $ValueType = 'String', diff --git a/Tests/MSFT_xRegistryResource.Tests.ps1 b/Tests/MSFT_xRegistryResource.Tests.ps1 deleted file mode 100644 index 75ff47d77..000000000 --- a/Tests/MSFT_xRegistryResource.Tests.ps1 +++ /dev/null @@ -1,47 +0,0 @@ -$ErrorActionPreference = 'Stop' - -Get-Module MSFT_xRegistryResource | Remove-Module -Force -Import-Module $PSScriptRoot\..\DSCResources\MSFT_xRegistryResource\MSFT_xRegistryResource.psm1 -Prefix UnitTest - -Describe 'MSFT_xRegistryResource' { - BeforeAll { - $rootPath = 'Software\__MSFT_xRegistryResource__' - $rootPathWithDrive = "HKCU:\$rootPath" - if (Test-Path -LiteralPath $rootPathWithDrive) - { - Remove-Item -LiteralPath $rootPathWithDrive -Recurse -Force - } - - New-Item -Path $rootPathWithDrive - } - - AfterAll { - if (Test-Path -LiteralPath $rootPathWithDrive) - { - Remove-Item -LiteralPath $rootPathWithDrive -Recurse -Force - } - } - - It 'Supports keys containing forward slashes' { - $keyName = 'Test/Key' - $valueName = 'Testing' - $valueData = 'TestValue' - - $scriptBlock = { - Set-UnitTestTargetResource -Key $rootPathWithDrive\$keyName ` - -ValueName $valueName ` - -ValueData $valueData ` - -ValueType String ` - -Force $true ` - -ErrorAction Stop - } - - $scriptBlock | Should Not Throw - - $regKey = (Get-Item HKCU:\).OpenSubKey("$rootPath\$keyName") - - $regKey | Should Not Be Null - $regKey.GetValue($valueName) | Should Be $valueData - } -} - diff --git a/Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 b/Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 new file mode 100644 index 000000000..4ef210a3d --- /dev/null +++ b/Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 @@ -0,0 +1,466 @@ +<# + .SYNOPSIS + Retrieves a registry key in read-only mode. + + .PARAMETER KeyPath + The path to the registry key to be opened. + Must include the registry hive. + + .PARAMETER WriteAccess + Indicates that the registry key should be retrieved with write access. +#> +function Get-RegistryKey +{ + [CmdletBinding()] + [OutputType([Microsoft.Win32.RegistryKey])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath, + + [switch] + $WriteAccess + ) + + $driveName = Split-Path -Path $KeyPath -Qualifier + Write-Verbose -Message "Get-RegistryKey - Drive name: $driveName" + + $subKey = Split-Path -Path $KeyPath -NoQualifier + Write-Verbose -Message "Get-RegistryKey - Subkey: $subKey" + + $drive = Get-Item -LiteralPath "${driveName}:\" + Write-Verbose -Message "Get-RegistryKey - Drive: $drive" + + return $drive.OpenSubKey($subKey, $WriteAccess) +} + +<# + .SYNOPSIS + Tests if a registry key exists. + + .PARAMETER KeyPath + The path to the registry key to test for existence. + Must include the registry hive. +#> +function Test-RegistryKeyExists +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath + ) + + return Test-Path -Path $KeyPath +} + +<# + .SYNOPSIS + Tests if a registry key value exists. + + .PARAMETER KeyPath + The path to the registry key that should contain the value to test for existence. + Must include the registry hive. + + .PARAMETER ValueName + The name of the value to test for existence. + + .PARAMETER ValueData + The data the existing value should contain. + + .PARAMETER ValueType + The value type that the registry value should have. +#> +function Test-RegistryValueExists +{ + [CmdletBinding()] + [OutputType([Boolean])] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [AllowEmptyString()] + [String] + $ValueName, + + [String] + $ValueData, + + [ValidateNotNullOrEmpty()] + [String] + $ValueType + ) + + try + { + $registryValue = Get-ItemProperty -Path $KeyPath -Name $ValueName -ErrorAction 'SilentlyContinue' + Write-Verbose -Message "Test-RegistryValueExists - Registry key value: $registryKeyValue" + + $registryValueExists = $null -ne $registryValue + + Write-Verbose -Message "Test-RegistryValueExists - Registry value is not null: $registryValueExists" + + if ($registryValueExists) + { + $registryValue = $registryValue.$ValueName + } + + if ($registryValueExists -and $PSBoundParameters.ContainsKey('ValueType')) + { + Write-Verbose -Message "Test-RegistryValueExists - Registry value type: $($registryValue.GetType().Name)" + + if ($ValueType -eq 'Binary') + { + $registryValueExists = $registryValueExists -and ($registryValue.GetType().Name -eq 'Byte[]') + $registryValue = Convert-ByteArrayToHexString -Data $registryValue + } + else + { + $registryValueExists = $registryValueExists -and ($registryValue.GetType().Name -eq $ValueType) + } + } + + if ($registryValueExists -and $PSBoundParameters.ContainsKey('ValueData')) + { + Write-Verbose -Message "Test-RegistryValueExists - Registry value data: $registryValue" + + $registryValueExists = $registryValueExists -and ($ValueData -eq $registryValue) + } + + return $registryValueExists + } + catch + { + return $false + } +} + +<# + .SYNOPSIS + Creates a registry key. + + .PARAMETER KeyPath + The path to the registry key to be created. + Must include the registry hive. +#> +function New-RegistryKey +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath + ) + + $parentPath = Split-Path -Path $KeyPath -Parent + + if (-not (Test-Path -Path $parentPath)) + { + New-RegistryKey -KeyPath $parentPath + } + + Write-Verbose -Message "New-RegistryKey - Creating new registry key at: $KeyPath" + + $null = New-Item -Path $KeyPath +} + +<# + .SYNOPSIS + Creates a registry key. + + .PARAMETER KeyPath + The path to the registry key to be created. + Must include the registry hive. + + .PARAMETER ValueName + The name of the value to add + + .PARAMETER ValueData + The data of the value to add. + + .PARAMETER ValueType + The type of the value to add. +#> +function New-RegistryValue +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [AllowEmptyString()] + [String] + $ValueName, + + [Object] + $ValueData, + + [ValidateNotNullOrEmpty()] + [String] + $ValueType + ) + + if (-not (Test-Path -Path $KeyPath)) + { + New-RegistryKey -KeyPath $KeyPath + } + + if ($ValueType -ieq 'Binary') + { + $convertedValueData = @() + + if (($ValueData.Length % 2) -eq 1) + { + $ValueData = '0' + $ValueData + } + + for($index = 0; $index -lt $ValueData.Length - 1; $index += 2) + { + $convertedValueData += [Convert]::ToInt32($ValueData.Substring($index, 2), 16) + } + + $ValueData = [Byte[]] $convertedValueData + + Write-Verbose -Message "New-RegistryValue - Binary data: $ValueData" + } + + $null = New-ItemProperty -Path $KeyPath -Name $ValueName -Value $ValueData -PropertyType $ValueType +} + +<# + .SYNOPSIS + Removes a registry key. + + .PARAMETER KeyPath + The path to the registry key to remove + Must include the registry hive. +#> +function Remove-RegistryKey +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath + ) + + $null = Remove-Item -Path $KeyPath -Recurse -Force +} + +<# + .SYNOPSIS + Removes a registry value. + + .PARAMETER KeyPath + The path to the registry key that contains the value to remove. + Must include the registry hive. + + .PARAMETER ValueName + The name of the value to remove. +#> +function Remove-RegistryValue +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath, + + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [AllowEmptyString()] + [String] + $ValueName + ) + + $null = Remove-ItemProperty -Path $KeyPath -Name $ValueName -Force +} + +<# + .SYNOPSIS + Removes the default registry value of a registry key. + + .PARAMETER KeyPath + The path to the registry key to remove the default value of. +#> +function Remove-DefaultRegistryValue +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath + ) + + $registryDrivePath = Split-Path -Path $KeyPath -Qualifier + $registryDrive = Get-Item -Path $registryDrivePath + + $subKeyPath = Split-Path -Path $KeyPath -NoQualifier + $subKeyPath = $subKeyPath.Substring(1) + + $registryKey = $registryDrive.OpenSubKey($subKeyPath, $true) + $registryKey.DeleteValue('') +} + +<# + .SYNOPSIS + Mounts the registry drive of the given registry key path. + + .PARAMETER KeyPath + The registry key path that contains the registry drive to mount. +#> +function Mount-RegistryDrive +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath + ) + + $driveName = (Split-Path -Path $KeyPath -Qualifier).TrimEnd(':') + Write-Verbose -Message "Mount-RegistryDrive - Drive name: $driveName" + + $registryDriveRootMappings = @{ + 'HKCR' = 'HKEY_CLASSES_ROOT' + 'HKUS' = 'HKEY_USERS' + 'HKCC' = 'HKEY_CURRENT_CONFIG' + 'HKCU' = 'HKEY_CURRENT_USER' + 'HKLM' = 'HKEY_LOCAL_MACHINE' + } + + if ($registryDriveRootMappings.ContainsKey($driveName)) + { + $null = New-PSDrive -Name $driveName -Root $registryDriveRootMappings[$driveName] -PSProvider 'Registry' -Scope 'Script' + } + elseif ($registryDriveRootMappings.ContainsValue($driveName)) + { + $mappingKey = $null + + foreach ($key in $registryDriveRootMappings.Keys) + { + if ($registryDriveRootMappings[$key] -ieq $driveName) + { + $mappingKey = $key + break + } + } + + $null = New-PSDrive @newPSDriveParams -Name $mappingKey -Root $registryDriveRootMappings[$mappingKey] + } + else + { + throw "Mount-RegistryDrive - Invalid registry drive in key path provided: $KeyPath" + } +} + +<# + .SYNOPSIS + Removes a registry drive. + + .PARAMETER KeyPath + The registry key path that contains the registry drive to remove. +#> +function Dismount-RegistryDrive +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath + ) + + $driveName = Split-Path -Path $KeyPath -Qualifier + Write-Verbose -Message "Mount-RegistryDrive - Drive name: $driveName" + + $null = Remove-PSDrive -Name $driveName -PSProvider 'Registry' -Scope 'Script' -Force +} + +<# + .SYNOPSIS + Tests if the registry drive of he given registry key path is mounted. + + .PARAMETER KeyPath + The registry key path that contains the registry drive to test. +#> +function Test-RegistryDriveMounted +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String] + $KeyPath + ) + + $driveName = Split-Path -Path $KeyPath -Qualifier + Write-Verbose -Message "Test-RegistryDriveMounted - Drive name: $driveName" + + $psDriveNames = (Get-PSDrive).Name.ToUpperInvariant() + + return $psDriveNames -icontains $driveName +} + +<# + .SYNOPSIS + Helper function to convert a byte array to its hex string representation + + .PARAMETER Data + Specifies the byte array to be converted. +#> +function Convert-ByteArrayToHexString +{ + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Object] + $Data + ) + + $retString = '' + $Data | ForEach-Object { $retString += ('{0:x2}' -f $_) } + + return $retString +} + +Export-ModuleMember -Function ` + 'Get-RegistryKey', ` + 'Test-RegistryKeyExists', ` + 'Test-RegistryValueExists', ` + 'New-RegistryKey', ` + 'New-RegistryValue', ` + 'Remove-RegistryKey', ` + 'Remove-RegistryValue', ` + 'Remove-DefaultRegistryValue', ` + 'Mount-RegistryDrive', ` + 'Dismount-RegistryDrive', ` + 'Test-RegistryDriveMounted' diff --git a/Tests/Unit/MSFT_xRegistryResource.Tests.ps1 b/Tests/Unit/MSFT_xRegistryResource.Tests.ps1 new file mode 100644 index 000000000..f88862658 --- /dev/null +++ b/Tests/Unit/MSFT_xRegistryResource.Tests.ps1 @@ -0,0 +1,337 @@ +Import-Module -Name "$PSScriptRoot\..\CommonTestHelper.psm1" -Force + +$script:testEnvironment = Enter-DscResourceTestEnvironment ` + -DscResourceModuleName 'xPSDesiredStateConfiguration' ` + -DscResourceName 'MSFT_xRegistryResource' ` + -TestType 'Unit' + +try +{ + InModuleScope 'MSFT_xRegistryResource' { + Describe 'xRegistry Unit Tests' { + BeforeAll { + Import-Module -Name "$PSScriptRoot\MSFT_xRegistryResource.TestHelper.psm1" -Force + + $baseRegistryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\TestKey' + $script:registryKeyPath = $baseRegistryKeyPath + + while (Test-RegistryKeyExists -KeyPath $script:registryKeyPath) + { + $randomNumber = Get-Random + $script:registryKeyPath = $baseRegistryKeyPath + $randomNumber + } + + $script:registryDriveOriginallyMounted = Test-RegistryDriveMounted -KeyPath $script:registryKeyPath + + if (-not $script:registryDriveOriginallyMounted) + { + Mount-RegistryDrive -KeyPath $script:registryKeyPath + } + } + + BeforeEach { + # Remove the test registry key if it already exists + if (Test-RegistryKeyExists -KeyPath $script:registryKeyPath) + { + Remove-RegistryKey -KeyPath $script:registryKeyPath + } + } + + AfterAll { + # Remove the test registry key if it already exists + if (Test-RegistryKeyExists -KeyPath $script:registryKeyPath) + { + Remove-RegistryKey -KeyPath $script:registryKeyPath + } + + if ($script:registryDriveOriginallyMounted) + { + Dismount-RegistryDrive -KeyPath $script:registryKeyPath + } + } + + Context 'Get-TargetResource' { + It 'Should return Present when retrieving a blank value from an existing registry key' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $getTargetResourceResult = Get-TargetResource -Key $registryKeyPath -ValueName '' + $getTargetResourceResult.Ensure | Should Be 'Present' + } + + It 'Should return Absent when retrieving a blank value from a registry key that does not exist' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environmental' + $getTargetResourceResult = Get-TargetResource -Key $registryKeyPath -ValueName '' + $getTargetResourceResult.Ensure | Should Be 'Absent' + } + + It 'Should return Present when retrieving an existing value from an existing registry key' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $registryValueName = 'Path' + $getTargetResourceResult = Get-TargetResource -Key $registryKeyPath -ValueName $registryValueName + $getTargetResourceResult.Ensure | Should Be 'Present' + } + + It 'Should return Absent when retrieving a nonexistant value from an existing registry key' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $registryValueName = 'PsychoPath' + $getTargetResourceResult = Get-TargetResource -Key $registryKeyPath -ValueName $registryValueName + $getTargetResourceResult.Ensure | Should Be 'Absent' + } + + $commonRegistryKeys = @( 'HKEY_CURRENT_USER', 'HKEY_CLASSES_ROOT', 'HKEY_USERS', 'HKEY_CURRENT_CONFIG' ) + foreach ($commonRegistryKey in $commonRegistryKeys) + { + It "Should return Present when retrieving a blank value from $commonRegistryKey" { + $getTargetResourceResult = Get-TargetResource -Key $commonRegistryKey -ValueName '' + $getTargetResourceResult.Ensure | Should Be 'Present' + } + } + } + + Context 'Set-TargetResource' { + It 'Should create a new registry key' { + Set-TargetResource -Key $script:registryKeyPath -ValueName '' + + # Verify that the registry key has been created + $registryKeyExists = Test-RegistryKeyExists -KeyPath $script:registryKeyPath + $registryKeyExists | Should Be $true + } + + It 'Should create a new registry key tree' { + $registryKeyTreePath = Join-Path -Path (Join-Path -Path (Join-Path -Path $script:registryKeyPath -ChildPath 'A') -ChildPath 'B') -ChildPath 'C' + + Set-TargetResource -Key $registryKeyTreePath -ValueName '' + + # Verify that the registry key has been created + $registryKeyExists = Test-RegistryKeyExists -KeyPath $registryKeyTreePath + $registryKeyExists | Should Be $true + } + + It 'Should remove a registry key' { + # Create the test registry key + New-RegistryKey -KeyPath $script:registryKeyPath + + # Verify that the registry key exists before removal + $registryKeyExists = Test-RegistryKeyExists -KeyPath $script:registryKeyPath + $registryKeyExists | Should Be $true + + # Now remove the TestKey + Set-TargetResource -Key $script:registryKeyPath -ValueName '' -Ensure 'Absent' + + # Verify that the registry key has been removed + $registryKeyExists = Test-RegistryKeyExists -KeyPath $script:registryKeyPath + $registryKeyExists | Should Be $false + } + + It 'Should remove a registry key tree' { + $registryKeyTreePath = Join-Path -Path (Join-Path -Path (Join-Path -Path $script:registryKeyPath -ChildPath 'A') -ChildPath 'B') -ChildPath 'C' + + # Create the test registry key + New-RegistryKey -KeyPath $registryKeyTreePath + + # Verify that the registry key tree exists before removal + $registryKeyExists = Test-RegistryKeyExists -KeyPath $registryKeyTreePath + $registryKeyExists | Should Be $true + + # Remove the test registry key tree + Set-TargetResource -Key $registryKeyTreePath -ValueName '' -Ensure 'Absent' + + # Verify that the registry key tree has been removed + $registryKeyExists = Test-RegistryKeyExists -KeyPath $registryKeyTreePath + $registryKeyExists | Should Be $false + } + + It 'Should create a new string registry key value' { + $valueName = 'TestValue' + $valueData = 'TestData' + $valueType = 'String' + + # Create the new registry key value + Set-TargetResource -Key $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + + # Verify that the registry key value has been created with the correct data and type + $registryValueExists = Test-RegistryValueExists -KeyPath $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + $registryValueExists | Should Be $true + } + + It 'Should create a new binary registry key value' { + $valueName = 'TestValue' + $valueData = 'aabbcc' + $valueType = 'Binary' + + # Create the new registry key value + Set-TargetResource -Key $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + + # Verify that the registry key value has been created with the correct data and type + $registryValueExists = Test-RegistryValueExists -KeyPath $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + $registryValueExists | Should Be $true + } + + It 'Should set the default value of a registry key' { + $valueName = '' + $valueData = 'DefaultValue' + + # Create the new registry key value + Set-TargetResource -Key $script:registryKeyPath -ValueName $valueName -ValueData $valueData + + # Verify that the registry key value has been created with the correct data and type + $registryValueExists = Test-RegistryValueExists -KeyPath $script:registryKeyPath -ValueName '(default)' -ValueData $valueData -ValueType 'String' + $registryValueExists | Should Be $true + } + + It 'Should remove a registry key value' { + $valueName = 'TestValue' + $valueData = 'TestData' + $valueType = 'String' + + # Create the test registry value + New-RegistryValue -KeyPath $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + + # Verify that the registry key value exists before removal + $registryValueExists = Test-RegistryValueExists -KeyPath $script:registryKeyPath -ValueName $valueName + $registryValueExists | Should Be $true + + # Remove the registry value + Set-TargetResource -Key $script:registryKeyPath -ValueName $valueName -Ensure 'Absent' + + # Verify that the registry key value has been removed + $registryValueExists = Test-RegistryValueExists -KeyPath $script:registryKeyPath -ValueName $valueName + $registryValueExists | Should Be $false + } + + It 'Should remove the default value for a registry key' { + $valueName = '' + $valueData = 'DefaultValue' + $valueType = 'String' + + # Create the test registry value + New-RegistryValue -KeyPath $script:registryKeyPath -ValueName '(default)' -ValueData $valueData -ValueType $valueType + + # Verify that the registry key value exists before removal + $registryValueExists = Test-RegistryValueExists -KeyPath $script:registryKeyPath -ValueName '(default)' + $registryValueExists | Should Be $true + + # Remove the registry value + Set-TargetResource -Key $script:registryKeyPath -ValueName $valueName -Ensure 'Absent' + + # Verify that the registry key value has been removed + $registryValueExists = Test-RegistryValueExists -KeyPath $script:registryKeyPath -ValueName '(default)' + $registryValueExists | Should Be $false + } + + It 'Should create a new key and value with path containing forward slashes' { + $registryKeyPathWithForwardSlashes = $script:registryKeyPath + '/Test/Key' + $valueName = 'Testing' + $valueData = 'TestValue' + + # Create the new registry key value + Set-TargetResource -Key $registryKeyPathWithForwardSlashes -ValueName $valueName -ValueData $valueData + + # Verify that the registry key value has been created with the correct data and type + $registryValueExists = Test-RegistryValueExists -KeyPath $registryKeyPathWithForwardSlashes -ValueName $valueName -ValueData $valueData + $registryValueExists | Should Be $true + } + } + + Context 'Test-TargetResource' { + It 'Should return true for an existing registry key' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName '' + $testTargetResourceResult | Should Be $true + } + + It 'Should return false for a registry key that does not exist' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environmentally' + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName '' + $testTargetResourceResult | Should Be $false + } + + It 'Should return true for an existing registry value' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $valueName = 'path' + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName $valueName + $testTargetResourceResult | Should Be $true + } + + It 'Should return true for a registry value that does not exist' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $valueName = 'NonExisting' + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName $valueName + $testTargetResourceResult | Should Be $false + } + + It 'Should return true when Ensure is Absent and registry key does not exist' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environmentally' + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName '' -Ensure 'Absent' + $testTargetResourceResult | Should Be $true + } + + It 'Should return false when Ensure is Absent and registry key exists' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName '' -Ensure 'Absent' + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when Ensure is Absent and registry value exists with invalid data' { + $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' + $valueName = 'path' + $valueData = 'FakePath' + + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName $valueName -ValueData $valueData -Ensure 'Absent' + $testTargetResourceResult | Should Be $false + } + + It 'Should return true for a multi-string registry value' { + $valueName = 'TestValue' + $valueData = @('a', 'b', 'c') + $valueType = 'MultiString' + + # Create the test registry value + New-RegistryValue -KeyPath $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + + $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName $valueName -ValueData $valueData + $testTargetResourceResult | Should Be $true + } + + It 'Should return true for a binary registry value' { + $valueName = 'TestValue' + $valueData = 'abcd123' + $valueType = 'Binary' + + # Create the test registry value + New-RegistryValue -KeyPath $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + + $testTargetResourceResult = Test-TargetResource -Key $script:registryKeyPath -ValueName $valueName -ValueData $valueData + $testTargetResourceResult | Should Be $true + } + + It 'Should return true for an empty binary registry value' { + $valueName = 'TestValue' + $valueData = '' + $valueType = 'Binary' + + # Create the test registry value + New-RegistryValue -KeyPath $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + + $testTargetResourceResult = Test-TargetResource -Key $script:registryKeyPath -ValueName $valueName -ValueData $valueData + $testTargetResourceResult | Should Be $true + } + + It 'Should return true for binary registry value with zeroes' { + $valueName = 'TestValue' + $valueData = 'abcd0123' + $valueType = 'Binary' + + # Create the test registry value + New-RegistryValue -KeyPath $script:registryKeyPath -ValueName $valueName -ValueData $valueData -ValueType $valueType + + $testTargetResourceResult = Test-TargetResource -Key $script:registryKeyPath -ValueName $valueName -ValueData $valueData + $testTargetResourceResult | Should Be $true + } + } + } + } +} +finally +{ + Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment +} From fd4895f7c9042dec0735102ce3bf9b5e4dc07ebc Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Fri, 9 Sep 2016 18:13:00 -0700 Subject: [PATCH 3/6] Update HQRM plan and README --- HighQualityResourceModulePlan.md | 10 ++++++---- README.md | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/HighQualityResourceModulePlan.md b/HighQualityResourceModulePlan.md index 673bebefb..dffc1b448 100644 --- a/HighQualityResourceModulePlan.md +++ b/HighQualityResourceModulePlan.md @@ -26,6 +26,7 @@ The PSDesiredStateConfiguration High Quality Resource Module will consist of the - WindowsOptionalFeatureSet ## Progress + - [x] [1. Port In-Box Only Resources](#port-in-box-only-resources) - [x] Environment - [x] GroupSet @@ -41,13 +42,14 @@ The PSDesiredStateConfiguration High Quality Resource Module will consist of the - [x] Group - [x] Package - [X] Process - - [ ] Registry (In Progress) + - [x] Registry - [x] Service - - [ ] WindowsOptionalFeature -- [ ] [3. Resolve Nano Server vs. Full Server Resources](#resolve-nano-server-vs-full-server-resources) + - [ ] WindowsOptionalFeature (In Progress) +- [x] [3. Resolve Nano Server vs. Full Server Resources](#resolve-nano-server-vs-full-server-resources) + The general consensus is to leave the if-statements for now. - [ ] [4. Update the Resource Module to a High Quality Resource Module](#update-the-resource-module-to-a-high-quality-resource-module) - [ ] 1. Fix PSSA issues per the [DSC Resource Kit PSSA Rule Severity List](https://github.com/PowerShell/DscResources/blob/master/PSSARuleSeverities.md). - - [ ] 2. Ensure unit tests are present for each resource with more than 70% code coverage. + - [ ] 2. Ensure unit tests are present for each resource with more than 70% code coverage. (In Progress) - [ ] 3. Ensure examples run correctly, work as expected, and are documented clearly. - [ ] 4. Ensure clear documentation is provided. - [ ] 5. Ensure the PSDesiredStateConfiguration module follows the standard DSC Resource Kit module format. diff --git a/README.md b/README.md index 9a161c750..4f32589fb 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **xGroup** provides a mechanism to manage local groups on the target node. * **xFileUpload** is a composite resource which ensures that local files exist on an SMB share. * **xWindowsOptionalFeature** configures optional Windows features. -* **xRegistry** is a copy of the built-in Registry resource, with some small bug fixes. +* **xRegistry** provides a mechanism to manage registry keys and values on a target node. * **xEnvironment** configures and manages environment variables. * **xWindowsFeature** provides a mechanism to ensure that roles and features are added or removed on a target node. * **xScript** provides a mechanism to run Windows PowerShell script blocks on target nodes. @@ -166,8 +166,15 @@ Domain members may be specified using domain\name or User Principal Name (UPN) f ### xRegistry -This is a copy of the built-in Registry resource from the PSDesiredStateConfiguration module, with one small change: it now supports -registry keys whose names contain forward slashes. +xRegistry provides a mechanism to manage registry keys and values on a target node. + +* **[String] Key** _(Key)_: Indicates the path of the registry key for which you want to ensure a specific state. This path must include the hive. +* **[String] ValueName** _(Key)_: Indicates the name of the registry value. +* **[String] Ensure** _(Write)_: Indicates if the key and value exist. To ensure that they do, set this property to "Present". To ensure that they do not exist, set the property to "Absent". The default value is "Present". { *Present* | Absent }. +* **[String] ValueData** _(Write)_: The data for the registry value. +* **[String] ValueType** _(Write)_: Indicates the type of the value. { String | Binary | DWord | QWord | Multi-string | Expandable string } +* **[Boolean] Hex** _(Write)_: Indicates if data will be expressed in hexadecimal format. If specified, the DWORD/QWORD value data is presented in hexadecimal format. Not valid for other types. The default value is $false. +* **[Boolean] Force** _(Write)_: If the specified registry key is present, Force overwrites it with the new value. ### xWindowsOptionalFeature Note: _the xWindowsOptionalFeature is only supported on Windows client or Windows Server 2012 (and later) SKUs._ @@ -348,6 +355,9 @@ These parameters will be the same for each Windows optional feature in the set. * Fixed PSSA and style issues * Renamed internal functions to follow verb-noun format * Decorated all functions with comment-based help + * Merged with in-box Registry + * Fixed registry key and value removal + * Added unit tests * xWindowsOptionalFeature: * Cleaned up resource (PSSA issues, formatting, etc.) * Added example script From 31b659cca93a677ca831164800fa2c43fc4874ce Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Fri, 9 Sep 2016 18:25:05 -0700 Subject: [PATCH 4/6] Fix ValidateSet for ValueType in Get-TargetResource --- DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 | 2 +- .../MSFT_xRegistryResource/MSFT_xRegistryResource.schema.mof | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 index 4c569f825..f7c06c378 100644 --- a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 +++ b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 @@ -204,7 +204,7 @@ function Get-TargetResource Special-case: Used only as a boolean flag (along with ValueData) to determine if the target entity is the Default Value or the key itself. #> - [ValidateSet('String', 'Binary', 'Dword', 'Qword', 'MultiString', 'ExpandString')] + [ValidateSet('String', 'Binary', 'DWord', 'QWord', 'MultiString', 'ExpandString')] [System.String] $ValueType ) diff --git a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.schema.mof b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.schema.mof index 4ebb5b501..2e50a75d9 100644 --- a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.schema.mof +++ b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.schema.mof @@ -4,7 +4,7 @@ class MSFT_xRegistryResource : OMI_BaseResource [Key] string Key; [Key] string ValueName; [Write] string ValueData[]; - [Write,ValueMap{"String", "Binary", "Dword", "Qword", "MultiString", "ExpandString"},Values{"String", "Binary", "Dword", "Qword", "MultiString", "ExpandString"}] string ValueType; + [Write,ValueMap{"String", "Binary", "DWord", "QWord", "MultiString", "ExpandString"},Values{"String", "Binary", "DWord", "QWord", "MultiString", "ExpandString"}] string ValueType; [Write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure; [Write] boolean Hex; [Write] boolean Force; From 8397e2d6046039cd3e65ce7b0575360de6a7dbf2 Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Mon, 12 Sep 2016 13:08:36 -0700 Subject: [PATCH 5/6] Fix tests per StrictMode output and fix ValueType values in README --- .../MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 | 3 ++- README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 index f7c06c378..9a4d0482a 100644 --- a/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 +++ b/DSCResources/MSFT_xRegistryResource/MSFT_xRegistryResource.psm1 @@ -226,7 +226,8 @@ function Get-TargetResource if ($retVal.Ensure -eq 'Present') { - [System.String[]]$retVal.ValueData += $retVal.Data + $retVal.ValueData = [System.String[]]@() + $retVal.ValueData += $retVal.Data if ($retVal.ValueType -ieq 'MultiString') { diff --git a/README.md b/README.md index 4f32589fb..a2cef207d 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ xRegistry provides a mechanism to manage registry keys and values on a target no * **[String] ValueName** _(Key)_: Indicates the name of the registry value. * **[String] Ensure** _(Write)_: Indicates if the key and value exist. To ensure that they do, set this property to "Present". To ensure that they do not exist, set the property to "Absent". The default value is "Present". { *Present* | Absent }. * **[String] ValueData** _(Write)_: The data for the registry value. -* **[String] ValueType** _(Write)_: Indicates the type of the value. { String | Binary | DWord | QWord | Multi-string | Expandable string } +* **[String] ValueType** _(Write)_: Indicates the type of the value. { String | Binary | DWord | QWord | MultiString | ExpandString } * **[Boolean] Hex** _(Write)_: Indicates if data will be expressed in hexadecimal format. If specified, the DWORD/QWORD value data is presented in hexadecimal format. Not valid for other types. The default value is $false. * **[Boolean] Force** _(Write)_: If the specified registry key is present, Force overwrites it with the new value. From bbbc0b402b9f73f1dc6c21a6397e54f241baedec Mon Sep 17 00:00:00 2001 From: Katie Keim Date: Mon, 12 Sep 2016 15:19:22 -0700 Subject: [PATCH 6/6] Add timeout to tests and fix small wording errors --- .../MSFT_xRegistryResource.TestHelper.psm1 | 13 +++++++----- Tests/Unit/MSFT_xRegistryResource.Tests.ps1 | 20 ++++++++++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 b/Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 index 4ef210a3d..c28a3ab14 100644 --- a/Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 +++ b/Tests/Unit/MSFT_xRegistryResource.TestHelper.psm1 @@ -355,12 +355,14 @@ function Mount-RegistryDrive if ($registryDriveRootMappings.ContainsKey($driveName)) { + # Abbreviated name was given. Use this as the new PSDrive name and the elongated name as the root $null = New-PSDrive -Name $driveName -Root $registryDriveRootMappings[$driveName] -PSProvider 'Registry' -Scope 'Script' } elseif ($registryDriveRootMappings.ContainsValue($driveName)) { $mappingKey = $null + # Find the abbreviated key that goes with the given registry drive path foreach ($key in $registryDriveRootMappings.Keys) { if ($registryDriveRootMappings[$key] -ieq $driveName) @@ -370,7 +372,8 @@ function Mount-RegistryDrive } } - $null = New-PSDrive @newPSDriveParams -Name $mappingKey -Root $registryDriveRootMappings[$mappingKey] + # Mount the PSDrive with the abbreviated name as the Name and the elongated name as the root + $null = New-PSDrive @newPSDriveParams -Name $mappingKey -Root $registryDriveRootMappings[$mappingKey] -PSProvider 'Registry' -Scope 'Script' } else { @@ -397,7 +400,7 @@ function Dismount-RegistryDrive ) $driveName = Split-Path -Path $KeyPath -Qualifier - Write-Verbose -Message "Mount-RegistryDrive - Drive name: $driveName" + Write-Verbose -Message "Dismount-RegistryDrive - Drive name: $driveName" $null = Remove-PSDrive -Name $driveName -PSProvider 'Registry' -Scope 'Script' -Force } @@ -446,10 +449,10 @@ function Convert-ByteArrayToHexString $Data ) - $retString = '' - $Data | ForEach-Object { $retString += ('{0:x2}' -f $_) } + $hexString = '' + $Data | ForEach-Object { $hexString += ('{0:x2}' -f $_) } - return $retString + return $hexString } Export-ModuleMember -Function ` diff --git a/Tests/Unit/MSFT_xRegistryResource.Tests.ps1 b/Tests/Unit/MSFT_xRegistryResource.Tests.ps1 index f88862658..ff46e50f3 100644 --- a/Tests/Unit/MSFT_xRegistryResource.Tests.ps1 +++ b/Tests/Unit/MSFT_xRegistryResource.Tests.ps1 @@ -15,10 +15,24 @@ try $baseRegistryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\TestKey' $script:registryKeyPath = $baseRegistryKeyPath - while (Test-RegistryKeyExists -KeyPath $script:registryKeyPath) + $script:doNotDeleteRegistryKey = $false + $script:registryDriveOriginallyMounted = $true + + $loopTimeoutMinutes = 1 + + $startLoopTime = Get-Date + while ((Test-RegistryKeyExists -KeyPath $script:registryKeyPath) -and $loopMinutes -lt $loopTimeoutMinutes) { $randomNumber = Get-Random $script:registryKeyPath = $baseRegistryKeyPath + $randomNumber + $loopMinutes = ((Get-Date) - $startLoopTime).Minutes + } + + if (Test-RegistryKeyExists -KeyPath $script:registryKeyPath) + { + $script:doNotDeleteRegistryKey = $true + throw "Timed out while attempting to set up a non-destructive registry key for testing. Last testing key attempted: $script:registryKeyPath" + return } $script:registryDriveOriginallyMounted = Test-RegistryDriveMounted -KeyPath $script:registryKeyPath @@ -39,7 +53,7 @@ try AfterAll { # Remove the test registry key if it already exists - if (Test-RegistryKeyExists -KeyPath $script:registryKeyPath) + if ((Test-RegistryKeyExists -KeyPath $script:registryKeyPath) -and -not $script:doNotDeleteRegistryKey) { Remove-RegistryKey -KeyPath $script:registryKeyPath } @@ -252,7 +266,7 @@ try $testTargetResourceResult | Should Be $true } - It 'Should return true for a registry value that does not exist' { + It 'Should return false for a registry value that does not exist' { $registryKeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' $valueName = 'NonExisting' $testTargetResourceResult = Test-TargetResource -Key $registryKeyPath -ValueName $valueName