diff --git a/changelogs/fragments/win_registry.yaml b/changelogs/fragments/win_registry.yaml new file mode 100644 index 00000000000..7db1f3b1112 --- /dev/null +++ b/changelogs/fragments/win_registry.yaml @@ -0,0 +1,5 @@ +bugfixes: +- win_regedit - Fix issue where creating a new key would set the ``(Default)`` key property to an empty string instead of undefined +- win_regedit - Support registry paths with special characters - https://github.com/ansible/ansible/issues/41791 +- win_reg_stat - Support registry paths with special characters - https://github.com/ansible/ansible/issues/41791 +- win_reg_stat - Fix issue where the key's ``(Default)`` property was not being returned if it was set diff --git a/lib/ansible/modules/windows/win_reg_stat.ps1 b/lib/ansible/modules/windows/win_reg_stat.ps1 index 5d517ac37da..aa7e4ba3147 100644 --- a/lib/ansible/modules/windows/win_reg_stat.ps1 +++ b/lib/ansible/modules/windows/win_reg_stat.ps1 @@ -15,122 +15,112 @@ $result = @{ changed = $false } -Function Get-NetHiveName($hive) { - # Will also check that the hive passed in the path is a known hive - switch ($hive.ToUpper()) { - "HKCR" {"ClassesRoot"} - "HKCC" {"CurrentConfig"} - "HKCU" {"CurrentUser"} - "HKLM" {"LocalMachine"} - "HKU" {"Users"} - default {"unsupported"} +Function Get-PropertyValue { + param( + [Parameter(Mandatory=$true)][Microsoft.Win32.RegistryKey]$Key, + [String]$Name + ) + + $value = $Key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::None) + if ($null -eq $value) { + # Property does not exist or the key's (Default) is not set + return $null } -} -Function Get-PropertyType($hive, $path, $property) { - $type = (Get-Item REGISTRY::$hive\$path).GetValueKind($property) - switch ($type) { - "Binary" {"REG_BINARY"} - "String" {"REG_SZ"} - "DWord" {"REG_DWORD"} - "QWord" {"REG_QWORD"} - "MultiString" {"REG_MULTI_SZ"} - "ExpandString" {"REG_EXPAND_SZ"} - "None" {"REG_NONE"} - default {"Unknown"} + $raw_value = $Key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + + if ($Name -eq "") { + # The key's (Default) will fail on GetValueKind + $type = [Microsoft.Win32.RegistryValueKind]::String + } else { + $type = $Key.GetValueKind($Name) } -} -Function Get-PropertyObject($hive, $net_hive, $path, $property) { - $value = (Get-ItemProperty REGISTRY::$hive\$path).$property - $type = Get-PropertyType -hive $hive -path $path -property $property - If ($type -eq 'REG_EXPAND_SZ') { - $raw_value = [Microsoft.Win32.Registry]::$net_hive.OpenSubKey($path).GetValue($property, $false, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) - } ElseIf ($type -eq 'REG_BINARY' -or $type -eq 'REG_NONE') { - $raw_value = @() + if ($type -in @([Microsoft.Win32.RegistryValueKind]::Binary, [Microsoft.Win32.RegistryValueKind]::None)) { + $formatted_raw_value = [System.Collections.Generic.List`1[String]]@() foreach ($byte in $value) { - $hex_value = ('{0:x}' -f $byte).PadLeft(2, '0') - $raw_value += "0x$hex_value" + $formatted_raw_value.Add("0x{0:x2}" -f $byte) } - } Else { + $raw_value = $formatted_raw_value + } elseif ($type -eq [Microsoft.Win32.RegistryValueKind]::DWord) { + # .NET returns the value as a signed integer, we need to make it unsigned + $value = [UInt32]("0x{0:x}" -f $value) + $raw_value = $value + } elseif ($type -eq [Microsoft.Win32.RegistryValueKind]::QWord) { + $value = [UInt64]("0x{0:x}" -f $value) $raw_value = $value } - $object = @{ - raw_value = $raw_value - value = $value - type = $type - } - - $object -} - -Function Test-RegistryProperty($hive, $path, $property) { - Try { - $type = (Get-Item REGISTRY::$hive\$path).GetValueKind($property) - } Catch { - $type = $null + $return_type = switch($type.ToString()) { + "Binary" { "REG_BINARY" } + "String" { "REG_SZ" } + "DWord" { "REG_DWORD" } + "QWord" { "REG_QWORD" } + "MultiString" { "REG_MULTI_SZ" } + "ExpandString" { "REG_EXPAND_SZ" } + "None" { "REG_NONE" } + default { "Unknown - $($type.ToString())" } } - If ($type -eq $null) { - $false - } Else { - $true + return @{ + type = $return_type + value = $value + raw_value = $raw_value } } # Will validate the key parameter to make sure it matches known format -if ($path -match "^([a-zA-Z_]*):\\(.*)$") { - $hive = $matches[1] - $reg_path = $matches[2] -} else { - Fail-Json $result "path does not match format 'HIVE:\KEY_PATH'" +if ($path -notmatch "^HK(CC|CR|CU|LM|U):\\") { + Fail-Json -obj $result -message "path: $path is not a valid registry path, see module documentation for examples." } -# Used when getting the actual REG_EXPAND_SZ value as well as checking the hive is a known value -$net_hive = Get-NetHiveName -hive $hive -if ($net_hive -eq 'unsupported') { - Fail-Json $result "the hive in path is '$hive'; must be 'HKCR', 'HKCC', 'HKCU', 'HKLM' or 'HKU'" +$registry_path = (Split-Path -Path $path -NoQualifier).Substring(1) # removes the hive: and leading \ +$registry_hive = switch(Split-Path -Path $path -Qualifier) { + "HKCR:" { [Microsoft.Win32.Registry]::ClassesRoot } + "HKCC:" { [Microsoft.Win32.Registry]::CurrentConfig } + "HKCU:" { [Microsoft.Win32.Registry]::CurrentUser } + "HKLM:" { [Microsoft.Win32.Registry]::LocalMachine } + "HKU" { [Microsoft.Win32.Registry]::Users } } -if (Test-Path REGISTRY::$hive\$reg_path) { - if ($name -eq $null) { - $property_info = @{} - $properties = Get-ItemProperty REGISTRY::$hive\$reg_path - - foreach ($property in $properties.PSObject.Properties) { - # Powershell adds in some metadata we need to filter out - $real_property = Test-RegistryProperty -hive $hive -path $reg_path -property $property.Name - if ($real_property -eq $true) { - $property_object = Get-PropertyObject -hive $hive -net_hive $net_hive -path $reg_path -property $property.Name - $property_info.Add($property.Name, $property_object) - } - } +$key = $null +try { + $key = $registry_hive.OpenSubKey($registry_path, $false) - $sub_keys = @() - $sub_keys_raw = Get-ChildItem REGISTRY::$hive\$reg_path -ErrorAction SilentlyContinue + if ($null -ne $key) { + if ($null -eq $name) { + $property_info = @{} + foreach ($property in $key.GetValueNames()) { + $property_info.$property = Get-PropertyValue -Key $key -Name $property + } - foreach ($sub_key in $sub_keys_raw) { - $sub_keys += $sub_key.PSChildName - } + # Return the key's (Default) property if it has been defined + $default_value = Get-PropertyValue -Key $key -Name "" + if ($null -ne $default_value) { + $property_info."" = $default_value + } - $result.exists = $true - $result.sub_keys = $sub_keys - $result.properties = $property_info - } else { - $exists = Test-RegistryProperty -hive $hive -path $reg_path -property $name - if ($exists -eq $true) { - $propertyObject = Get-PropertyObject -hive $hive -net_hive $net_hive -path $reg_path -property $name $result.exists = $true - $result.raw_value = $propertyObject.raw_value - $result.value = $propertyObject.value - $result.type = $propertyObject.type + $result.properties = $property_info + $result.sub_keys = $key.GetSubKeyNames() } else { - $result.exists = $false + $property_value = Get-PropertyValue -Key $key -Name $name + if ($null -ne $property_value) { + $result.exists = $true + $result += $property_value + } else { + $result.exists = $false + } } + } else { + $result.exists = $false } -} else { - $result.exists = $false +} finally { + if ($key) { + $key.Dispose() + } + $registry_hive.Dispose() } -Exit-Json $result +Exit-Json -obj $result + diff --git a/lib/ansible/modules/windows/win_reg_stat.py b/lib/ansible/modules/windows/win_reg_stat.py index fb88aca0afe..6b76c971ed8 100644 --- a/lib/ansible/modules/windows/win_reg_stat.py +++ b/lib/ansible/modules/windows/win_reg_stat.py @@ -29,8 +29,12 @@ options: name: description: - The registry property name to get information for, the return json will not include the sub_keys and properties entries for the I(key) specified. + - Set to an empty string to target the registry key's C((Default)) property value. type: str aliases: [ entry, value, property ] +notes: +- The C(properties) return value will contain an empty string key C("") that refers to the key's C(Default) value. If + the value has not been set then this key is not returned. seealso: - module: win_regedit - module: win_regmerge @@ -49,6 +53,12 @@ EXAMPLES = r''' path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion name: CommonFilesDir register: common_files_dir + +- name: Obtain the registry key's (Default) property + win_reg_stat: + path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion + name: '' + register: current_version_default ''' RETURN = r''' @@ -67,6 +77,11 @@ properties: returned: success, path exists and property not specified type: dict sample: { + "" : { + "raw_value": "", + "type": "REG_SZ", + "value": "" + }, "binary_property" : { "raw_value": ["0x01", "0x16"], "type": "REG_BINARY", @@ -77,7 +92,7 @@ properties: "type": "REG_MULTI_SZ", "value": ["a", "b"] } - } + } sub_keys: description: A list of all the sub keys of the key specified. returned: success, path exists and property not specified diff --git a/lib/ansible/modules/windows/win_regedit.ps1 b/lib/ansible/modules/windows/win_regedit.ps1 index 37b7d1e09c6..a1b1928487b 100644 --- a/lib/ansible/modules/windows/win_regedit.ps1 +++ b/lib/ansible/modules/windows/win_regedit.ps1 @@ -8,8 +8,6 @@ #Requires -Module Ansible.ModuleUtils.Legacy #Requires -Module Ansible.ModuleUtils.PrivilegeUtil -$ErrorActionPreference = "Stop" - $params = Parse-Args -arguments $args -supports_check_mode $true $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false $diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false @@ -31,7 +29,8 @@ $result = @{ if ($diff_mode) { $result.diff = @{ - prepared = "" + before = "" + after = "" } } @@ -40,12 +39,20 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -namespace Ansible.RegEdit +namespace Ansible.WinRegedit { - public enum HKEY : uint + internal class NativeMethods { - LOCAL_MACHINE = 0x80000002, - USERS = 0x80000003 + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int RegLoadKeyW( + UInt32 hKey, + string lpSubKey, + string lpFile); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode)] + public static extern int RegUnLoadKeyW( + UInt32 hKey, + string lpSubKey); } public class Win32Exception : System.ComponentModel.Win32Exception @@ -60,41 +67,48 @@ namespace Ansible.RegEdit public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } } - public class Hive + public class Hive : IDisposable { - [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern int RegLoadKey( - HKEY hKey, - string lpSubKey, - string lpFile); - - [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern int RegUnLoadKey( - HKEY hKey, - string lpSubKey); + private const UInt32 SCOPE = 0x80000002; // HKLM + private string hiveKey; + private bool loaded = false; - public static void LoadHive(string lpSubKey, string lpFile) + public Hive(string hiveKey, string hivePath) { - int ret; - ret = RegLoadKey(HKEY.LOCAL_MACHINE, lpSubKey, lpFile); + this.hiveKey = hiveKey; + int ret = NativeMethods.RegLoadKeyW(SCOPE, hiveKey, hivePath); if (ret != 0) - throw new Win32Exception(ret, String.Format("Failed to load registry hive at {0}", lpFile)); + throw new Win32Exception(ret, String.Format("Failed to load registry hive at {0}", hivePath)); + loaded = true; } - public static void UnloadHive(string lpSubKey) + public static void UnloadHive(string hiveKey) { - GC.Collect(); - int ret; - ret = RegUnLoadKey(HKEY.LOCAL_MACHINE, lpSubKey); + int ret = NativeMethods.RegUnLoadKeyW(SCOPE, hiveKey); if (ret != 0) - throw new Win32Exception(ret, String.Format("Failed to unload registry hive at {0}", lpSubKey)); + throw new Win32Exception(ret, String.Format("Failed to unload registry hive at {0}", hiveKey)); + } + + public void Dispose() + { + if (loaded) + { + // Make sure the garbage collector disposes all unused handles and waits until it is complete + GC.Collect(); + GC.WaitForPendingFinalizers(); + + UnloadHive(hiveKey); + loaded = false; + } + GC.SuppressFinalize(this); } + ~Hive() { this.Dispose(); } } } '@ # fire a warning if the property name isn't specified, the (Default) key ($null) can only be a string -if ($name -eq $null -and $type -ne "string") { +if ($null -eq $name -and $type -ne "string") { Add-Warning -obj $result -message "the data type when name is not specified can only be 'string', the type has automatically been converted" $type = "string" } @@ -104,16 +118,13 @@ if ($path -notmatch "^HK(CC|CR|CU|LM|U):\\") { Fail-Json $result "path: $path is not a valid powershell path, see module documentation for examples." } -# Create the required PSDrives if missing -$registry_hive = Split-Path -Path $path -Qualifier -if ($registry_hive -eq "HKCR:" -and (-not (Test-Path HKCR:\))) { - New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -} -if ($registry_hive -eq "HKU:" -and (-not (Test-Path HKU:\))) { - New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS -} -if ($registry_hive -eq "HKCC:" -and (-not (Test-Path HKCC:\))) { - New-PSDrive -Name HKCC -PSProvider Registry -Root HKEY_CURRENT_CONFIG +# Add a warning if the path does not contains a \ and is not the leaf path +$registry_path = (Split-Path -Path $path -NoQualifier).Substring(1) # removes the hive: and leading \ +$registry_leaf = Split-Path -Path $path -Leaf +if ($registry_path -ne $registry_leaf -and -not $registry_path.Contains('\')) { + $msg = "path is not using '\' as a separator, support for '/' as a separator will be removed in a future Ansible version" + Add-DeprecationWarning -obj $result -message $msg -version 2.12 + $registry_path = $registry_path.Replace('/', '\') } # Simplified version of Convert-HexStringToByteArray from @@ -148,62 +159,202 @@ Function Convert-RegExportHexStringToByteArray($string) { } } -Function Test-RegistryProperty($path, $name) { - # will validate if the registry key contains the property, returns true - # if the property exists and false if the property does not +Function Compare-RegistryProperties($existing, $new) { + # Outputs $true if the property values don't match + if ($existing -is [Array]) { + (Compare-Object -ReferenceObject $existing -DifferenceObject $new -SyncWindow 0).Length -ne 0 + } else { + $existing -cne $new + } +} + +Function Get-DiffValue { + param( + [Parameter(Mandatory=$true)][Microsoft.Win32.RegistryValueKind]$Type, + [Parameter(Mandatory=$true)][Object]$Value + ) + + $diff = @{ type = $Type.ToString(); value = $Value } + + $enum = [Microsoft.Win32.RegistryValueKind] + if ($Type -in @($enum::Binary, $enum::None)) { + $diff.value = [System.Collections.Generic.List`1[String]]@() + foreach ($dec_value in $Value) { + $diff.value.Add("0x{0:x2}" -f $dec_value) + } + } elseif ($Type -eq $enum::DWord) { + $diff.value = "0x{0:x8}" -f $Value + } elseif ($Type -eq $enum::QWord) { + $diff.value = "0x{0:x16}" -f $Value + } + + return $diff +} + +Function Set-StateAbsent { + param( + # Used for diffs and exception messages to match up against Ansible input + [Parameter(Mandatory=$true)][String]$PrintPath, + [Parameter(Mandatory=$true)][Microsoft.Win32.RegistryKey]$Hive, + [Parameter(Mandatory=$true)][String]$Path, + [String]$Name, + [Switch]$DeleteKey + ) + + $key = $Hive.OpenSubKey($Path, $true) + if ($null -eq $key) { + # Key does not exist, no need to delete anything + return + } + try { - $reg_key = Get-Item -Path $path - $value = $reg_key.GetValue($name) - # need to do it this way return ($value -eq $null) does not work - if ($value -eq $null) { - return $false + if ($DeleteKey -and -not $Name) { + # delete_key=yes is set and name is null/empty, so delete the entire key + $key.Dispose() + $key = $null + if (-not $check_mode) { + try { + $Hive.DeleteSubKeyTree($Path, $false) + } catch { + Fail-Json -obj $result -message "failed to delete registry key at $($PrintPath): $($_.Exception.Message)" + } + } + $result.changed = $true + + if ($diff_mode) { + $result.diff.before = @{$PrintPath = @{}} + $result.diff.after = @{} + } } else { - return $true + # delete_key=no or name is not null/empty, delete the property not the full key + $property = $key.GetValue($Name) + if ($null -eq $property) { + # property does not exist + return + } + $property_type = $key.GetValueKind($Name) # used for the diff + + if (-not $check_mode) { + try { + $key.DeleteValue($Name) + } catch { + Fail-Json -obj $result -message "failed to delete registry property '$Name' at $($PrintPath): $($_.Exception.Message)" + } + } + + $result.changed = $true + if ($diff_mode) { + $diff_value = Get-DiffValue -Type $property_type -Value $property + $result.diff.before = @{ $PrintPath = @{ $Name = $diff_value } } + $result.diff.after = @{ $PrintPath = @{} } + } } - } catch [System.Management.Automation.ItemNotFoundException] { - # key didn't exist so the property mustn't - return $false } finally { - if ($reg_key) { - $reg_key.Close() + if ($key) { + $key.Dispose() } } } -Function Compare-RegistryProperties($existing, $new) { - $mismatch = $false - if ($existing -is [Array]) { - if ((Compare-Object -ReferenceObject $existing -DifferenceObject $new -SyncWindow 0).Length -ne 0) { - $mismatch = $true +Function Set-StatePresent { + param( + [Parameter(Mandatory=$true)][String]$PrintPath, + [Parameter(Mandatory=$true)][Microsoft.Win32.RegistryKey]$Hive, + [Parameter(Mandatory=$true)][String]$Path, + [String]$Name, + [Object]$Data, + [Microsoft.Win32.RegistryValueKind]$Type + ) + + $key = $Hive.OpenSubKey($Path, $true) + try { + if ($null -eq $key) { + # the key does not exist, create it so the next steps work + if (-not $check_mode) { + try { + $key = $Hive.CreateSubKey($Path) + } catch { + Fail-Json -obj $result -message "failed to create registry key at $($PrintPath): $($_.Exception.Message)" + } + } + $result.changed = $true + + if ($diff_mode) { + $result.diff.before = @{} + $result.diff.after = @{$PrintPath = @{}} + } + } elseif ($diff_mode) { + # Make sure the diff is in an expected state for the key + $result.diff.before = @{$PrintPath = @{}} + $result.diff.after = @{$PrintPath = @{}} } - } else { - if ($existing -cne $new) { - $mismatch = $true + + if ($null -eq $key -or $null -eq $Data) { + # Check mode and key was created above, we cannot do any more work, or $Data is $null which happens when + # we create a new key but haven't explicitly set the data + return } - } - return $mismatch -} + $property = $key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + if ($null -ne $property) { + # property exists, need to compare the values and type + $existing_type = $key.GetValueKind($name) + $change_value = $false -Function Get-DiffValueString($type, $value) { - $enum = [Microsoft.Win32.RegistryValueKind] - if ($type -in @($enum::Binary, $enum::None)) { - $hex_values = @() - foreach ($dec_value in $value) { - $hex_values += "0x$("{0:x2}" -f $dec_value)" + if ($Type -ne $existing_type) { + $change_value = $true + $result.data_type_changed = $true + $data_mismatch = Compare-RegistryProperties -existing $property -new $Data + if ($data_mismatch) { + $result.data_changed = $true + } + } else { + $data_mismatch = Compare-RegistryProperties -existing $property -new $Data + if ($data_mismatch) { + $change_value = $true + $result.data_changed = $true + } + } + + if ($change_value) { + if (-not $check_mode) { + try { + $key.SetValue($Name, $Data, $Type) + } catch { + Fail-Json -obj $result -message "failed to change registry property '$Name' at $($PrintPath): $($_.Exception.Message)" + } + } + $result.changed = $true + + if ($diff_mode) { + $result.diff.before.$PrintPath.$Name = Get-DiffValue -Type $existing_type -Value $property + $result.diff.after.$PrintPath.$Name = Get-DiffValue -Type $Type -Value $Data + } + } elseif ($diff_mode) { + $diff_value = Get-DiffValue -Type $existing_type -Value $property + $result.diff.before.$PrintPath.$Name = $diff_value + $result.diff.after.$PrintPath.$Name = $diff_value + } + } else { + # property doesn't exist just create a new one + if (-not $check_mode) { + try { + $key.SetValue($Name, $Data, $Type) + } catch { + Fail-Json -obj $result -message "failed to create registry property '$Name' at $($PrintPath): $($_.Exception.Message)" + } + } + $result.changed = $true + + if ($diff_mode) { + $result.diff.after.$PrintPath.$Name = Get-DiffValue -Type $Type -Value $Data + } + } + } finally { + if ($key) { + $key.Dispose() } - $diff_value = "$($type):[$($hex_values -join ", ")]" - } elseif ($type -eq $enum::DWord) { - $diff_value = "$($type):0x$("{0:x8}" -f $value)" - } elseif ($type -eq $enum::QWord) { - $diff_value = "$($type):0x$("{0:x16}" -f $value)" - } elseif ($type -eq $enum::MultiString) { - $diff_value = "$($type):[$($value -join ", ")]" - } else { - $diff_value = "$($type):$value" } - - return $diff_value } # convert property names "" to $null as "" refers to (Default) @@ -213,7 +364,7 @@ if ($name -eq "") { # convert the data to the required format if ($type -in @("binary", "none")) { - if ($data -eq $null) { + if ($null -eq $data) { $data = "" } @@ -230,7 +381,7 @@ if ($type -in @("binary", "none")) { } } elseif ($type -in @("dword", "qword")) { # dword's and dword's don't allow null values, set to 0 - if ($data -eq $null) { + if ($null -eq $data) { $data = 0 } @@ -260,14 +411,15 @@ if ($type -in @("binary", "none")) { } $data = [Int64]$data } -} elseif ($type -in @("string", "expandstring")) { +} elseif ($type -in @("string", "expandstring") -and $name) { # a null string or expandstring must be empty quotes - if ($data -eq $null) { + # Only do this if $name has been defined (not the default key) + if ($null -eq $data) { $data = "" } } elseif ($type -eq "multistring") { # convert the data for a multistring to a String[] array - if ($data -eq $null) { + if ($null -eq $data) { $data = [String[]]@() } elseif ($data -isnot [Array]) { $new_data = New-Object -TypeName String[] -ArgumentList 1 @@ -285,209 +437,59 @@ if ($type -in @("binary", "none")) { # convert the type string to the .NET class $type = [System.Enum]::Parse([Microsoft.Win32.RegistryValueKind], $type, $true) -if ($hive) { - if (-not (Test-Path $hive)) { - Fail-Json -obj $result -message "hive at path '$hive' is not valid or accessible, cannot load hive" - } - - $original_tmp = $env:TMP - $env:TMP = $_remote_tmp - Add-Type -TypeDefinition $registry_util - $env:TMP = $original_tmp +$registry_hive = switch(Split-Path -Path $path -Qualifier) { + "HKCR:" { [Microsoft.Win32.Registry]::ClassesRoot } + "HKCC:" { [Microsoft.Win32.Registry]::CurrentConfig } + "HKCU:" { [Microsoft.Win32.Registry]::CurrentUser } + "HKLM:" { [Microsoft.Win32.Registry]::LocalMachine } + "HKU" { [Microsoft.Win32.Registry]::Users } +} +$loaded_hive = $null +try { + if ($hive) { + if (-not (Test-Path -LiteralPath $hive)) { + Fail-Json -obj $result -message "hive at path '$hive' is not valid or accessible, cannot load hive" + } - try { - Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true - Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true - } catch [System.ComponentModel.Win32Exception] { - Fail-Json -obj $result -message "failed to enable SeBackupPrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)" - } + $original_tmp = $env:TMP + $env:TMP = $_remote_tmp + Add-Type -TypeDefinition $registry_util + $env:TMP = $original_tmp - if (Test-Path -Path HKLM:\ANSIBLE) { - Add-Warning -obj $result -message "hive already loaded at HKLM:\ANSIBLE, had to unload hive for win_regedit to continue" try { - [Ansible.RegEdit.Hive]::UnloadHive("ANSIBLE") + Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true + Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true } catch [System.ComponentModel.Win32Exception] { - Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)" + Fail-Json -obj $result -message "failed to enable SeBackupPrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)" } - } - try { - [Ansible.RegEdit.Hive]::LoadHive("ANSIBLE", $hive) - } catch [System.ComponentModel.Win32Exception] { - Fail-Json -obj $result -message "failed to load registry hive from '$hive' to HKLM:\ANSIBLE: $($_.Exception.Message)" - } -} - -try { - if ($state -eq "present") { - if (-not (Test-Path -path $path)) { - # the key doesn't exist, create it so the next steps work + if (Test-Path -Path HKLM:\ANSIBLE) { + Add-Warning -obj $result -message "hive already loaded at HKLM:\ANSIBLE, had to unload hive for win_regedit to continue" try { - $new_key = New-Item -Path $path -Type directory -Force -WhatIf:$check_mode - } catch { - Fail-Json $result "failed to create registry key at $($path): $($_.Exception.Message)" - } finally { - if ($new_key) { - $new_key.Close() - } - } - $result.changed = $true - - if ($diff_mode) { - $result.diff.prepared += @" -+[$path] -"@ + [Ansible.WinRegedit.Hive]::UnloadHive("ANSIBLE") + } catch [System.ComponentModel.Win32Exception] { + Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)" } } - - if (Test-RegistryProperty -path $path -name $name) { - # property exists, need to compare the values and type - $existing_key = Get-Item -Path $path - $existing_type = $existing_key.GetValueKind($name) - $existing_data = $existing_key.GetValue($name, $false, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) - $existing_key.Close() - $change_value = $false - - if ($type -ne $existing_type) { - $change_value = $true - $result.data_type_changed = $true - $data_mismatch = Compare-RegistryProperties -existing $existing_data -new $data - if ($data_mismatch) { - $result.data_changed = $true - } - } else { - $data_mismatch = Compare-RegistryProperties -existing $existing_data -new $data - if ($data_mismatch) { - $change_value = $true - $result.data_changed = $true - } - } - - if ($change_value) { - if (-not $check_mode) { - $reg_key = Get-Item -Path $path - try { - $sub_key = $reg_key.OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) - try { - $sub_key.SetValue($name, $data, $type) - } finally { - $sub_key.Close() - } - } catch { - Fail-Json $result "failed to change registry property '$name' at $($path): $($_.Exception.Message)" - } finally { - $reg_key.Close() - } - } - $result.changed = $true - - if ($diff_mode) { - if ($result.diff.prepared) { - $key_prefix = "+" - } else { - $key_prefix = "" - } - - $result.diff.prepared = @" -$key_prefix[$path] --"$name" = "$(Get-DiffValueString -type $existing_type -value $existing_data)" -+"$name" = "$(Get-DiffValueString -type $type -value $data)" -"@ - } - } - } else { - # property doesn't exist just create a new one - if (-not $check_mode) { - $reg_key = Get-Item -Path $path - try { - $sub_key = $reg_key.OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) - try { - $sub_key.SetValue($name, $data, $type) - } finally { - $sub_key.Close() - } - } catch { - Fail-Json $result "failed to change registry property '$name' at $($path): $($_.Exception.Message)" - } finally { - $reg_key.Close() - } - } - $result.changed = $true - if ($diff_mode) { - if ($result.diff.prepared) { - $key_prefix = "+" - } else { - $key_prefix = "" - } - - $result.diff.prepared = @" -$key_prefix[$path] -+"$name" = "$(Get-DiffValueString -type $type -value $data)" -"@ - } + + try { + $loaded_hive = New-Object -TypeName Ansible.WinRegedit.Hive -ArgumentList "ANSIBLE", $hive + } catch [System.ComponentModel.Win32Exception] { + Fail-Json -obj $result -message "failed to load registry hive from '$hive' to HKLM:\ANSIBLE: $($_.Exception.Message)" } + } + + if ($state -eq "present") { + Set-StatePresent -PrintPath $path -Hive $registry_hive -Path $registry_path -Name $name -Data $data -Type $type } else { - if (Test-Path -path $path) { - if ($delete_key -and $name -eq $null) { - # the clear_key flag is set and name is null so delete the entire key - try { - $null = Remove-Item -Path $path -Force -Recurse -WhatIf:$check_mode - } catch { - Fail-Json $result "failed to delete registry key at $($path): $($_.Exception.Message)" - } - $result.changed = $true - - if ($diff_mode) { - $result.diff.prepared += @" --[$path] -"@ - } - } else { - # the clear_key flag is set or name is not null, check whether we need to delete a property - if (Test-RegistryProperty -path $path -name $name) { - $existing_key = Get-Item -Path $path - $existing_type = $existing_key.GetValueKind($name) - $existing_data = $existing_key.GetValue($name, $false, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) - $existing_key.Close() - - # cannot use Remove-ItemProperty as it fails when deleting the (Default) key ($name = $null) - if (-not $check_mode) { - $reg_key = Get-Item -Path $path - try { - $sub_key = $reg_key.OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree) - try { - $sub_key.DeleteValue($name) - } finally { - $sub_key.Close() - } - } catch { - Fail-Json $result "failed to delete registry property '$name' at $($path): $($_.Exception.Message)" - } finally { - $reg_key.Close() - } - } - $result.changed = $true - - if ($diff_mode) { - $result.diff.prepared += @" -[$path] --"$name" = "$(Get-DiffValueString -type $existing_type -value $existing_data)" -"@ - } - } - } - } + Set-StateAbsent -PrintPath $path -Hive $registry_hive -Path $registry_path -Name $name -DeleteKey:$delete_key } } finally { - if ($hive) { - [GC]::Collect() - [GC]::WaitForPendingFinalizers() - try { - [Ansible.RegEdit.Hive]::UnloadHive("ANSIBLE") - } catch [System.ComponentModel.Win32Exception] { - Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)" - } + $registry_hive.Dispose() + if ($loaded_hive) { + $loaded_hive.Dispose() } } Exit-Json $result + diff --git a/test/integration/targets/win_reg_stat/defaults/main.yml b/test/integration/targets/win_reg_stat/defaults/main.yml new file mode 100644 index 00000000000..a714e4399db --- /dev/null +++ b/test/integration/targets/win_reg_stat/defaults/main.yml @@ -0,0 +1,2 @@ +--- +test_reg_path: Test Key / [&Ansible*] diff --git a/test/integration/targets/win_reg_stat/files/test_reg.reg b/test/integration/targets/win_reg_stat/files/test_reg.reg deleted file mode 100644 index 64a3fc6971c..00000000000 --- a/test/integration/targets/win_reg_stat/files/test_reg.reg +++ /dev/null @@ -1,24 +0,0 @@ -Windows Registry Editor Version 5.00 - -[HKEY_CURRENT_USER\Test] - -[HKEY_CURRENT_USER\Test\nested] -"string"="test" -"binary"=hex:01,16 -"dword"=dword:00000001 -"qword"=hex(b):01,00,00,00,00,00,00,00 -"multi"=hex(7):61,00,2c,00,20,00,62,00,00,00,63,00,00,00,00,00 -"expand"=hex(2):25,00,77,00,69,00,6e,00,64,00,69,00,72,00,25,00,5c,00,64,00,69,\ - 00,72,00,00,00 - -[HKEY_CURRENT_USER\Test\nested\nest1] -"dontcare"="" - -[HKEY_CURRENT_USER\Test\nested\nest2] - - -[HKEY_CURRENT_USER\Test\single] -"string1"="" -"string2"="abc123" -"none"=hex(0): -"none1"=hex(0):00 diff --git a/test/integration/targets/win_reg_stat/tasks/main.yml b/test/integration/targets/win_reg_stat/tasks/main.yml index ebaa37d2339..654aeebd111 100644 --- a/test/integration/targets/win_reg_stat/tasks/main.yml +++ b/test/integration/targets/win_reg_stat/tasks/main.yml @@ -1,322 +1,29 @@ --- -- name: test - win_file: - path: "{{win_output_dir}}" - state: absent - -- name: make sure win output dir exists - win_file: - path: "{{win_output_dir}}" - state: directory - -- name: template out test registry structure - win_copy: - src: test_reg.reg - dest: "{{win_output_dir}}\\raw_test_reg.reg" - -- name: convert the line endings to the windows variant - win_shell: Get-Content "{{win_output_dir}}\raw_test_reg.reg" | Set-Content "{{win_output_dir}}\test_reg.reg" - -- name: import test registry structure - win_regmerge: - path: "{{win_output_dir}}\\test_reg.reg" - - name: get value of expand string %windir% win_command: powershell.exe $env:windir register: win_dir_value -- name: expect failure when not passing in path option - win_reg_stat: - name: a - register: actual - failed_when: "actual.msg != 'Get-AnsibleParam: Missing required argument: path'" - -- name: expect failure when passing in an invalid hive - win_reg_stat: - path: ABCD:\test - register: actual - failed_when: actual.msg != "the hive in path is 'ABCD'; must be 'HKCR', 'HKCC', 'HKCU', 'HKLM' or 'HKU'" - -- name: get known nested reg key structure for testing with short hive form - win_reg_stat: - path: HKCU:\Test\nested - register: actual_short - -- name: get known nested reg key structure for testing with quoted yaml - win_reg_stat: - path: "HKCU:\\Test\\nested" - register: actual_quoted - -- name: set expected value for reg structure - set_fact: - expected: - changed: false - exists: true - failed: false - properties: - binary: { raw_value: ["0x01", "0x16"], type: 'REG_BINARY', value: [1, 22] } - dword: { raw_value: 1, type: 'REG_DWORD', value: 1 } - expand: { raw_value: '%windir%\dir', type: 'REG_EXPAND_SZ', value: "{{win_dir_value.stdout_lines[0]}}\\dir" } - multi: { raw_value: ['a, b', 'c'], type: 'REG_MULTI_SZ', value: ['a, b', 'c'] } - qword: { raw_value: 1, type: 'REG_QWORD', value: 1 } - string: { raw_value: 'test', type: 'REG_SZ', value: 'test' } - sub_keys: - - nest1 - - nest2 - -- name: validate test - assert: - that: - - "actual_short == expected" - - "actual_quoted == expected" - -- name: get known reg key with no sub keys but some properties - win_reg_stat: - path: HKCU:\Test\single - register: actual - -- name: set expected value for reg key with no sub keys but some properties - set_fact: - expected: - changed: false - exists: true - failed: false - properties: - none: { raw_value: [], type: 'REG_NONE', value: [] } - none1: { raw_value: ["0x00"], type: 'REG_NONE', value: [0] } - string1: { raw_value: '', type: 'REG_SZ', value: '' } - string2: { raw_value: 'abc123', type: 'REG_SZ', value: 'abc123' } - sub_keys: [] - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get known reg key without sub keys and properties - win_reg_stat: - path: HKCU:\Test\nested\nest2 - register: actual - -- name: set expected value for reg key without sub keys or properties - set_fact: - expected: - changed: false - exists: true - failed: false - properties: {} - sub_keys: [] - register: expected - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get non-existent reg key - win_reg_stat: - path: HKCU:\Test\Thispathwillneverexist - register: actual - -- name: set expected value for non-existent reg key - set_fact: - expected: - changed: false - exists: false - failed: false - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get string property - win_reg_stat: - path: HKCU:\Test\nested - name: string - register: actual - -- name: set expected string property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: 'test' - type: 'REG_SZ' - value: 'test' - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get expand string property - win_reg_stat: - path: HKCU:\Test\nested - name: expand - register: actual - -- name: set expected expand string property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: '%windir%\dir' - type: 'REG_EXPAND_SZ' - value: "{{win_dir_value.stdout_lines[0]}}\\dir" - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get multi string property - win_reg_stat: - path: HKCU:\Test\nested - name: multi - register: actual - -- name: set expected multi string property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: ['a, b', 'c'] - type: 'REG_MULTI_SZ' - value: ['a, b', 'c'] - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get binary property - win_reg_stat: - path: HKCU:\Test\nested - name: binary - register: actual - -- name: set expected binary property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: ["0x01", "0x16"] - type: 'REG_BINARY' - value: [1, 22] - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get dword property - win_reg_stat: - path: HKCU:\Test\nested - name: dword - register: actual - -- name: set expected dword property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: 1 - type: 'REG_DWORD' - value: 1 - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get qword property - win_reg_stat: - path: HKCU:\Test\nested - name: qword - register: actual - -- name: set expected qword property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: 1 - type: 'REG_QWORD' - value: 1 - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get none property - win_reg_stat: - path: HKCU:\Test\single - name: none - register: actual - -- name: set expected none property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: [] - type: 'REG_NONE' - value: [] - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get none with value property - win_reg_stat: - path: HKCU:\Test\single - name: none1 - register: actual - -- name: set expected none with value property - set_fact: - expected: - changed: false - exists: true - failed: false - raw_value: ["0x00"] - type: 'REG_NONE' - value: [0] - -- name: validate test - assert: - that: - - "actual == expected" - -- name: get non-existence property - win_reg_stat: - path: HKCU:\Test\single - name: doesnotexist - register: actual - -- name: set expected non-existence property - set_fact: - expected: - changed: false - exists: false - failed: false - -- name: validate test - assert: - that: - - "actual == expected" +- name: template out test registry structure + win_template: + src: test_reg.reg.j2 + dest: '{{ win_output_dir }}\test_reg.reg' -- name: remove registry entry - win_regedit: - path: HKCU:\Test - state: absent +- name: import test registry structure + win_regmerge: + path: '{{ win_output_dir }}\test_reg.reg' + +- block: + - name: run tests + import_tasks: tests.yml + + always: + - name: remove test registry key + win_regedit: + path: HKCU:\{{ test_reg_path }} + state: absent + delete_key: True + + - name: remove template registry file + win_file: + path: '{{ win_output_dir }}\test_reg.reg' + state: absent diff --git a/test/integration/targets/win_reg_stat/tasks/tests.yml b/test/integration/targets/win_reg_stat/tasks/tests.yml new file mode 100644 index 00000000000..285b501a4ad --- /dev/null +++ b/test/integration/targets/win_reg_stat/tasks/tests.yml @@ -0,0 +1,364 @@ +--- +- name: expect failure when not passing in path option + win_reg_stat: + name: a + register: actual + failed_when: "actual.msg != 'Get-AnsibleParam: Missing required argument: path'" + +- name: expect failure when passing in an invalid hive + win_reg_stat: + path: ABCD:\test + register: actual + failed_when: 'actual.msg != "path: ABCD:\\test is not a valid registry path, see module documentation for examples."' + +- name: get known nested reg key structure + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested + register: actual + +- name: set expected value for reg structure + set_fact: + expected: + changed: false + exists: true + failed: false + properties: + binary: { raw_value: ["0x01", "0x16"], type: 'REG_BINARY', value: [1, 22] } + dword: { raw_value: 1, type: 'REG_DWORD', value: 1 } + expand: { raw_value: '%windir%\dir', type: 'REG_EXPAND_SZ', value: "{{win_dir_value.stdout_lines[0]}}\\dir" } + large_dword: { raw_value: 4294967295, type: 'REG_DWORD', value: 4294967295 } + large_qword: { raw_value: 18446744073709551615, type: 'REG_QWORD', value: 18446744073709551615 } + multi: { raw_value: ['a, b', 'c'], type: 'REG_MULTI_SZ', value: ['a, b', 'c'] } + qword: { raw_value: 1, type: 'REG_QWORD', value: 1 } + string: { raw_value: 'test', type: 'REG_SZ', value: 'test' } + sub_keys: + - nest1 + - nest2 + +- name: assert get known nested reg key structure + assert: + that: + - actual == expected + +- name: get known reg key with no sub keys but some properties + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\single + register: actual + +- name: set expected value for reg key with no sub keys but some properties + set_fact: + expected: + changed: false + exists: true + failed: false + properties: + none: { raw_value: [], type: 'REG_NONE', value: [] } + none1: { raw_value: ["0x00"], type: 'REG_NONE', value: [0] } + string1: { raw_value: '', type: 'REG_SZ', value: '' } + string2: { raw_value: 'abc123', type: 'REG_SZ', value: 'abc123' } + sub_keys: [] + +- name: assert get known reg key with no sub keys but some properties + assert: + that: + - actual == expected + +- name: get known reg key without sub keys and properties + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested\nest2 + register: actual + +- name: set expected value for reg key without sub keys or properties + set_fact: + expected: + changed: false + exists: true + failed: false + properties: {} + sub_keys: [] + register: expected + +- name: assert get known reg key without sub keys and properties + assert: + that: + - actual == expected + +- name: get non-existent reg key + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\Thispathwillneverexist + register: actual + +- name: set expected value for non-existent reg key + set_fact: + expected: + changed: false + exists: false + failed: false + +- name: assert get non-existent reg key + assert: + that: + - actual == expected + +- name: get string property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested + name: string + register: actual + +- name: set expected string property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: 'test' + type: 'REG_SZ' + value: 'test' + +- name: assert get string property + assert: + that: + - actual == expected + +- name: get expand string property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested + name: expand + register: actual + +- name: set expected expand string property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: '%windir%\dir' + type: 'REG_EXPAND_SZ' + value: "{{win_dir_value.stdout_lines[0]}}\\dir" + +- name: assert get expand string property + assert: + that: + - actual == expected + +- name: get multi string property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested + name: multi + register: actual + +- name: set expected multi string property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: ['a, b', 'c'] + type: 'REG_MULTI_SZ' + value: ['a, b', 'c'] + +- name: assert get multi string property + assert: + that: + - actual == expected + +- name: get binary property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested + name: binary + register: actual + +- name: set expected binary property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: ["0x01", "0x16"] + type: 'REG_BINARY' + value: [1, 22] + +- name: assert get binary property + assert: + that: + - actual == expected + +- name: get dword property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested + name: dword + register: actual + +- name: set expected dword property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: 1 + type: 'REG_DWORD' + value: 1 + +- name: assert get dword property + assert: + that: + - actual == expected + +- name: get qword property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\nested + name: qword + register: actual + +- name: set expected qword property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: 1 + type: 'REG_QWORD' + value: 1 + +- name: assert get qword property + assert: + that: + - actual == expected + +- name: get none property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\single + name: none + register: actual + +- name: set expected none property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: [] + type: 'REG_NONE' + value: [] + +- name: assert get none property + assert: + that: + - actual == expected + +- name: get none with value property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\single + name: none1 + register: actual + +- name: set expected none with value property + set_fact: + expected: + changed: false + exists: true + failed: false + raw_value: ["0x00"] + type: 'REG_NONE' + value: [0] + +- name: assert get non with value property + assert: + that: + - actual == expected + +- name: get non-existent property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\single + name: doesnotexist + register: actual + +- name: set expected non-existent property + set_fact: + expected: + changed: false + exists: false + failed: false + +- name: assert get non-existent property + assert: + that: + - actual == expected + +- name: get key with default property set + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\Duplicate Default + register: actual + +- name: assert get key with default property set + assert: + that: + - actual.properties[""]['raw_value'] == "default" + - actual.properties[""]['type'] == "REG_SZ" + - actual.properties[""]['value'] == "default" + - actual.properties['(Default)'].raw_value == "custom" + - actual.properties['(Default)'].type == "REG_SZ" + - actual.properties['(Default)'].value == "custom" + +- name: get default property + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\Duplicate Default + name: '' + register: actual + +- name: assert get default property + assert: + that: + - actual.value == "default" + - actual.raw_value == "default" + - actual.type == "REG_SZ" + +- name: get key with blank property set + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\Blank Default + register: actual + +- name: assert get key with blank property set + assert: + that: + - actual.properties[""].raw_value == "" + - actual.properties[""].type == "REG_SZ" + - actual.properties[""].value == "" + - actual.properties['(Default)'].raw_value == "" + - actual.properties['(Default)'].type == "REG_SZ" + - actual.properties['(Default)'].value == "" + +- name: get default property as empty string + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\Blank Default + name: '' + register: actual + +- name: assert get default property as empty string + assert: + that: + - actual.value == "" + - actual.raw_value == "" + - actual.type == "REG_SZ" + +- name: get key with no properties set + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\Empty Default + register: actual + +- name: assert get key with no properties set + assert: + that: + - actual.properties == {} + +- name: get default property that has not been set + win_reg_stat: + path: HKCU:\{{ test_reg_path }}\Empty Default + name: '' + register: actual + +- name: assert get default property that has not been set + assert: + that: + - not actual.exists diff --git a/test/integration/targets/win_reg_stat/templates/test_reg.reg.j2 b/test/integration/targets/win_reg_stat/templates/test_reg.reg.j2 new file mode 100644 index 00000000000..ac3d44ed730 --- /dev/null +++ b/test/integration/targets/win_reg_stat/templates/test_reg.reg.j2 @@ -0,0 +1,37 @@ +Windows Registry Editor Version 5.00 + +[HKEY_CURRENT_USER\{{ test_reg_path }}] + +[HKEY_CURRENT_USER\{{ test_reg_path }}\nested] +"string"="test" +"binary"=hex:01,16 +"dword"=dword:00000001 +"qword"=hex(b):01,00,00,00,00,00,00,00 +"large_dword"=dword:ffffffff +"large_qword"=hex(b):ff,ff,ff,ff,ff,ff,ff,ff +"multi"=hex(7):61,00,2c,00,20,00,62,00,00,00,63,00,00,00,00,00 +"expand"=hex(2):25,00,77,00,69,00,6e,00,64,00,69,00,72,00,25,00,5c,00,64,00,69,\ + 00,72,00,00,00 + +[HKEY_CURRENT_USER\{{ test_reg_path }}\nested\nest1] +"dontcare"="" + +[HKEY_CURRENT_USER\{{ test_reg_path }}\nested\nest2] + + +[HKEY_CURRENT_USER\{{ test_reg_path }}\single] +"string1"="" +"string2"="abc123" +"none"=hex(0): +"none1"=hex(0):00 + +[HKEY_CURRENT_USER\{{ test_reg_path }}\Empty Default] + + +[HKEY_CURRENT_USER\{{ test_reg_path }}\Blank Default] +@="" +"(Default)"="" + +[HKEY_CURRENT_USER\{{ test_reg_path }}\Duplicate Default] +@="default" +"(Default)"="custom" diff --git a/test/integration/targets/win_regedit/defaults/main.yml b/test/integration/targets/win_regedit/defaults/main.yml index cb3b84b0a15..ec884875568 100644 --- a/test/integration/targets/win_regedit/defaults/main.yml +++ b/test/integration/targets/win_regedit/defaults/main.yml @@ -1,3 +1,3 @@ -test_win_regedit_local_key: HKLM:\Software\Cow Corp +test_win_regedit_local_key: 'HKLM:\Software\Moo []{}!@#$%^&*()-_=+/key''"?<>' test_win_regedit_classes_key: HKCR:\.test-ansible test_win_regedit_hive_key: HKLM:\ANSIBLE\NewKey diff --git a/test/integration/targets/win_regedit/tasks/create_tests.yml b/test/integration/targets/win_regedit/tasks/create_tests.yml index d2e3a38e64e..e8cc5367744 100644 --- a/test/integration/targets/win_regedit/tasks/create_tests.yml +++ b/test/integration/targets/win_regedit/tasks/create_tests.yml @@ -131,14 +131,6 @@ - data_create_actual.type == test_win_regedit_key_expected_type when: test_win_regedit_key_type not in ['dword', 'qword'] -- name: debug 1 - debug: - var: test_win_regedit_key_expected_value1 - -- name: debug 2 - debug: - var: test_win_regedit_key_expected_value1|int - - name: assert create a {{test_win_regedit_key_type}} for dword or qword assert: that: diff --git a/test/integration/targets/win_regedit/tasks/tests.yml b/test/integration/targets/win_regedit/tasks/tests.yml index d2e89963b40..d633e5333b0 100644 --- a/test/integration/targets/win_regedit/tasks/tests.yml +++ b/test/integration/targets/win_regedit/tasks/tests.yml @@ -1,4 +1,17 @@ --- +- name: check warning is fired if path with / as separators is used + win_regedit: + path: HKLM:\SOFTWARE/Microsoft + state: present + register: forward_separator_warn + +- name: assert warning is fired if / is used as a separator + assert: + that: + - forward_separator_warn.deprecations|length == 1 + - forward_separator_warn.deprecations[0].msg == "path is not using '\\' as a separator, support for '/' as a separator will be removed in a future Ansible version" + - forward_separator_warn.deprecations[0].version == 2.12 + - name: fail run win_regedit with larger dword win_regedit: path: '{{test_win_regedit_local_key}}' @@ -82,16 +95,17 @@ register: modify_default_check check_mode: yes -# win_reg_stat struggles with the (Default) property just use powershell - name: get actual modify the (Default) key property check - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' + name: '' register: modify_default_actual_check - name: assert modify the (Default) key property check assert: that: - modify_default_check is changed - - modify_default_actual_check.stdout == "" + - not modify_default_actual_check.exists - name: modify the (Default) key property win_regedit: @@ -101,14 +115,16 @@ register: modify_default - name: get actual modify the (Default) key property - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' + name: '' register: modify_default_actual - name: assert modify the (Default) key property assert: that: - modify_default is changed - - modify_default_actual.stdout == "default value\r\n" + - modify_default_actual.value == "default value" - name: create an actual property called (Default) win_regedit: @@ -120,20 +136,17 @@ register: create_specific_default_check check_mode: yes -- name: get actual value for (Default) property to ensure it didn't change check - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" - register: create_specific_default_actual_default_check - -- name: get actual for create specific property called (Default) check - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue('(Default)', $null)" +- name: get actual value for (Default) property + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' register: create_specific_default_actual_check - name: assert create specific property called (Default) check assert: that: - create_specific_default_check is changed - - create_specific_default_actual_default_check.stdout == "default value\r\n" - - create_specific_default_actual_check.stdout == "" + - create_specific_default_actual_check.properties[""].value == "default value" + - not "(Default)" in create_specific_default_actual_check.properties - name: create an actual property called (Default) win_regedit: @@ -144,20 +157,17 @@ state: present register: create_specific_default -- name: get actual value for (Default) property to ensure it didn't change - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" - register: create_specific_default_actual_default - -- name: get actual for specific property called (Default) - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue('(Default)', $null)" +- name: get actual value for (Default) property + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' register: create_specific_default_actual - name: assert create specific property called (Default) assert: that: - create_specific_default is changed - - create_specific_default_actual_default.stdout == "default value\r\n" - - create_specific_default_actual.stdout == "custom default value\r\n" + - create_specific_default_actual.properties[""].value == "default value" + - create_specific_default_actual.properties["(Default)"].value == "custom default value" - name: delete property check win_regedit: @@ -219,19 +229,16 @@ check_mode: yes - name: get actual of key's (Default) property check - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' register: delete_default_actual_check -- name: get actual of custom (Default) property check - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue('(Default)', $null)" - register: delete_default_custom_actual_check - - name: assert delete the key's (Default) property check assert: that: - delete_default_check is changed - - delete_default_actual_check.stdout == "default value\r\n" - - delete_default_custom_actual_check.stdout == "custom default value\r\n" + - delete_default_actual_check.properties[""].value == "default value" + - delete_default_actual_check.properties["(Default)"].value == "custom default value" - name: delete the key's (Default) property win_regedit: @@ -241,19 +248,16 @@ register: delete_default - name: get actual of key's (Default) property - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' register: delete_default_actual -- name: get actual of custom (Default) property - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue('(Default)', $null)" - register: delete_default_custom_actual - - name: assert delete the key's (Default) property assert: that: - delete_default is changed - - delete_default_actual.stdout == "" - - delete_default_custom_actual.stdout == "custom default value\r\n" + - not "" in delete_default_actual.properties + - delete_default_actual.properties["(Default)"].value == "custom default value" - name: recreate the key's (Default) property for next test win_regedit: @@ -270,19 +274,16 @@ check_mode: yes - name: get actual of key's (Default) property check - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' register: delete_custom_default_actual_check -- name: get actual of custom (Default) property check - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue('(Default)', $null)" - register: delete_custom_default_custom_actual_check - - name: assert delete the custom (Default) property check assert: that: - delete_custom_default_check is changed - - delete_custom_default_actual_check.stdout == "default value\r\n" - - delete_custom_default_custom_actual_check.stdout == "custom default value\r\n" + - delete_custom_default_actual_check.properties[""].value == "default value" + - delete_custom_default_actual_check.properties["(Default)"].value == "custom default value" - name: delete the custom (Default) property win_regedit: @@ -293,19 +294,16 @@ register: delete_custom_default - name: get actual of key's (Default) property - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue($null, $null)" + win_reg_stat: + path: '{{ test_win_regedit_local_key }}' register: delete_custom_default_actual -- name: get actual of custom (Default) property - win_command: powershell.exe "(Get-Item -Path '{{test_win_regedit_local_key}}').GetValue('(Default)', $null)" - register: delete_custom_default_custom_actual - - name: assert delete the custom (Default) property assert: that: - delete_custom_default is changed - - delete_custom_default_actual.stdout == "default value\r\n" - - delete_custom_default_custom_actual.stdout == "" + - delete_custom_default_actual.properties[""].value == "default value" + - not "(Default)" in delete_custom_default_actual.properties - name: add some nested keys for later deletion win_regedit: @@ -366,6 +364,26 @@ that: - delete_key_again is not changed +- name: create a new key without specifying the property + win_regedit: + path: '{{ test_win_regedit_local_key }}\new' + state: present + register: create_key_no_prop + +- name: get result of create a new key without specifying the property + win_reg_stat: + path: '{{ test_win_regedit_local_key }}\new' + register: create_key_no_prop_actual + +- name: assert create a new key without specifying the property + assert: + that: + - create_key_no_prop is changed + - not create_key_no_prop.data_changed + - not create_key_no_prop.data_type_changed + - create_key_no_prop_actual.exists + - create_key_no_prop_actual.properties == {} + - name: create key in HKEY_CLASSES_ROOT check win_regedit: path: '{{test_win_regedit_classes_key}}'