| 1 | + | #!powershell |
| 2 | + | |
| 3 | + | # Copyright: (c) 2018, Jordan Borean |
| 4 | + | # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
| 5 | + | |
| 6 | + | #Requires -Module Ansible.ModuleUtils.Legacy |
| 7 | + | |
| 8 | + | $ErrorActionPreference = "Stop" |
| 9 | + | |
| 10 | + | $params = Parse-Args -arguments $args -supports_check_mode $true |
| 11 | + | $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false |
| 12 | + | $diff = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false |
| 13 | + | |
| 14 | + | $gpo = Get-AnsibleParam -obj $params -name "gpo" -type "str" -failifempty $true |
| 15 | + | $name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true |
| 16 | + | $path = Get-AnsibleParam -obj $params -name "path" -type "str" -failifempty $true |
| 17 | + | $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent", "disabled", "present" |
| 18 | + | $type = Get-AnsibleParam -obj $params -name "type" -type "str" -default "string" -ValidateSet "string", "expandstring", "binary", "dword", "multistring", "qword" |
| 19 | + | $value = Get-AnsibleParam -obj $params -name "value" |
| 20 | + | |
| 21 | + | $result = @{ |
| 22 | + | changed = $false |
| 23 | + | } |
| 24 | + | if ($diff) { |
| 25 | + | $result.diff = @{ |
| 26 | + | before = "" |
| 27 | + | after = "" |
| 28 | + | } |
| 29 | + | } |
| 30 | + | |
| 31 | + | if ($state -in @("absent", "disabled") -and $null -ne $value) { |
| 32 | + | Fail-Json -obj $result -message "Cannot set a value with state=$state" |
| 33 | + | } |
| 34 | + | |
| 35 | + | Function Convert-RegExportHexStringToByteArray { |
| 36 | + | <# |
| 37 | + | .SYNOPSIS |
| 38 | + | Simplified version of Convert-HexStringToByteArray from |
| 39 | + | https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert |
| 40 | + | Expects a hex in the format you get when you run reg.exe export and |
| 41 | + | converts to a byte array so powershell can modify binary registry entries |
| 42 | + | |
| 43 | + | .PARAMETER String |
| 44 | + | The string to convert in the format hex:be,ef,be,ef,be,ef,be,ef,be,ef |
| 45 | + | #># |
| 46 | + | param( |
| 47 | + | [Parameter(Mandatory=$true)][String]$String |
| 48 | + | ) |
| 49 | + | # Remove 'hex:' from the front of the string if present |
| 50 | + | $String = $String.ToLower() -replace '^hex\:','' |
| 51 | + | |
| 52 | + | # Remove whitespace and any other non-hex crud. |
| 53 | + | $String = $String -replace '[^a-f0-9\\,x\-\:]','' |
| 54 | + | |
| 55 | + | # Turn commas into colons |
| 56 | + | $String = $String -replace ',',':' |
| 57 | + | |
| 58 | + | # Maybe there's nothing left over to convert... |
| 59 | + | if ($String.Length -eq 0) { |
| 60 | + | return ,@() |
| 61 | + | } |
| 62 | + | |
| 63 | + | # Split string with or without colon delimiters. |
| 64 | + | if ($String.Length -eq 1) { |
| 65 | + | return ,@([System.Convert]::ToByte($String,16)) |
| 66 | + | } elseif (($String.Length % 2 -eq 0) -and ($String.IndexOf(":") -eq -1)) { |
| 67 | + | return ,@($String -split '([a-f0-9]{2})' | ForEach-Object { if ($_) {[System.Convert]::ToByte($_,16)}}) |
| 68 | + | } elseif ($String.IndexOf(":") -ne -1) { |
| 69 | + | return ,@($String -split ':+' | ForEach-Object {[System.Convert]::ToByte($_,16)}) |
| 70 | + | } else { |
| 71 | + | return ,@() |
| 72 | + | } |
| 73 | + | } |
| 74 | + | |
| 75 | + | Function Get-DiffValueString { |
| 76 | + | <# |
| 77 | + | .SYNOPSIS |
| 78 | + | Converts an input value to a string for use in a diff comparison. |
| 79 | + | |
| 80 | + | .PARAMETER Type |
| 81 | + | The registry value type that is used to display the appropriate string |
| 82 | + | output. |
| 83 | + | |
| 84 | + | .PARAMETER Value |
| 85 | + | The value to stringify. |
| 86 | + | #> |
| 87 | + | param( |
| 88 | + | [Parameter(Mandatory=$true)] |
| 89 | + | [ValidateSet("string", "expandstring", "binary", "dword", "multistring", "qword")] |
| 90 | + | [String]$Type, |
| 91 | + | [Parameter(Mandatory=$true)]$Value |
| 92 | + | ) |
| 93 | + | if ($Type -eq "binary") { |
| 94 | + | $hex_values = [System.Collections.ArrayList]@() |
| 95 | + | foreach ($dec_value in $Value) { |
| 96 | + | $hex_values.Add("0x$("{0:x2}" -f $dec_value)") > $null |
| 97 | + | } |
| 98 | + | $diff_value = "[$($hex_values -join ", ")]" |
| 99 | + | } elseif ($Type -eq "dword") { |
| 100 | + | $diff_value = "0x$("{0:x8}" -f $Value)" |
| 101 | + | } elseif ($Type -eq "qword") { |
| 102 | + | $diff_value = "0x$("{0:x16}" -f $Value)" |
| 103 | + | } elseif ($Type -eq "multistring") { |
| 104 | + | $diff_value = "[$($Value -join ", ")]" |
| 105 | + | } else { |
| 106 | + | if ($Value.EndsWith([char]0x0000)) { |
| 107 | + | $Value = $Value.Substring(0, $Value.Length - 1) |
| 108 | + | } |
| 109 | + | |
| 110 | + | $diff_value = $Value |
| 111 | + | } |
| 112 | + | |
| 113 | + | return $diff_value |
| 114 | + | } |
| 115 | + | |
| 116 | + | # Convert the input value to the type specified |
| 117 | + | if ($type -eq "binary") { |
| 118 | + | if ($null -eq $value) { |
| 119 | + | $value = "" |
| 120 | + | } |
| 121 | + | |
| 122 | + | if ($value -is [String]) { |
| 123 | + | $value = [byte[]](Convert-RegExportHexStringToByteArray -String $value) |
| 124 | + | } elseif ($value -is [Int]) { |
| 125 | + | if ($value -gt 255) { |
| 126 | + | Fail-Json -obj $result -message "Cannot convert binary value '$value' to byte array, please specify this value as a yaml byte array or comma separated hex value string" |
| 127 | + | } |
| 128 | + | $value = [byte[]]@([byte]$value) |
| 129 | + | } elseif ($value -is [Array]) { |
| 130 | + | $value = [byte[]]$value |
| 131 | + | } |
| 132 | + | } elseif ($type -in @("dword", "qword")) { |
| 133 | + | # dword's and dword's don't allow null values, set to 0 |
| 134 | + | if ($null -eq $value) { |
| 135 | + | $value = 0 |
| 136 | + | } |
| 137 | + | |
| 138 | + | if ($value -is [String]) { |
| 139 | + | # If the value is a string we need to convert it to an unsigned int64 |
| 140 | + | # it needs to be unsigned as Ansible passes in an unsigned value while |
| 141 | + | # powershell uses a signed value type. The value will then be converted |
| 142 | + | # below |
| 143 | + | $value = [UInt64]$value |
| 144 | + | } |
| 145 | + | |
| 146 | + | if ($type -eq "dword") { |
| 147 | + | if ($value -gt [UInt32]::MaxValue) { |
| 148 | + | Fail-Json -obj $result -message "value cannot be larger than 0xffffffff when type is dword" |
| 149 | + | } elseif ($value -gt [Int32]::MaxValue) { |
| 150 | + | # When dealing with larger int32 (> 2147483647 or 0x7FFFFFFF) PowerShell |
| 151 | + | # automatically converts it to a signed int64. We need to convert this to |
| 152 | + | # signed int32 by parsing the hex string value. |
| 153 | + | $value = "0x$("{0:x}" -f $value)" |
| 154 | + | } |
| 155 | + | $value = [Int32]$value |
| 156 | + | } else { |
| 157 | + | if ($value -gt [UInt64]::MaxValue) { |
| 158 | + | Fail-Json -obj $result -message "value cannot be larger than 0xffffffffffffffff when type is qword" |
| 159 | + | } elseif ($value -gt [Int64]::MaxValue) { |
| 160 | + | $value = "0x$("{0:x}" -f $value)" |
| 161 | + | } |
| 162 | + | $value = [Int64]$value |
| 163 | + | } |
| 164 | + | } elseif ($type -in @("string", "expandstring")) { |
| 165 | + | # A null string or expandstring must be empty quotes |
| 166 | + | if ($null -eq $value) { |
| 167 | + | $value = "" |
| 168 | + | } |
| 169 | + | } elseif ($type -eq "multistring") { |
| 170 | + | # Convert the value for a multistring to a String[] array |
| 171 | + | if ($null -eq $value) { |
| 172 | + | $value = [String[]]@() |
| 173 | + | } elseif ($value -isnot [Array]) { |
| 174 | + | $new_value = New-Object -TypeName String[] -ArgumentList 1 |
| 175 | + | $new_value[0] = $value.ToString([CultureInfo]::InvariantCulture) |
| 176 | + | $value = $new_value |
| 177 | + | } else { |
| 178 | + | $new_value = New-Object -TypeName String[] -ArgumentList $value.Count |
| 179 | + | foreach ($entry in $value) { |
| 180 | + | $new_value[$value.IndexOf($entry)] = $entry.ToString([CultureInfo]::InvariantCulture) |
| 181 | + | } |
| 182 | + | $value = $new_value |
| 183 | + | } |
| 184 | + | } |
| 185 | + | |
| 186 | + | $existing_value = Get-GPRegistryValue -Name $gpo -Key $path -ValueName $name -ErrorAction SilentlyContinue |
| 187 | + | if ($null -ne $existing_value -and $diff) { |
| 188 | + | $result.diff.before = @{ |
| 189 | + | disabled = ($existing_value.PolicyState -eq "Delete") |
| 190 | + | gpo = $gpo |
| 191 | + | name = $existing_value.ValueName |
| 192 | + | path = $existing_value.KeyPath |
| 193 | + | type = $existing_value.Type.ToString() |
| 194 | + | value = (Get-DiffValueString -Type $existing_value.Type.ToString() -Value $existing_value.Value) |
| 195 | + | } |
| 196 | + | } |
| 197 | + | |
| 198 | + | if ($state -eq "absent") { |
| 199 | + | if ($null -ne $existing_value) { |
| 200 | + | Remove-GPRegistryValue -Name $gpo -Key $path -ValueName $name -WhatIf:$check_mode > $null |
| 201 | + | $result.changed = $true |
| 202 | + | } |
| 203 | + | } else { |
| 204 | + | $common_params = @{ |
| 205 | + | Name = $gpo |
| 206 | + | Key = $path |
| 207 | + | ValueName = $name |
| 208 | + | WhatIf = $check_mode |
| 209 | + | } |
| 210 | + | |
| 211 | + | if ($null -eq $existing_value) { |
| 212 | + | if ($state -eq "disabled") { |
| 213 | + | $common_params.Disable = $true |
| 214 | + | } else { |
| 215 | + | $common_params.Value = $value |
| 216 | + | $common_params.Type = $type |
| 217 | + | } |
| 218 | + | |
| 219 | + | Set-GPRegistryValue @common_params > $null |
| 220 | + | $result.changed = $true |
| 221 | + | } elseif ($state -eq "disabled") { |
| 222 | + | if ($existing_value.PolicyState -ne "Delete") { |
| 223 | + | Set-GPRegistryValue -Disable @common_params > $null |
| 224 | + | $result.changed = $true |
| 225 | + | } |
| 226 | + | } elseif ($existing_value.PolicyState -eq "Delete") { |
| 227 | + | # If the previous state was disabled then we need to remove that value |
| 228 | + | # before we set the new one as it will cause double ups on the key |
| 229 | + | Remove-GPRegistryValue @common_params > $null |
| 230 | + | Set-GPRegistryValue -Value $value -Type $type @common_params > $null |
| 231 | + | $result.changed = $true |
| 232 | + | } else { |
| 233 | + | $before_type = $existing_value.Type.ToString() |
| 234 | + | $before_value = Get-DiffValueString -Type $before_type -Value $existing_value.Value |
| 235 | + | $new_value = Get-DiffValueString -Type $type -Value $value |
| 236 | + | |
| 237 | + | if (($before_value -ne $new_value) -or ($before_type -ne $type)) { |
| 238 | + | Set-GPRegistryValue -Value $value -Type $type @common_params > $null |
| 239 | + | $result.changed = $true |
| 240 | + | } |
| 241 | + | } |
| 242 | + | |
| 243 | + | if ($diff) { |
| 244 | + | if ($check_mode) { |
| 245 | + | # in check mode we won't have access to the new values so just used the inputs |
| 246 | + | $result.diff.after = @{ |
| 247 | + | disabled = ($state -eq "disabled") |
| 248 | + | gpo = $gpo |
| 249 | + | name = $name |
| 250 | + | path = $path |
| 251 | + | type = $type |
| 252 | + | value = (Get-DiffValueString -Type $type -Value $value) |
| 253 | + | } |
| 254 | + | } else { |
| 255 | + | $new_value = Get-GPRegistryValue -Name $gpo -Key $path -ValueName $name |
| 256 | + | $result.diff.after = @{ |
| 257 | + | disabled = ($new_value.PolicyState -eq "Delete") |
| 258 | + | gpo = $gpo |
| 259 | + | name = $new_value.ValueName |
| 260 | + | path = $new_value.KeyPath |
| 261 | + | type = $new_value.Type.ToString() |
| 262 | + | value = (Get-DiffValueString -Type $new_value.Type.ToString() -Value $new_value.Value) |
| 263 | + | } |
| 264 | + | } |
| 265 | + | } |
| 266 | + | } |
| 267 | + | |
| 268 | + | Exit-Json -obj $result |
| 269 | + | |
| 270 | + | |