From cfb7b12f8215e84fd560e383809ecdb2a8b91840 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Tue, 24 Jan 2017 14:58:40 +0100 Subject: [PATCH 1/4] win_regedit: Cleanup, check/diff mode support, HKCC fix The following changes have been made: - Added check-mode support - Added diff support - Corrected HCCC to HKCC (according to windows documentation) - Updated documentation, and examples - Added -aliases support to module_utils/powershell.ps1 - Renamed `key > value > data` to `path > name > data` - Re-indented code and consistency changes - Added support for expandstring type This fixes #20595 --- lib/ansible/module_utils/powershell.ps1 | 67 ++-- lib/ansible/modules/windows/win_regedit.ps1 | 378 ++++++++++++-------- lib/ansible/modules/windows/win_regedit.py | 104 +++--- 3 files changed, 312 insertions(+), 237 deletions(-) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 16cc726acd0..e22fad9d0fa 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -110,57 +110,60 @@ Function Expand-Environment($value) #Get-AnsibleParam also supports Parameter validation to save you from coding that manually: #Example: Get-AnsibleParam -obj $params -name "State" -default "Present" -ValidateSet "Present","Absent" -resultobj $resultobj -failifempty $true #Note that if you use the failifempty option, you do need to specify resultobject as well. -Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj, $failifempty=$false, $emptyattributefailmessage, $ValidateSet, $ValidateSetErrorMessage, $type=$null) +Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj = @{}, $failifempty = $false, $emptyattributefailmessage, $ValidateSet, $ValidateSetErrorMessage, $type = $null, $aliases = @()) { - # Check if the provided Member $name exists in $obj and return it or the default. - Try - { - If (-not $obj.$name.GetType) - { + # Check if the provided Member $name or aliases exist in $obj and return it or the default. + try { + + $found = $null + # First try to find preferred parameter $name + $aliases = @($name) + $aliases + + # Iterate over aliases to find acceptable Member $name + foreach ($alias in $aliases) { + if (Get-Member -InputObject $obj -Name $alias) { + $found = $alias + break + } + } + + if ($found -eq $null) { throw } + $name = $found - if ($ValidateSet) - { - if ($ValidateSet -contains ($obj.$name)) - { + if ($ValidateSet) { + + if ($ValidateSet -contains ($obj.$name)) { $value = $obj.$name - } - Else - { - if ($ValidateSetErrorMessage -eq $null) - { + } else { + if ($ValidateSetErrorMessage -eq $null) { #Auto-generated error should be sufficient in most use cases $ValidateSetErrorMessage = "Argument $name needs to be one of $($ValidateSet -join ",") but was $($obj.$name)." } Fail-Json -obj $resultobj -message $ValidateSetErrorMessage } - } - Else - { + + } else { $value = $obj.$name } - } - Catch - { - If ($failifempty -eq $false) - { + + } catch { + if ($failifempty -eq $false) { $value = $default - } - Else - { - If (!$emptyattributefailmessage) - { + } else { + if (!$emptyattributefailmessage) { $emptyattributefailmessage = "Missing required argument: $name" } Fail-Json -obj $resultobj -message $emptyattributefailmessage } + } - If ($value -ne $null -and $type -eq "path") { + if ($value -ne $null -and $type -eq "path") { # Expand environment variables on path-type (Beware: turns $null into "") $value = Expand-Environment($value) - } ElseIf ($type -eq "bool") { + } elseif ($type -eq "bool") { # Convert boolean types to real Powershell booleans $value = $value | ConvertTo-Bool } @@ -221,7 +224,7 @@ Function Parse-Args($arguments, $supports_check_mode = $false) $parameters } -# Helper function to calculate a hash of a file in a way which powershell 3 +# Helper function to calculate a hash of a file in a way which powershell 3 # and above can handle: Function Get-FileChecksum($path) { @@ -255,7 +258,7 @@ Function Get-PendingRebootStatus { return $True } - else + else { return $False } diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1 index 78497891b9f..f82c69f83d6 100644 --- a/lib/ansible/modules/windows/win_regedit.ps1 +++ b/lib/ansible/modules/windows/win_regedit.ps1 @@ -16,221 +16,283 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . -$ErrorActionPreference = "Stop" - # WANT_JSON # POWERSHELL_COMMON -New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR -ErrorAction SilentlyContinue | Out-Null -New-PSDrive -PSProvider registry -Root HKEY_USERS -Name HKU -ErrorAction SilentlyContinue | Out-Null -New-PSDrive -PSProvider registry -Root HKEY_CURRENT_CONFIG -Name HCCC -ErrorAction SilentlyContinue | Out-Null +# TODO: Add missing REG_NONE support + +$ErrorActionPreference = "Stop" + +$params = Parse-Args $args -supports_check_mode $true +$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false -$params = Parse-Args $args; -$result = New-Object PSObject; -Set-Attr $result "changed" $false; -Set-Attr $result "data_changed" $false; -Set-Attr $result "data_type_changed" $false; +$path = Get-AnsibleParam -obj $params -name "path" -type "string" -failifempty $true -aliases "key" +$name = Get-AnsibleParam -obj $params -name "name" -type "string" -aliases "entry","value" +$data = Get-AnsibleParam -obj $params -name "data" +$type = Get-AnsibleParam -obj $params -name "type" -type "string" -validateSet "binary","dword","expandstring","multistring","string","qword" -aliases "datatype" -default "string" +$state = Get-AnsibleParam -obj $params -name "state" -type "string" -validateSet "present","absent" -default "present" -$registryKey = Get-Attr -obj $params -name "key" -failifempty $true -$registryValue = Get-Attr -obj $params -name "value" -default $null -$state = Get-Attr -obj $params -name "state" -validateSet "present","absent" -default "present" -$registryData = Get-Attr -obj $params -name "data" -default $null -$registryDataType = Get-Attr -obj $params -name "datatype" -validateSet "binary","dword","expandstring","multistring","string","qword" -default "string" +$result = @{ + changed = $false + data_changed = $false + data_type_changed = $false + diff = @{ + prepared = "" + } + warnings = @() +} -If ($state -eq "present" -and $registryData -eq $null -and $registryValue -ne $null) -{ +if ($state -eq "present" -and $data -eq $null -and $name -ne $null) { Fail-Json $result "missing required argument: data" } -# check the registry key is in powershell ps-drive format: HKLM, HKCU, HKU, HKCR, HCCC -If (-not ($registryKey -match "^H[KC][CLU][MURC]{0,1}:\\")) -{ - Fail-Json $result "key: $registryKey is not a valid powershell path, see module documentation for examples." +# Fix HCCC:\ PSDrive for pre-2.3 compatibility +if ($path -match "^HCCC:\\") { + $result.warnings += "Please use path: HKCC:\... instead of path: $path\n" + $path = $path -replace "HCCC:\\","HKCC:\\" } +# Check that the registry path is in PSDrive format: HKCC, HKCR, HKCU, HKLM, HKU +if (-not ($path -match "^HK(CC|CR|CU|LM|U):\\")) { + Fail-Json $result "path: $path is not a valid powershell path, see module documentation for examples." +} -Function Test-RegistryValueData { +# Allow empty values as the "(default)" value +if ($name -eq "") { + $registryValue = "(default)" +} + +Function Test-ValueData { Param ( - [parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()]$Path, - [parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()]$Value + [parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path, + [parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Name ) - Try { - Get-ItemProperty -Path $Path -Name $Value - Return $true - } - Catch { - Return $false + + try { + Get-ItemProperty -Path $Path -Name $Name + return $true + } catch { + return $false } } # Returns true if registry data matches. # Handles binary, integer(dword) and string registry data -Function Compare-RegistryData { +Function Compare-Data { Param ( - [parameter(Mandatory=$true)] - [AllowEmptyString()]$ReferenceData, - [parameter(Mandatory=$true)] - [AllowEmptyString()]$DifferenceData - ) - - if ($ReferenceData -is [String] -or $ReferenceData -is [int]) { - if ($ReferenceData -eq $DifferenceData) { - return $true - } else { - return $false - } - } elseif ($ReferenceData -is [Object[]]) { - if (@(Compare-Object $ReferenceData $DifferenceData -SyncWindow 0).Length -eq 0) { - return $true - } else { - return $false - } + [parameter(Mandatory=$true)] [AllowEmptyString()] $ReferenceData, + [parameter(Mandatory=$true)] [AllowEmptyString()] $DifferenceData + ) + + if ($ReferenceData -is [String] -or $ReferenceData -is [int]) { + if ($ReferenceData -eq $DifferenceData) { + return $true + } else { + return $false } + } elseif ($ReferenceData -is [Object[]]) { + if (@(Compare-Object $ReferenceData $DifferenceData -SyncWindow 0).Length -eq 0) { + return $true + } else { + return $false + } + } } # Simplified version of Convert-HexStringToByteArray from # https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert # Expects a hex in the format you get when you run reg.exe export, # and converts to a byte array so powershell can modify binary registry entries -function Convert-RegExportHexStringToByteArray -{ +function Convert-RegExportHexStringToByteArray { Param ( - [parameter(Mandatory=$true)] [String] $String + [parameter(Mandatory=$true)] [String] $String ) -# remove 'hex:' from the front of the string if present -$String = $String.ToLower() -replace '^hex\:', '' + # Remove 'hex:' from the front of the string if present + $String = $String.ToLower() -replace '^hex\:','' -#remove whitespace and any other non-hex crud. -$String = $String.ToLower() -replace '[^a-f0-9\\,x\-\:]','' + # Remove whitespace and any other non-hex crud. + $String = $String.ToLower() -replace '[^a-f0-9\\,x\-\:]','' -# turn commas into colons -$String = $String -replace ',',':' + # Turn commas into colons + $String = $String -replace ',',':' -#Maybe there's nothing left over to convert... -if ($String.Length -eq 0) { ,@() ; return } + # Maybe there's nothing left over to convert... + if ($String.Length -eq 0) { + return ,@() + } -#Split string with or without colon delimiters. -if ($String.Length -eq 1) -{ ,@([System.Convert]::ToByte($String,16)) } -elseif (($String.Length % 2 -eq 0) -and ($String.IndexOf(":") -eq -1)) -{ ,@($String -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}) } -elseif ($String.IndexOf(":") -ne -1) -{ ,@($String -split ':+' | foreach-object {[System.Convert]::ToByte($_,16)}) } -else -{ ,@() } + # Split string with or without colon delimiters. + if ($String.Length -eq 1) { + return ,@([System.Convert]::ToByte($String,16)) + } elseif (($String.Length % 2 -eq 0) -and ($String.IndexOf(":") -eq -1)) { + return ,@($String -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}}) + } elseif ($String.IndexOf(":") -ne -1) { + return ,@($String -split ':+' | foreach-object {[System.Convert]::ToByte($_,16)}) + } else { + return ,@() + } +} +# Create the required PSDrives if missing +if (-not (Test-Path HKCR:\)) { + New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT +} +if (-not (Test-Path HKU:\)) { + New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS +} +if (-not (Test-Path HKCC:\)) { + New-PSDrive -Name HKCC -PSProvider Registry -Root HKEY_CURRENT_CONFIG } -if($registryDataType -eq "binary" -and $registryData -ne $null -and $registryData -is [String]) { - $registryData = Convert-RegExportHexStringToByteArray($registryData) +# Convert HEX string to binary if type binary +if ($type -eq "binary" -and $data -ne $null -and $data -is [String]) { + $data = Convert-RegExportHexStringToByteArray($data) } -if($state -eq "present") { - if ((Test-Path $registryKey) -and $registryValue -ne $null) - { - if (Test-RegistryValueData -Path $registryKey -Value $registryValue) - { - # handle binary data - $currentRegistryData =(Get-ItemProperty -Path $registryKey | Select-Object -ExpandProperty $registryValue) - - if ($registryValue.ToLower() -eq "(default)") { - # Special case handling for the key's default property. Because .GetValueKind() doesn't work for the (default) key property - $oldRegistryDataType = "String" - } - else { - $oldRegistryDataType = (Get-Item $registryKey).GetValueKind($registryValue) +# Expand string if type expandstring +if ($type -eq "expandstring" -and $data -ne $null -and $data -is [String]) { + $data = Expand-Environment($data) + $datatype = "string" +} + +if ($state -eq "present") { + + if ((Test-Path $path) -and $name -ne $null) { + + if (Test-ValueData -Path $path -Name $name) { + + # Handle binary data + $old_data =(Get-ItemProperty -Path $path | Select-Object -ExpandProperty $name) + + if ($name.ToLower() -eq "(default)") { + # Special case handling for the path's default property. + # Because .GetValueKind() doesn't work for the (default) path property + $old_type = "String" + } else { + $old_type = (Get-Item $path).GetValueKind($name) } - # Changes Data and DataType - if ($registryDataType -ne $oldRegistryDataType) - { - Try - { - Remove-ItemProperty -Path $registryKey -Name $registryValue - New-ItemProperty -Path $registryKey -Name $registryValue -Value $registryData -PropertyType $registryDataType - $result.changed = $true - $result.data_changed = $true - $result.data_type_changed = $true + if ($type -ne $old_type) { + # Changes Data and DataType + if (-not $check_mode) { + try { + Remove-ItemProperty -Path $path -Name $name + New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type -Force + } catch { + Fail-Json $result $_.Exception.Message + } } - Catch - { - Fail-Json $result $_.Exception.Message + $result.changed = $true + $result.data_changed = $true + $result.data_type_changed = $true + $result.diff.prepared += @" + [$path] +-"$name" = "$old_type`:$data" ++"$name" = "$type`:$data" +"@ + + } elseif (-not (Compare-Data -ReferenceData $old_data -DifferenceData $data)) { + # Changes Only Data + if (-not $check_mode) { + try { + Set-ItemProperty -Path $path -Name $name -Value $data + } catch { + Fail-Json $result $_.Exception.Message + } } + $result.changed = $true + $result.data_changed = $true + $result.diff.prepared += @" + [$path] +-"$name" = "$type`:$old_data" ++"$name" = "$type`:$data" +"@ + + } else { + # Nothing to do, everything is already as requested } - # Changes Only Data - elseif (-Not (Compare-RegistryData -ReferenceData $currentRegistryData -DifferenceData $registryData)) - { - Try { - Set-ItemProperty -Path $registryKey -Name $registryValue -Value $registryData - $result.changed = $true - $result.data_changed = $true - } - Catch - { + + } else { + + if (-not $check_mode) { + try { + New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type + } Catch { Fail-Json $result $_.Exception.Message } } + $result.changed = $true + $result.diff.prepared += @" + [$path] ++"$name" = "$type`:$data" +"@ } - else - { - Try - { - New-ItemProperty -Path $registryKey -Name $registryValue -Value $registryData -PropertyType $registryDataType - $result.changed = $true - } - Catch - { + + } elseif (-not (Test-Path $path)) { + + if (-not $check_mode) { + try { + $new_path = New-Item $path -Type directory -Force + if ($name -ne $null) { + $new_path | New-ItemProperty -Name $name -Value $data -PropertyType $type -Force + } + } catch { + throw Fail-Json $result $_.Exception.Message } } - } - elseif(-not (Test-Path $registryKey)) - { - Try - { - $newRegistryKey = New-Item $registryKey -Force - $result.changed = $true + $result.changed = $true + $result.diff.prepared += @" ++[$path"] - if($registryValue -ne $null) { - $newRegistryKey | New-ItemProperty -Name $registryValue -Value $registryData -Force -PropertyType $registryDataType - $result.changed = $true - } - } - Catch - { - Fail-Json $result $_.Exception.Message +"@ + if ($name -ne $null) { + $result.diff.prepared += @" ++"$name" = "$type`:$data" + +"@ } + + } else { + # FIXME: Value is null, should we silently ignore this and do nothing ? } -} -else -{ - if (Test-Path $registryKey) - { - if ($registryValue -eq $null) { - Try - { - Remove-Item -Path $registryKey -Recurse - $result.changed = $true - } - Catch - { - Fail-Json $result $_.Exception.Message - } - } - elseif (Test-RegistryValueData -Path $registryKey -Value $registryValue) { - Try - { - Remove-ItemProperty -Path $registryKey -Name $registryValue - $result.changed = $true + +} elseif ($state -eq "absent") { + + if (Test-Path $path) { + if ($name -eq $null) { + + if (-not $check_mode) { + try { + Remove-Item -Path $path -Recurse + } catch { + Fail-Json $result $_.Exception.Message + } } - Catch - { - Fail-Json $result $_.Exception.Message + $result.changed = $true + $result.diff.prepared += @" +-[$path] +-"$name" = "$type`:$data" +"@ + + } elseif (Test-ValueData -Path $path -Value $name) { + + if (-not $check_mode) { + try { + Remove-ItemProperty -Path $path -Name $name + } catch { + Fail-Json $result $_.Exception.Message + } } + $result.changed = $true + $result.diff.prepared += @" + [$path] +-"$name" = "$type`:$data" +"@ } + } else { + # Nothing to do, everything is already as requested } } diff --git a/lib/ansible/modules/windows/win_regedit.py b/lib/ansible/modules/windows/win_regedit.py index 9b6919d3047..a32d40aa50d 100644 --- a/lib/ansible/modules/windows/win_regedit.py +++ b/lib/ansible/modules/windows/win_regedit.py @@ -29,32 +29,30 @@ DOCUMENTATION = r''' --- module: win_regedit version_added: "2.0" -short_description: Add, Edit, or Remove Registry Keys and Values +short_description: Add, change, or remove registry keys and values description: - - Add, Edit, or Remove Registry Keys and Values using ItemProperties Cmdlets + - Add, modify or remove registry keys and values. + - More information about the windows registry from Wikipedia (https://en.wikipedia.org/wiki/Windows_Registry). options: - key: + path: description: - - Name of Registry Key + - Name of registry path. + - Should be in one of the following registry hives: HKCC, HKCR, HKCU, HKLM, HKU. required: true - default: null - aliases: [] - value: + aliases: [ key ] + name: description: - - Name of Registry Value - required: true - default: null - aliases: [] + - Name of registry entry in C(path). + - This is an entry in the above C(key) parameter. + - If not provided, or empty we use the default name '(default)' + aliases: [ entry ] data: description: - - Registry Value Data. Binary data should be expressed a yaml byte array or as comma separated hex values. An easy way to generate this is to run C(regedit.exe) and use the I(Export) option to save the registry values to a file. In the exported file binary values will look like C(hex:be,ef,be,ef). The C(hex:) prefix is optional. - required: false - default: null - aliases: [] - datatype: + - Value of the registry entry C(name) in C(path). + - Binary data should be expressed a yaml byte array or as comma separated hex values. An easy way to generate this is to run C(regedit.exe) and use the I(Export) option to save the registry values to a file. In the exported file binary values will look like C(hex:be,ef,be,ef). The C(hex:) prefix is optional. + type: description: - - Registry Value Data Type - required: false + - Registry value data type. choices: - binary - dword @@ -63,65 +61,77 @@ options: - string - qword default: string - aliases: [] + aliases: [ datatype ] state: description: - - State of Registry Value - required: false + - State of registry entry. choices: - present - absent default: present - aliases: [] +notes: +- Check-mode C(-C/--check) and diff output (-D/--diff) are supported, so that you can test every change against the active configuration before applying changes. +- At the moment REG_NONE support is missing because it is lacking from the Powershell API. Workarounds are possible but currently lacking. +- Beware that some registry hives (HKEY_USERS in particular) do not allow to create new registry paths. author: "Adam Keech (@smadam813), Josh Ludwig (@joshludwig)" ''' EXAMPLES = r''' -- name: Create Registry Key called MyCompany +- name: Create registry path MyCompany win_regedit: - key: HKCU:\Software\MyCompany + path: HKCU:\Software\MyCompany -- name: Create Registry Key called MyCompany, a value within MyCompany Key called "hello", and data for the value "hello" containing "world". +- name: Add or update registry path MyCompany, with entry 'hello', and containing 'world' win_regedit: - key: HKCU:\Software\MyCompany - value: hello + path: HKCU:\Software\MyCompany + name: hello data: world -- name: Create Registry Key called MyCompany, a value within MyCompany Key called "hello", and data for the value "hello" containing "1337" as type "dword". +- name: Add or update registry path MyCompany, with entry 'hello', and containing 1337 win_regedit: - key: HKCU:\Software\MyCompany - value: hello + path: HKCU:\Software\MyCompany + name: hello data: 1337 - datatype: dword + type: dword -- name: Create Registry Key called MyCompany, a value within MyCompany Key called "hello", and binary data for the value "hello" as type "binary" data expressed as comma separated list +- name: Add or update registry path MyCompany, with entry 'hello', and containing binary data in hex-string format win_regedit: - key: HKCU:\Software\MyCompany - value: hello + path: HKCU:\Software\MyCompany + name: hello data: hex:be,ef,be,ef,be,ef,be,ef,be,ef - datatype: binary + type: binary -- name: Create Registry Key called MyCompany, a value within MyCompany Key called "hello", and binary data for the value "hello" as type "binary" data expressed as yaml array of bytes +- name: Add or update registry path MyCompany, with entry 'hello', and containing binary data in yaml format win_regedit: - key: HKCU:\Software\MyCompany - value: hello + path: HKCU:\Software\MyCompany + name: hello data: [0xbe,0xef,0xbe,0xef,0xbe,0xef,0xbe,0xef,0xbe,0xef] - datatype: binary + type: binary -- name: Delete Registry Key MyCompany. Not specifying a value will delete the root key which means all values will be deleted +- name: Disable keyboard layout hotkey for all users (changes existing) win_regedit: - key: HKCU:\Software\MyCompany - state: absent + path: HKU:\.DEFAULT\Keyboard Layout\Toggle + name: Layout Hotkey + data: 3 + type: dword -- name: Delete Registry Value "hello" from MyCompany Key +- name: Disable language hotkey for current users (adds new) win_regedit: - key: HKCU:\Software\MyCompany - value: hello + path: HKCU:\Keyboard Layout\Toggle + name: Language Hotkey + data: 3 + type: dword + +- name: Remove registry path MyCompany (including all entries it contains) + win_regedit: + path: HKCU:\Software\MyCompany state: absent -- name: Creates Registry Key called 'My Company' +- name: Remove entry 'hello' from registry path MyCompany win_regedit: - key: HKCU:\Software\My Company + path: HKCU:\Software\MyCompany + name: hello + state: absent ''' RETURN = r''' From 454dde5dfda2495b93172f487726c012041dac51 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Wed, 25 Jan 2017 12:51:42 +0100 Subject: [PATCH 2/4] Fix special case for '(default)' entries in original module --- lib/ansible/modules/windows/win_regedit.ps1 | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1 index f82c69f83d6..cf9e230121a 100644 --- a/lib/ansible/modules/windows/win_regedit.ps1 +++ b/lib/ansible/modules/windows/win_regedit.ps1 @@ -150,10 +150,12 @@ if ($type -eq "binary" -and $data -ne $null -and $data -is [String]) { $data = Convert-RegExportHexStringToByteArray($data) } -# Expand string if type expandstring -if ($type -eq "expandstring" -and $data -ne $null -and $data -is [String]) { - $data = Expand-Environment($data) - $datatype = "string" +# Special case handling for the path's default property. +if ($name.ToLower() -eq "(default)") { + if ($type -eq "expandstring" -and $data -ne $null -and $data -is [String]) { + $data = Expand-Environment($data) + } + $type = "string" } if ($state -eq "present") { @@ -177,8 +179,12 @@ if ($state -eq "present") { # Changes Data and DataType if (-not $check_mode) { try { - Remove-ItemProperty -Path $path -Name $name - New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type -Force + if ($name.ToLower() -eq "(default)") { + $null = $(Get-Item -Path $path -ErrorAction 'Stop').OpenSubKey('','ReadWriteSubTree').SetValue($null,$data) + } else { + Remove-ItemProperty -Path $path -Name $name + New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type -Force + } } catch { Fail-Json $result $_.Exception.Message } From 4c7715a4e53db951f8c492143cafd7e449027ff0 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Wed, 25 Jan 2017 20:40:57 +0100 Subject: [PATCH 3/4] Implemented working REG_NONE support This fixes #20343 --- lib/ansible/modules/windows/win_regedit.ps1 | 49 ++++++++++++--------- lib/ansible/modules/windows/win_regedit.py | 3 +- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1 index cf9e230121a..80db38e12ef 100644 --- a/lib/ansible/modules/windows/win_regedit.ps1 +++ b/lib/ansible/modules/windows/win_regedit.ps1 @@ -19,8 +19,6 @@ # WANT_JSON # POWERSHELL_COMMON -# TODO: Add missing REG_NONE support - $ErrorActionPreference = "Stop" $params = Parse-Args $args -supports_check_mode $true @@ -29,7 +27,7 @@ $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "b $path = Get-AnsibleParam -obj $params -name "path" -type "string" -failifempty $true -aliases "key" $name = Get-AnsibleParam -obj $params -name "name" -type "string" -aliases "entry","value" $data = Get-AnsibleParam -obj $params -name "data" -$type = Get-AnsibleParam -obj $params -name "type" -type "string" -validateSet "binary","dword","expandstring","multistring","string","qword" -aliases "datatype" -default "string" +$type = Get-AnsibleParam -obj $params -name "type" -type "string" -validateSet "none","binary","dword","expandstring","multistring","string","qword" -aliases "datatype" -default "string" $state = Get-AnsibleParam -obj $params -name "state" -type "string" -validateSet "present","absent" -default "present" $result = @{ @@ -42,10 +40,6 @@ $result = @{ warnings = @() } -if ($state -eq "present" -and $data -eq $null -and $name -ne $null) { - Fail-Json $result "missing required argument: data" -} - # Fix HCCC:\ PSDrive for pre-2.3 compatibility if ($path -match "^HCCC:\\") { $result.warnings += "Please use path: HKCC:\... instead of path: $path\n" @@ -59,7 +53,7 @@ if (-not ($path -match "^HK(CC|CR|CU|LM|U):\\")) { # Allow empty values as the "(default)" value if ($name -eq "") { - $registryValue = "(default)" + $name = "(default)" } Function Test-ValueData { @@ -80,11 +74,17 @@ Function Test-ValueData { # Handles binary, integer(dword) and string registry data Function Compare-Data { Param ( - [parameter(Mandatory=$true)] [AllowEmptyString()] $ReferenceData, - [parameter(Mandatory=$true)] [AllowEmptyString()] $DifferenceData + [parameter(Mandatory=$true)] [AllowEmptyString()] [AllowNull()] $ReferenceData, + [parameter(Mandatory=$true)] [AllowEmptyString()] [AllowNull()] $DifferenceData ) - if ($ReferenceData -is [String] -or $ReferenceData -is [int]) { + if ($ReferenceData -eq $null) { + if ($DifferenceData -eq $null) { + return $true + } else { + return $false + } + } elseif ($ReferenceData -is [String] -or $ReferenceData -is [int]) { if ($ReferenceData -eq $DifferenceData) { return $true } else { @@ -158,6 +158,13 @@ if ($name.ToLower() -eq "(default)") { $type = "string" } +# Support REG_NONE with empty value +# FIXME: REG_NONE support is not idempotent +if ($type -eq "none" -or $data -eq $null) { + $data = New-Object byte[] 0 +# $data = ([byte[]] @()) +} + if ($state -eq "present") { if ((Test-Path $path) -and $name -ne $null) { @@ -165,14 +172,14 @@ if ($state -eq "present") { if (Test-ValueData -Path $path -Name $name) { # Handle binary data - $old_data =(Get-ItemProperty -Path $path | Select-Object -ExpandProperty $name) + $old_data = (Get-ItemProperty -Path $path | Select-Object -ExpandProperty $name) if ($name.ToLower() -eq "(default)") { # Special case handling for the path's default property. # Because .GetValueKind() doesn't work for the (default) path property - $old_type = "String" + $old_type = "String".ToLower() } else { - $old_type = (Get-Item $path).GetValueKind($name) + $old_type = (Get-Item $path).GetValueKind($name).ToString().ToLower() } if ($type -ne $old_type) { @@ -197,12 +204,17 @@ if ($state -eq "present") { -"$name" = "$old_type`:$data" +"$name" = "$type`:$data" "@ - + # FIXME: Compare-Data fails to work for null-length byte arrays } elseif (-not (Compare-Data -ReferenceData $old_data -DifferenceData $data)) { # Changes Only Data if (-not $check_mode) { try { - Set-ItemProperty -Path $path -Name $name -Value $data + if ($type -eq "none") { + Remove-ItemProperty -Path $path -Name $name + New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type -Force + } else { + Set-ItemProperty -Path $path -Name $name -Value $data + } } catch { Fail-Json $result $_.Exception.Message } @@ -220,7 +232,7 @@ if ($state -eq "present") { } } else { - + # Add missing entry if (-not $check_mode) { try { New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type @@ -244,7 +256,6 @@ if ($state -eq "present") { $new_path | New-ItemProperty -Name $name -Value $data -PropertyType $type -Force } } catch { - throw Fail-Json $result $_.Exception.Message } } @@ -260,8 +271,6 @@ if ($state -eq "present") { "@ } - } else { - # FIXME: Value is null, should we silently ignore this and do nothing ? } } elseif ($state -eq "absent") { diff --git a/lib/ansible/modules/windows/win_regedit.py b/lib/ansible/modules/windows/win_regedit.py index a32d40aa50d..cf1c472da36 100644 --- a/lib/ansible/modules/windows/win_regedit.py +++ b/lib/ansible/modules/windows/win_regedit.py @@ -37,7 +37,7 @@ options: path: description: - Name of registry path. - - Should be in one of the following registry hives: HKCC, HKCR, HKCU, HKLM, HKU. + - 'Should be in one of the following registry hives: HKCC, HKCR, HKCU, HKLM, HKU.' required: true aliases: [ key ] name: @@ -71,7 +71,6 @@ options: default: present notes: - Check-mode C(-C/--check) and diff output (-D/--diff) are supported, so that you can test every change against the active configuration before applying changes. -- At the moment REG_NONE support is missing because it is lacking from the Powershell API. Workarounds are possible but currently lacking. - Beware that some registry hives (HKEY_USERS in particular) do not allow to create new registry paths. author: "Adam Keech (@smadam813), Josh Ludwig (@joshludwig)" ''' From 0e80ba18678e67dff37636b31f50b63bef3e984b Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Thu, 26 Jan 2017 16:26:56 +0100 Subject: [PATCH 4/4] Fix the integration test to comply recent changes --- lib/ansible/modules/windows/win_regedit.ps1 | 16 +++++++--------- .../targets/win_regedit/tasks/main.yml | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1 index 80db38e12ef..6c9997a436a 100644 --- a/lib/ansible/modules/windows/win_regedit.ps1 +++ b/lib/ansible/modules/windows/win_regedit.ps1 @@ -51,11 +51,6 @@ if (-not ($path -match "^HK(CC|CR|CU|LM|U):\\")) { Fail-Json $result "path: $path is not a valid powershell path, see module documentation for examples." } -# Allow empty values as the "(default)" value -if ($name -eq "") { - $name = "(default)" -} - Function Test-ValueData { Param ( [parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Path, @@ -145,21 +140,24 @@ if (-not (Test-Path HKCC:\)) { New-PSDrive -Name HKCC -PSProvider Registry -Root HKEY_CURRENT_CONFIG } + # Convert HEX string to binary if type binary if ($type -eq "binary" -and $data -ne $null -and $data -is [String]) { $data = Convert-RegExportHexStringToByteArray($data) } # Special case handling for the path's default property. -if ($name.ToLower() -eq "(default)") { +if ($name -eq "" -or ($name -ne $null -and $name.ToLower() -eq "(default)")) { + # Apparently, "(default)" cannot be of type expandstring, do it ourselves if ($type -eq "expandstring" -and $data -ne $null -and $data -is [String]) { $data = Expand-Environment($data) } + $name = "(default)" $type = "string" } # Support REG_NONE with empty value -# FIXME: REG_NONE support is not idempotent +# FIXME: REG_NONE support is not idempotent yet if ($type -eq "none" -or $data -eq $null) { $data = New-Object byte[] 0 # $data = ([byte[]] @()) @@ -174,7 +172,7 @@ if ($state -eq "present") { # Handle binary data $old_data = (Get-ItemProperty -Path $path | Select-Object -ExpandProperty $name) - if ($name.ToLower() -eq "(default)") { + if ($name -eq "(default)") { # Special case handling for the path's default property. # Because .GetValueKind() doesn't work for the (default) path property $old_type = "String".ToLower() @@ -291,7 +289,7 @@ if ($state -eq "present") { -"$name" = "$type`:$data" "@ - } elseif (Test-ValueData -Path $path -Value $name) { + } elseif (Test-ValueData -Path $path -Name $name) { if (-not $check_mode) { try { diff --git a/test/integration/targets/win_regedit/tasks/main.yml b/test/integration/targets/win_regedit/tasks/main.yml index 6948055dd1c..72080e034f5 100644 --- a/test/integration/targets/win_regedit/tasks/main.yml +++ b/test/integration/targets/win_regedit/tasks/main.yml @@ -32,7 +32,7 @@ - assert: that: - "check00_result.failed == true" - - "check00_result.msg == 'key: HKCU\\Software is not a valid powershell path, see module documentation for examples.'" + - "check00_result.msg == 'path: HKCU\\Software is not a valid powershell path, see module documentation for examples.'" - name: remove setting win_regedit: