win_regedit: added function to load a dat file for editing (#31289)

* win_regedit: added function to load a dat file for editing

* try to make the tests more resiliant

* more stability changes
This commit is contained in:
Jordan Borean 2017-10-18 06:30:33 +10:00 committed by GitHub
commit 743ff4897a
6 changed files with 597 additions and 129 deletions

View file

@ -1,24 +1,11 @@
#!powershell
# This file is part of Ansible
#
# (c) 2015, Adam Keech <akeech@chathamfinancial.com>, Josh Ludwig <jludwig@chathamfinancial.com>
# (c) 2017, Jordan Borean <jborean93@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# WANT_JSON
# POWERSHELL_COMMON
#Requires -Module Ansible.ModuleUtils.Legacy.psm1
$ErrorActionPreference = "Stop"
@ -32,6 +19,7 @@ $data = Get-AnsibleParam -obj $params -name "data"
$type = Get-AnsibleParam -obj $params -name "type" -type "str" -default "string" -validateset "none","binary","dword","expandstring","multistring","string","qword" -aliases "datatype"
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent"
$delete_key = Get-AnsibleParam -obj $params -name "delete_key" -type "bool" -default $true
$hive = Get-AnsibleParam -obj $params -name "hive" -type "path"
$result = @{
changed = $false
@ -45,6 +33,147 @@ if ($diff_mode) {
}
}
$registry_util = @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Ansible
{
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
public LUID Luid;
public UInt32 Attributes;
}
public enum HKEY : uint
{
LOCAL_MACHINE = 0x80000002,
USERS = 0x80000003
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
}
public override string Message { get { return _msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
public class RegistryUtil
{
public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
public const int TOKEN_QUERY = 0x00000008;
public const int SE_PRIVILEGE_ENABLED = 0x00000002;
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(
IntPtr hObject);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
out IntPtr TokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
[MarshalAs(UnmanagedType.Struct)] out LUID lpLuid);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
UInt32 BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
[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);
public static void EnablePrivileges()
{
List<String> privileges = new List<String>()
{
"SeRestorePrivilege",
"SeBackupPrivilege"
};
foreach (string privilege in privileges)
{
IntPtr hToken;
LUID luid;
TOKEN_PRIVILEGES tkpPrivileges;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
throw new Win32Exception("OpenProcessToken() failed");
try
{
if (!LookupPrivilegeValue(null, privilege, out luid))
throw new Win32Exception("LookupPrivilegeValue() failed");
tkpPrivileges.PrivilegeCount = 1;
tkpPrivileges.Luid = luid;
tkpPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, false, ref tkpPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
throw new Win32Exception(String.Format("AdjustTokenPrivileges() failed to adjust privilege {0}", privilege));
}
finally
{
CloseHandle(hToken);
}
}
}
public static void LoadHive(string lpSubKey, string lpFile)
{
int ret;
ret = RegLoadKey(HKEY.LOCAL_MACHINE, lpSubKey, lpFile);
if (ret != 0)
throw new Win32Exception(ret, String.Format("Failed to load registry hive at {0}", lpFile));
}
public static void UnloadHive(string lpSubKey)
{
GC.Collect();
int ret;
ret = RegUnLoadKey(HKEY.LOCAL_MACHINE, lpSubKey);
if (ret != 0)
throw new Win32Exception(ret, String.Format("Failed to unload registry hive at {0}", lpSubKey));
}
}
}
'@
# Fix HCCC:\ PSDrive for pre-2.3 compatibility
if ($path -match "^HCCC:\\") {
Add-DeprecationWarning -obj $result -message "Please use path: HKCC:\... instead of path: $path" -version 2.6
@ -110,7 +239,8 @@ 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
try {
$value = (Get-Item -Path $path).GetValue($name)
$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
@ -120,6 +250,10 @@ Function Test-RegistryProperty($path, $name) {
} catch [System.Management.Automation.ItemNotFoundException] {
# key didn't exist so the property mustn't
return $false
} finally {
if ($reg_key) {
$reg_key.Close()
}
}
}
@ -235,59 +369,121 @@ if ($type -in @("binary", "none")) {
}
}
# convert the type string to the .NET class
$type = [System.Enum]::Parse([Microsoft.Win32.RegistryValueKind], $type, $true)
if ($state -eq "present") {
if (-not (Test-Path -path $path)) {
# the key doesn't exist, create it so the next steps work
try {
$null = New-Item -Path $path -Type directory -Force -WhatIf:$check_mode
} catch {
Fail-Json $result "failed to create registry key at $($path): $($_.Exception.Message)"
}
$result.changed = $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"
}
Add-Type -TypeDefinition $registry_util
try {
[Ansible.RegistryUtil]::EnablePrivileges()
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to enable SeRestorePrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)"
}
if ($diff_mode) {
$result.diff.prepared += @"
+[$path]
"@
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.RegistryUtil]::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)
$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
}
}
try {
[Ansible.RegistryUtil]::LoadHive("ANSIBLE", $hive)
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to load registry hive from '$hive' to HKLM:\ANSIBLE: $($_.Exception.Message)"
}
}
if ($change_value) {
if (-not $check_mode) {
try {
(Get-Item -Path $path).OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree).SetValue($name, $data, $type)
} catch {
Fail-Json $result "failed to change registry property '$name' at $($path): $($_.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
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]
"@
}
}
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 {
$reg_key.OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree).SetValue($name, $data, $type)
} 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 {
$reg_key.OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree).SetValue($name, $data, $type)
} 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 = "+"
@ -297,76 +493,66 @@ if ($state -eq "present") {
$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) {
try {
(Get-Item -Path $path).OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree).SetValue($name, $data, $type)
} catch {
Fail-Json $result "failed to change registry property '$name' at $($path): $($_.Exception.Message)"
}
}
$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)"
"@
}
}
} 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)
# cannot use Remove-ItemProperty as it fails when deleting the (Default) key ($name = $null)
if (-not $check_mode) {
try {
(Get-Item -Path $path).OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree).DeleteValue($name)
} catch {
Fail-Json $result "failed to delete registry property '$name' at $($path): $($_.Exception.Message)"
}
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 {
$reg_key.OpenSubKey($null, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree).DeleteValue($name)
} 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)"
"@
}
}
}
}
}
} finally {
if ($hive) {
[GC]::Collect()
try {
[Ansible.RegistryUtil]::UnloadHive("ANSIBLE")
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)"
}
}
}
Exit-Json $result