diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b9fde3e4d..a44e7454e4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -238,6 +238,7 @@ Ansible Changes By Release * win_find * win_path * win_psexec + * win_reg_stat * win_say * win_shortcut - xbps diff --git a/lib/ansible/modules/windows/win_reg_stat.ps1 b/lib/ansible/modules/windows/win_reg_stat.ps1 new file mode 100644 index 00000000000..6f4ec8ad594 --- /dev/null +++ b/lib/ansible/modules/windows/win_reg_stat.ps1 @@ -0,0 +1,150 @@ +#!powershell +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# WANT_JSON +# POWERSHELL_COMMON + +$ErrorActionPreference = "Stop" + +$params = Parse-Args $args -supports_check_mode $true +$key = Get-AnsibleParam $params "key" -FailIfEmpty $true +$property = Get-AnsibleParam $params "property" -FailIfEmpty $false -default $null + +$result = @{ + win_reg_stat = @{} + changed = $false + warnings = @() +} + +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-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"} + } +} + +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 = @() + foreach ($byte in $value) { + $hex_value = ('{0:x}' -f $byte).PadLeft(2, '0') + $raw_value += "0x$hex_value" + } + } Else { + $raw_value = $value + } + + $object = New-Object PSObject @{ + 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 + } + + If ($type -eq $null) { + $false + } Else { + $true + } +} + +# Will validate the key parameter to make sure it matches known format +if ($key -match "^([a-zA-Z_]*):\\(.*)$") { + $hive = $matches[1] + $path = $matches[2] +} else { + Fail-Json $result "key does not match format 'HIVE:\KEY_PATH'" +} + +# 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 key is '$hive'; must be 'HKCR', 'HKCC', 'HKCU', 'HKLM' or 'HKU'" +} + +if (Test-Path REGISTRY::$hive\$path) { + if ($property -eq $null) { + $property_info = @{} + $properties = Get-ItemProperty REGISTRY::$hive\$path + + foreach ($property in $properties.PSObject.Properties) { + # Powershell adds in some metadata we need to filter out + $real_property = Test-RegistryProperty -hive $hive -path $path -property $property.Name + if ($real_property -eq $true) { + $property_object = Get-PropertyObject -hive $hive -net_hive $net_hive -path $path -property $property.Name + $property_info.Add($property.Name, $property_object) + } + } + + $sub_keys = @() + $sub_keys_raw = Get-ChildItem REGISTRY::$hive\$path -ErrorAction SilentlyContinue + + foreach ($sub_key in $sub_keys_raw) { + $sub_keys += $sub_key.PSChildName + } + + $result.win_reg_stat.exists = $true + $result.win_reg_stat.sub_keys = $sub_keys + $result.win_reg_stat.properties = $property_info + } else { + $exists = Test-RegistryProperty -hive $hive -path $path -property $property + if ($exists -eq $true) { + $propertyObject = Get-PropertyObject -hive $hive -net_hive $net_hive -path $path -property $property + $result.win_reg_stat.exists = $true + $result.win_reg_stat.raw_value = $propertyObject.raw_value + $result.win_reg_stat.value = $propertyObject.value + $result.win_reg_stat.type = $propertyObject.type + } else { + $result.win_reg_stat.exists = $false + } + } +} else { + $result.win_reg_stat.exists = $false +} + +Exit-Json $result diff --git a/lib/ansible/modules/windows/win_reg_stat.py b/lib/ansible/modules/windows/win_reg_stat.py new file mode 100644 index 00000000000..c097295efec --- /dev/null +++ b/lib/ansible/modules/windows/win_reg_stat.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Ansible, inc +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +DOCUMENTATION = r''' +--- +module: win_reg_stat +version_added: "2.3" +short_description: returns information about a Windows registry key or property of a key +description: +- Like M(win_file), M(win_reg_stat) will return whether the key/property exists. +- It also returns the sub keys and properties of the key specified. +- If specifying a property name through I(property), it will return the information specific for that property. +options: + key: + description: + - The full registry key path including the hive to search for. + required: true + property: + 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. + required: false + +author: "Jordan Borean (@jborean93)" +''' + +EXAMPLES = r''' +# Obtain information about a registry key using short form +- win_reg_stat: + key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion + register: current_version + +# Obtain information about a registry key property +- win_reg_stat: + key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion + property: CommonFilesDir + register: common_files_dir +''' + +RETURN = r''' +changed: + description: Whether anything was changed. + returned: always + type: boolean + sample: True +win_reg_stat: + description: Information about the registry key or property specified. + returned: success + type: dictionary + contains: + exists: + description: States whether the registry key/property exists. + returned: success and path/property exists + type: boolean + sample: True + properties: + description: A list of all the properties and their values in the key. + returned: success, path exists and property not specified + type: list + sample: [ + "binary_property" : { + "raw_value": ["0x01", "0x16"], + "type": "REG_BINARY", + "value": [1, 22] + }, + "multi_string_property" : { + "raw_value": ["a", "b"], + "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 + type: list + sample: [ + "AppHost", + "Casting", + "DateTime" + ] + raw_value: + description: Returns the raw value of the registry property, REG_EXPAND_SZ has no string expansion, REG_BINARY or REG_NONE is in hex 0x format. + REG_NONE, this value is a hex string in the 0x format. + returned: success, path/property exists and property specified + type: string + sample: '%ProgramDir%\\Common Files' + type: + description: The property type. + returned: success, path/property exists and property specified + type: string + sample: "REG_EXPAND_SZ" + value: + description: The value of the property. + returned: success, path/property exists and property specified + type: string + sample: 'C:\\Program Files\\Common Files' +''' diff --git a/test/integration/targets/win_reg_stat/aliases b/test/integration/targets/win_reg_stat/aliases new file mode 100644 index 00000000000..10e03fc2bf7 --- /dev/null +++ b/test/integration/targets/win_reg_stat/aliases @@ -0,0 +1 @@ +windows/ci/group1 diff --git a/test/integration/targets/win_reg_stat/files/test_reg.reg b/test/integration/targets/win_reg_stat/files/test_reg.reg new file mode 100644 index 00000000000..64a3fc6971c --- /dev/null +++ b/test/integration/targets/win_reg_stat/files/test_reg.reg @@ -0,0 +1,24 @@ +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/meta/main.yml b/test/integration/targets/win_reg_stat/meta/main.yml new file mode 100644 index 00000000000..d328716dfa4 --- /dev/null +++ b/test/integration/targets/win_reg_stat/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - prepare_win_tests diff --git a/test/integration/targets/win_reg_stat/tasks/main.yml b/test/integration/targets/win_reg_stat/tasks/main.yml new file mode 100644 index 00000000000..82679edbbf2 --- /dev/null +++ b/test/integration/targets/win_reg_stat/tasks/main.yml @@ -0,0 +1,317 @@ +--- +- 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 key option + win_reg_stat: + property: a + register: actual + failed_when: "actual.msg != 'Missing required argument: key'" + +- name: expect failure when passing in an invalid hive + win_reg_stat: + key: ABCD:\test + register: actual + failed_when: actual.msg != "the hive in key 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: + key: HKCU:\Test\nested + register: actual_short + +- name: get known nested reg key structure for testing with quoted yaml + win_reg_stat: + key: "HKCU:\\Test\\nested" + register: actual_quoted + +- name: set expected value for reg structure + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + 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: + key: HKCU:\Test\single + register: actual + +- name: set expected value for reg key with no sub keys but some properties + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + 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: + key: HKCU:\Test\nested\nest2 + register: actual + +- name: set expected value for reg key without sub keys or properties + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + properties: {} + sub_keys: [] + register: expected + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get non-existant reg key + win_reg_stat: + key: HKCU:\Test\Thispathwillneverexist + register: actual + +- name: set expected value for non-existant reg key + set_fact: + expected: + changed: false + win_reg_stat: + exists: false + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get string property + win_reg_stat: + key: HKCU:\Test\nested + property: string + register: actual + +- name: set expected string property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + raw_value: 'test' + type: 'REG_SZ' + value: 'test' + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get expand string property + win_reg_stat: + key: HKCU:\Test\nested + property: expand + register: actual + +- name: set expected expand string property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + 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: + key: HKCU:\Test\nested + property: multi + register: actual + +- name: set expected multi string property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + 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: + key: HKCU:\Test\nested + property: binary + register: actual + +- name: set expected binary property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + raw_value: ["0x01", "0x16"] + type: 'REG_BINARY' + value: [1, 22] + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get dword property + win_reg_stat: + key: HKCU:\Test\nested + property: dword + register: actual + +- name: set expected dword property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + raw_value: 1 + type: 'REG_DWORD' + value: 1 + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get qword property + win_reg_stat: + key: HKCU:\Test\nested + property: qword + register: actual + +- name: set expected qword property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + raw_value: 1 + type: 'REG_QWORD' + value: 1 + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get none property + win_reg_stat: + key: HKCU:\Test\single + property: none + register: actual + +- name: set expected none property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + raw_value: [] + type: 'REG_NONE' + value: [] + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get none with value property + win_reg_stat: + key: HKCU:\Test\single + property: none1 + register: actual + +- name: set expected none with value property + set_fact: + expected: + changed: false + win_reg_stat: + exists: true + raw_value: ["0x00"] + type: 'REG_NONE' + value: [0] + +- name: validate test + assert: + that: + - "actual == expected" + +- name: get non-existance property + win_reg_stat: + key: HKCU:\Test\single + property: doesnotexist + register: actual + +- name: set expected non-existance property + set_fact: + expected: + changed: false + win_reg_stat: + exists: false + +- name: validate test + assert: + that: + - "actual == expected" + +- name: remove registry entry + win_regedit: + key: HKCU:\Test + state: absent diff --git a/test/integration/test_win_group3.yml b/test/integration/test_win_group3.yml index bcb19a965d5..80f2590d229 100644 --- a/test/integration/test_win_group3.yml +++ b/test/integration/test_win_group3.yml @@ -7,3 +7,4 @@ - { role: win_async_wrapper, tags: ["test_win_async_wrapper", "test_async_wrapper", "test_win_async_status", "test_async_status"] } - { role: win_shell, tags: test_win_shell } - { role: win_command, tags: test_win_command } + - { role: win_reg_stat, tags: test_win_reg_stat }