@ -16,63 +16,72 @@
# You should have received a copy of the GNU General Public License
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
$ErrorActionPreference = " Stop "
# WANT_JSON
# WANT_JSON
# POWERSHELL_COMMON
# POWERSHELL_COMMON
New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR -ErrorAction SilentlyContinue | Out-Null
# TODO: Add missing REG_NONE support
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
$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 ;
$pa th = Get-AnsibleParam -obj $params -name " path " -type " string " -failifempty $true -aliases " key "
$result = New-Object PSObject ;
$ name = Get-AnsibleParam -obj $params -name " name " -type " string " -aliases " entry " , " value "
Set-Attr $result " changed " $false ;
$data = Get-AnsibleParam -obj $params -name " data "
Set-Attr $result " data_changed " $false ;
$type = Get-AnsibleParam -obj $params -name " type " -type " string " -validateSet " binary " , " dword " , " expandstring " , " multistring " , " string " , " qword " -aliases " datatype " -default " string "
Set-Attr $result " data_type_changed " $false ;
$state = Get-AnsibleParam -obj $params -name " state " -type " string " -validateSet " present " , " absent " -default " present "
$registryKey = Get-Attr -obj $params -name " key " -failifempty $true
$result = @ {
$registryValue = Get-Attr -obj $params -name " value " -default $null
changed = $false
$state = Get-Attr -obj $params -name " state " -validateSet " present " , " absent " -default " present "
data_changed = $false
$registryData = Get-Attr -obj $params -name " data " -default $null
data_type_changed = $false
$registryDataType = Get-Attr -obj $params -name " datatype " -validateSet " binary " , " dword " , " expandstring " , " multistring " , " string " , " qword " -default " string "
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 "
Fail-Json $result " missing required argument: data "
}
}
# check the registry key is in powershell ps-drive format: HKLM, HKCU, HKU, HKCR, HCCC
# Fix HCCC:\ PSDrive for pre-2.3 compatibility
If ( -not ( $registryKey -match " ^H[KC][CLU][MURC]{0,1}:\\ " ) )
if ( $path -match " ^HCCC:\\ " ) {
{
$result . warnings + = " Please use path: HKCC:\... instead of path: $path \n "
Fail-Json $result " key: $registryKey is not a valid powershell path, see module documentation for examples. "
$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 (
Param (
[ parameter ( Mandatory = $true ) ]
[ parameter ( Mandatory = $true ) ] [ ValidateNotNullOrEmpty ( ) ] $Path ,
[ ValidateNotNullOrEmpty ( ) ] $Path ,
[ parameter ( Mandatory = $true ) ] [ ValidateNotNullOrEmpty ( ) ] $Name
[ parameter ( Mandatory = $true ) ]
[ ValidateNotNullOrEmpty ( ) ] $Value
)
)
Try {
Get-ItemProperty -Path $Path -Name $Value
try {
Return $tru e
Get-ItemProperty -Path $Path -Name $Nam e
}
return $true
C atch {
} c atch {
R eturn $false
r eturn $false
}
}
}
}
# Returns true if registry data matches.
# Returns true if registry data matches.
# Handles binary, integer(dword) and string registry data
# Handles binary, integer(dword) and string registry data
Function Compare- Registry Data {
Function Compare- Data {
Param (
Param (
[ parameter ( Mandatory = $true ) ]
[ parameter ( Mandatory = $true ) ] [ AllowEmptyString ( ) ] $ReferenceData ,
[ AllowEmptyString ( ) ] $ReferenceData ,
[ parameter ( Mandatory = $true ) ] [ AllowEmptyString ( ) ] $DifferenceData
[ parameter ( Mandatory = $true ) ]
[ AllowEmptyString ( ) ] $DifferenceData
)
)
if ( $ReferenceData -is [ String ] -or $ReferenceData -is [ int ] ) {
if ( $ReferenceData -is [ String ] -or $ReferenceData -is [ int ] ) {
@ -94,143 +103,196 @@ Function Compare-RegistryData {
# https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert
# 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,
# 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
# and converts to a byte array so powershell can modify binary registry entries
function Convert-RegExportHexStringToByteArray
function Convert-RegExportHexStringToByteArray {
{
Param (
Param (
[ parameter ( Mandatory = $true ) ] [ String ] $String
[ parameter ( Mandatory = $true ) ] [ String ] $String
)
)
# remove 'hex:' from the front of the string if present
# Remove 'hex:' from the front of the string if present
$String = $String . ToLower ( ) -replace '^hex\:' , ''
$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.
# Turn commas into colons
$String = $String . ToLower ( ) -replace '[^a-f0-9\\,x\-\:]' , ''
$String = $String -replace ' ,', ' : '
# turn commas into colons
# Maybe there's nothing left over to convert...
$String = $String -replace ',' , ':'
if ( $String . Length -eq 0 ) {
return , @ ( )
}
#Maybe there's nothing left over to convert...
# Split string with or without colon delimiters.
if ( $String . Length -eq 0 ) { , @ ( ) ; return }
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 , @ ( )
}
}
#Split string with or without colon delimiters.
# Create the required PSDrives if missing
if ( $String . Length -eq 1 )
if ( -not ( Test-Path HKCR : \ ) ) {
{ , @ ( [ System.Convert ] :: ToByte ( $String , 16 ) ) }
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT
elseif ( ( $String . Length % 2 -eq 0 ) -and ( $String . IndexOf ( " : " ) -eq -1 ) )
}
{ , @ ( $String -split '([a-f0-9]{2})' | foreach-object { if ( $_ ) { [ System.Convert ] :: ToByte ( $_ , 16 ) } } ) }
if ( -not ( Test-Path HKU : \ ) ) {
elseif ( $String . IndexOf ( " : " ) -ne -1 )
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS
{ , @ ( $String -split ':+' | foreach-object { [ System.Convert ] :: ToByte ( $_ , 16 ) } ) }
}
else
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 )
}
}
if ( $registryDataType -eq " binary " -and $registryData -ne $null -and $registryData -is [ String ] ) {
# Expand string if type expandstring
$registryData = Convert-RegExportHexStringToByteArray ( $registryData )
if ( $type -eq " expandstring " -and $data -ne $null -and $data -is [ String ] ) {
$data = Expand-Environment ( $data )
$datatype = " string "
}
}
if ( $state -eq " present " ) {
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) " ) {
if ( ( Test-Path $path ) -and $name -ne $null ) {
# Special case handling for the key's default property. Because .GetValueKind() doesn't work for the (default) key property
$oldRegistryDataType = " String "
if ( Test-ValueData -Path $path -Name $name ) {
}
else {
# Handle binary data
$oldRegistryDataType = ( Get-Item $registryKey ) . GetValueKind ( $registryValue )
$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 )
}
}
if ( $type -ne $old_type ) {
# Changes Data and DataType
# Changes Data and DataType
if ( $registryDataType -ne $oldRegistryDataType )
if ( -not $check_mode ) {
{
try {
Try
Remove-ItemProperty -Path $path -Name $name
{
New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type -Force
Remove-ItemProperty -Path $registryKey -Name $registryValue
} catch {
New-ItemProperty -Path $registryKey -Name $registryValue -Value $registryData -PropertyType $registryDataType
Fail-Json $result $_ . Exception . Message
}
}
$result . changed = $true
$result . changed = $true
$result . data_changed = $true
$result . data_changed = $true
$result . data_type_changed = $true
$result . data_type_changed = $true
}
$result . diff . prepared + = @ "
Catch
[ $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
Fail-Json $result $_ . Exception . Message
}
}
}
}
# Changes Only Data
elseif ( -Not ( Compare-RegistryData -ReferenceData $currentRegistryData -DifferenceData $registryData ) )
{
Try {
Set-ItemProperty -Path $registryKey -Name $registryValue -Value $registryData
$result . changed = $true
$result . changed = $true
$result . data_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
}
}
Catch
{
} else {
if ( -not $check_mode ) {
try {
New-ItemProperty -Path $path -Name $name -Value $data -PropertyType $type
} Catch {
Fail-Json $result $_ . Exception . Message
Fail-Json $result $_ . Exception . Message
}
}
}
}
}
else
{
Try
{
New-ItemProperty -Path $registryKey -Name $registryValue -Value $registryData -PropertyType $registryDataType
$result . changed = $true
$result . changed = $true
$result . diff . prepared + = @ "
[ $path ]
+ " $name " = " $type ` : $data "
" @
}
}
Catch
{
} elseif ( -not ( Test-Path $path ) ) {
Fail-Json $result $_ . Exception . Message
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
if ( $name -ne $null ) {
$result . changed = $true
$result . diff . prepared + = @ "
+ " $name " = " $type ` : $data "
" @
}
}
} else {
# FIXME: Value is null, should we silently ignore this and do nothing ?
}
}
Catch
{
} 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
Fail-Json $result $_ . Exception . Message
}
}
}
}
}
else
{
if ( Test-Path $registryKey )
{
if ( $registryValue -eq $null ) {
Try
{
Remove-Item -Path $registryKey -Recurse
$result . changed = $true
$result . changed = $true
}
$result . diff . prepared + = @ "
Catch
- [ $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
Fail-Json $result $_ . Exception . Message
}
}
}
}
elseif ( Test-RegistryValueData -Path $registryKey -Value $registryValue ) {
Try
{
Remove-ItemProperty -Path $registryKey -Name $registryValue
$result . changed = $true
$result . changed = $true
$result . diff . prepared + = @ "
[ $path ]
- " $name " = " $type ` : $data "
" @
}
}
Catch
} else {
{
# Nothing to do, everything is already as requested
Fail-Json $result $_ . Exception . Message
}
}
}
}
}
}