]> insang Git - newton-cn_pos.git/blob
292a744e6d30c85bcecd3a2c8305bdc2b3721948
[newton-cn_pos.git] /
1 # This PS module contains functions for Desired State Configuration (DSC) Registry provider. It enables querying, creation, removal and update of Windows registry keys through Get, Set and Test operations on DSC managed nodes.
2
3 # Fallback message strings in en-US
4 DATA localizedData
5 {
6     # culture = "en-US"
7     ConvertFrom-StringData @'                
8         ParameterValueInvalid = (ERROR) Parameter '{0}' has an invalid value '{1}' for type '{2}'
9         InvalidPSDriveSpecified = (ERROR) Invalid PSDrive '{0}' specified in registry key '{1}'
10         InvalidRegistryHiveSpecified = (ERROR) Invalid registry hive was specified in registry key '{0}'
11         SetRegValueFailed = (ERROR) Failed to set registry key value '{0}' to value '{1}' of type '{2}'
12         SetRegValueUnchanged = (UNCHANGED) No change to registry key value '{0}' containing '{1}'
13         SetRegKeyUnchanged = (UNCHANGED) No change to registry key '{0}'
14         SetRegValueSucceeded = (SET) Set registry key value '{0}' to '{1}' of type '{2}'
15         SetRegKeySucceeded = (SET) Create registry key '{0}'
16         SetRegKeyFailed = (ERROR) Failed to created registry key '{0}'
17         RemoveRegKeyTreeFailed = (ERROR) Registry Key '{0}' has subkeys, cannot remove without Force flag
18         RemoveRegKeySucceeded = (REMOVAL) Registry key '{0}' removed
19         RemoveRegKeyFailed = (ERROR) Failed to remove registry key '{0}'
20         RemoveRegValueSucceeded = (REMOVAL) Registry key value '{0}' removed
21         RemoveRegValueFailed = (ERROR) Failed to remove registry key value '{0}'
22         RegKeyDoesNotExist = Registry key '{0}' does not exist
23         RegKeyExists = Registry key '{0}' exists
24         RegValueExists = Found registry key value '{0}' with type '{1}' and data '{2}'
25         RegValueDoesNotExist = Registry key value '{0}' does not exist        
26         RegValueTypeMismatch = Registry key value '{0}' of type '{1}' does not exist
27         RegValueDataMismatch = Registry key value '{0}' of type '{1}' does not contain data '{2}'
28         DefaultValueDisplayName = (Default)
29 '@
30 }
31 Import-LocalizedData LocalizedData -filename MSFT_RegistryResource.strings.psd1
32
33 #--------------------------------------
34 # The Get-TargetResourceInternal cmdlet
35 #--------------------------------------
36 FUNCTION Get-TargetResourceInternal
37 {    
38         param
39         (       
40         [parameter(Mandatory = $true)]          
41                 [ValidateNotNullOrEmpty()]
42                 [System.String]
43                 $Key,
44                                 
45         # Default is [String]::Empty to cater for the (Default) RegValue
46                 [System.String]
47                 $ValueName = [String]::Empty
48         )
49
50     # Perform any required setup steps for the provider
51     SetupProvider -KeyName ([ref]$Key)
52
53     $ValueNameSpecified = $PSBoundParameters.ContainsKey("ValueName")
54
55     # First check if the specified key exists
56     $keyInfo = Get-Item -Path $Key -ErrorAction SilentlyContinue
57  
58     # If $keyInfo is $null, the registry key doesn't exist
59     if ($keyInfo -eq $null)
60     {
61         Write-Verbose ($localizedData.RegKeyDoesNotExist -f $Key)
62            
63         $retVal = @{Ensure='Absent'; Key=$Key}        
64
65         return $retVal
66     }
67
68     # If the control reaches here, the key has been found at least
69     $retVal = @{Ensure='Present'; Key=$Key; Data=$keyInfo}
70
71     # If $ValueName parameter has not been specified then we simply report success on finding the $Key
72     if (!$ValueNameSpecified)
73     {
74         Write-Verbose ($localizedData.RegKeyExists -f $Key)
75
76         return $retVal
77     }
78
79     # If the control reaches here, the $ValueName has been specified as a parameter and we should query it now
80     $valData = $keyInfo.GetValue($ValueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
81
82     # If $ValueName is not found in the specified $Key
83     if($valData -eq $null)
84     {
85         Write-Verbose ($localizedData.RegValueDoesNotExist -f "$Key\$ValueName") 
86
87         $retVal = @{Ensure='Absent'; Key=$Key; ValueName=(GetValueDisplayName -ValueName $ValueName)}        
88
89         return $retVal
90     }
91
92     # Finalize name, type and data to be returned
93     $finalName = GetValueDisplayName -ValueName $ValueName
94     $finalType = $keyInfo.GetValueKind($ValueName)
95     $finalData = $valData
96     
97     # Special case: For Binary type data we convert the received bytes back to a readable hex-strin
98     if ($finalType -ieq "Binary")
99     {
100         $finalData = ConvertByteArrayToHexString -Data $valData    
101     }
102
103     # Populate all config in the return object        
104     $retVal.ValueName = $finalName
105     $retVal.ValueType = $finalType
106     $retVal.Data =  $finalData
107
108     # If the control reaches here, both the $Key and the $ValueName have been found, query is fully successful
109     Write-Verbose ($localizedData.RegValueExists -f "$Key\$ValueName", $retVal.ValueType, (ArrayToString $retVal.Data))
110
111     return $retVal
112 }
113
114 #------------------------------
115 # The Get-TargetResource cmdlet
116 #------------------------------
117 FUNCTION Get-TargetResource
118 {    
119         param
120         (       
121         [Parameter(Mandatory)]
122                 [ValidateNotNullOrEmpty()]
123                 [System.String]
124                 $Key,
125                         
126         [Parameter(Mandatory)]
127         [ValidateNotNull()]
128         [AllowEmptyString()]
129         [System.String]
130                 $ValueName,
131
132         # Special-case: Used only as a boolean flag (along with ValueType) to determine if the target entity is the Default Value or the key itself.
133                 [System.String[]]
134                 $ValueData,
135
136         # Special-case: Used only as a boolean flag (along with ValueData) to determine if the target entity is the Default Value or the key itself.
137                 [System.String]
138                 $ValueType
139         )    
140         
141     # If $ValueName is "" and ValueType and ValueData are both not specified, then we target the key itself (not Default Value)
142     if ($ValueName -eq "" -and !$PSBoundParameters.ContainsKey("ValueType") -and !$PSBoundParameters.ContainsKey("ValueData"))
143     {
144         $retVal = Get-TargetResourceInternal -Key $Key
145     }
146     else
147     {
148         $retVal = Get-TargetResourceInternal -Key $Key -ValueName $ValueName
149         
150         if ($retVal.Ensure -eq 'Present')
151         {                                               
152             [string[]]$retVal.ValueData += $retVal.Data
153             
154             if ($retVal.ValueType -ieq "MultiString")
155             {
156                 $retVal.ValueData = $retVal.Data
157             }
158         }        
159     }    
160         
161     $retVal.Remove("Data")
162
163     return $retVal
164 }
165
166
167 #------------------------------
168 # The Set-TargetResource cmdlet
169 #------------------------------
170 FUNCTION Set-TargetResource
171 {
172     [CmdletBinding(SupportsShouldProcess=$true)]
173         param
174         (
175         [Parameter(Mandatory)]
176                 [ValidateNotNullOrEmpty()]
177                 [System.String]
178                 $Key,
179                 
180         [Parameter(Mandatory)] 
181             [ValidateNotNull()]
182         [AllowEmptyString()]
183                 [System.String]
184                 $ValueName,
185         
186         [ValidateSet("Present", "Absent")]
187                 [System.String]
188                 $Ensure = "Present",
189
190         [ValidateNotNull()]
191                 [System.String[]]
192                 $ValueData = @(),
193
194         [ValidateSet("String", "Binary", "DWord", "QWord", "MultiString", "ExpandString")]
195                 [System.String]
196                 $ValueType = "String",
197         
198                 [System.Boolean]
199                 $Hex = $false,
200         
201                 [System.Boolean]
202                 $Force = $false
203         )
204
205     # Perform any required setup steps for the provider
206     SetupProvider -KeyName ([ref]$Key)
207
208     # Query if the RegVal related parameters have been specified    
209     $ValueNameSpecified = $PSBoundParameters.ContainsKey("ValueName")
210     $ValueTypeSpecified = $PSBoundParameters.ContainsKey("ValueType")    
211     $ValueDataSpecified = $PSBoundParameters.ContainsKey("ValueData") 
212     $keyCreated = $false     
213
214     # If an empty string ValueName has been specified and no ValueType and no ValueData has been specified, 
215     # treat this case as if ValueName was not specified and target the Key itself. This is to cater the limitation 
216     # that both Key and ValueName are mandatory now and we must special-case like this to target the Key only.
217     if ($ValueName -eq "" -and !$ValueTypeSpecified -and !$ValueDataSpecified)
218     {
219         $ValueNameSpecified = $false
220     }
221
222     # Now, query the specified key
223     $keyInfo = Get-TargetResourceInternal -Key $Key -Verbose:$false
224
225     # ----------------
226     # ENSURE = PRESENT
227     if ($Ensure -ieq "Present")
228     {       
229         # If key doesn't exist, attempt to create it
230         if ($keyInfo.Ensure -ieq "Absent")
231         {
232             if ($PSCmdlet.ShouldProcess(($localizedData.SetRegKeySucceeded -f "$Key"), $null, $null))
233             {
234                 try
235                 {
236                     $keyInfo = CreateRegistryKey -Key $Key
237                     $keyCreated = $true
238                 }
239                 catch [Exception]
240                 {
241                     Write-Verbose ($localizedData.SetRegKeyFailed -f "$Key")
242
243                     throw
244                 }
245             }
246         }        
247         
248         # If $ValueName, $ValueType and $ValueData are not specified, the simple existence/creation of the Regkey satisfies the Ensure=Present condition, just return        
249         if (!$ValueNameSpecified -and !$ValueDataSpecified -and !$ValueTypeSpecified)
250         {            
251             if (!$keyCreated)
252             {
253                 Write-Log ($localizedData.SetRegKeyUnchanged -f "$Key")
254             }
255
256             return
257         }    
258
259         # If $ValueType and $ValueData are both not specified, but $ValueName is specified, check if the Value exists, if yes return with status unchanged, otherwise report input error
260         if (!$ValueTypeSpecified -and !$ValueDataSpecified -and $ValueNameSpecified)
261         {
262             $valData = $keyInfo.Data.GetValue($ValueName)
263
264             if ($valData -ne $null)
265             {
266                 Write-Log ($localizedData.SetRegValueUnchanged -f "$Key\$ValueName", (ArrayToString -Value $valData)) 
267
268                 return
269             }            
270         }
271
272         # Create a strongly-typed object (in accordance with the specified $ValueType)
273         $setVal = $null
274         GetTypedObject -Type $ValueType -Data $ValueData -Hex $Hex -ReturnValue ([ref]$setVal)
275
276         # Get the appropriate display name for the specified ValueName (to handle the Default RegValue case)
277         $valDisplayName = GetValueDisplayName -ValueName $ValueName
278         
279         if ($PSCmdlet.ShouldProcess(($localizedData.SetRegValueSucceeded -f "$Key\$valDisplayName", (ArrayToString -Value $setVal), $ValueType), $null, $null))
280         {                
281             try
282             {                                          
283                 # Finally set the $ValueName here
284                 [Microsoft.Win32.Registry]::SetValue($keyInfo.Data.Name, $ValueName, $setVal, $ValueType)                
285             }
286             catch [Exception]
287             {
288                 Write-Verbose ($localizedData.SetRegValueFailed -f "$Key\$valDisplayName", (ArrayToString -Value $setVal), $ValueType)
289
290                 throw
291             }
292         }
293     }
294
295     # ---------------
296     # ENSURE = ABSENT
297     elseif ($Ensure -ieq "Absent")
298     {              
299         # If key doesn't exist, no action is required
300         if ($keyInfo.Ensure -ieq "Absent")
301         {
302             Write-Log ($localizedData.RegKeyDoesNotExist -f "$Key")
303
304             return
305         }
306
307         # If the code reaches here, the key exists
308         
309         # If ValueName is "" and ValueType and ValueData have not been specified, target the key for removal
310         if(!$ValueNameSpecified -and !$ValueTypeSpecified -and !$ValueDataSpecified)
311         {
312             # If this is not a Force removal and the Key contains subkeys, report no change and return
313             if (!$Force -and ($keyInfo.Data.SubKeyCount -gt 0))
314             {             
315                 $errorMessage = $localizedData.RemoveRegKeyTreeFailed -f "$Key"
316                 
317                 Write-Log $errorMessage
318
319                 ThrowError -ExceptionName "System.InvalidOperationException" -ExceptionMessage $errorMessage -ExceptionObject $Force -ErrorId "CannotRemoveKeyTreeWithoutForceFlag" -ErrorCategory NotSpecified
320             }
321
322             # If the control reaches here, either the $Force flag was specified or the Regkey has no subkeys. In either case we simply remove it.
323
324             if ($PSCmdlet.ShouldProcess(($localizedData.RemoveRegKeySucceeded -f $Key), $null, $null))
325             {
326                 try
327                 {                                          
328                     # Formulate hiveName and subkeyName compatible with .NET APIs
329                     $hiveName = $keyInfo.Data.PSDrive.Root.Replace("_","").Replace("HKEY","")
330                     $subkeyName = $keyInfo.Data.Name.Substring($keyInfo.Data.Name.IndexOf("\")+1)
331
332                     # Finally remove the subkeytree
333                     [Microsoft.Win32.Registry]::$hiveName.DeleteSubKeyTree($subkeyName)             
334                 }
335                 catch [Exception]
336                 {
337                     Write-Verbose ($localizedData.RemoveRegKeyFailed -f "$Key")
338
339                     throw
340                 }                
341             }
342
343             return
344         }
345
346         # If the control reaches here, ValueName has been specified so a RegValue needs be removed (if found)
347
348         # Get the appropriate display name for the specified ValueName (to handle the Default RegValue case)
349         $valDisplayName = GetValueDisplayName -ValueName $ValueName
350     
351         # Query the specified $ValueName
352         $valData = $keyInfo.Data.GetValue($ValueName)
353
354         # If $ValueName is not found in the specified $Key
355         if($valData -eq $null)
356         {
357             Write-Log ($localizedData.RegValueDoesNotExist -f "$Key\$valDisplayName") 
358
359             return
360         }
361
362         # If the control reaches here, the specified Value has been found and should be removed.
363         
364         if ($PSCmdlet.ShouldProcess(($localizedData.RemoveRegValueSucceeded -f "$Key\$valDisplayName"), $null, $null))
365         {                                                    
366             try
367             {                                          
368                 # Formulate hiveName and subkeyName compatible with .NET APIs                
369                 $hiveName = $keyInfo.Data.PSDrive.Root.Replace("_","").Replace("HKEY","")
370                 $subkeyName = $keyInfo.Data.Name.Substring($keyInfo.Data.Name.IndexOf("\")+1)
371
372                 # Finally open the subkey and remove the RegValue in subkey
373                 $subkey = [Microsoft.Win32.Registry]::$hiveName.OpenSubKey($subkeyName, $true)
374                 $subkey.DeleteValue($ValueName)
375
376             }
377             catch [Exception]
378             {
379                 Write-Verbose ($localizedData.RemoveRegValueFailed -f "$Key\$valDisplayName")
380
381                 throw
382             }
383         }
384     }
385 }
386
387
388 #-------------------------------
389 # The Test-TargetResource cmdlet
390 #-------------------------------
391 FUNCTION Test-TargetResource
392 {
393         param
394         (
395         [parameter(Mandatory)]
396                 [ValidateNotNullOrEmpty()]
397                 [System.String]
398                 $Key,
399                 
400                 [parameter(Mandatory)]
401                 [AllowEmptyString()]
402             [ValidateNotNull()]
403                 [System.String]
404                 $ValueName,
405         
406         [ValidateSet("Present", "Absent")]
407                 [System.String]
408                 $Ensure = "Present",
409
410         [ValidateNotNull()]
411                 [System.String[]]
412                 $ValueData = @(),
413
414         [ValidateSet("String", "Binary", "DWord", "QWord", "MultiString", "ExpandString")]
415                 [System.String]
416                 $ValueType = "String",
417         
418                 [System.Boolean]
419                 $Hex = $false,
420
421         # Force is not used in Test-TargetResource but is required by DSC engine to keep parameter-sets in parity for both SET and TEST
422         [System.Boolean]
423                 $Force = $false
424         )
425
426     # Perform any required setup steps for the provider
427     SetupProvider -KeyName ([ref]$Key)
428
429     # Query if the RegVal related parameters have been specified
430     $ValueNameSpecified = $PSBoundParameters.ContainsKey("ValueName")
431     $ValueTypeSpecified = $PSBoundParameters.ContainsKey("ValueType")    
432     $ValueDataSpecified = $PSBoundParameters.ContainsKey("ValueData")
433
434     # If an empty string ValueName has been specified and no ValueType and no ValueData has been specified, 
435     # treat this case as if ValueName was not specified and target the Key itself. This is to cater the limitation 
436     # that both Key and ValueName are mandatory now and we must special-case like this to target the Key only.
437     if (($ValueName -eq "") -and !$ValueTypeSpecified -and !$ValueDataSpecified)
438     {
439         $ValueNameSpecified = $false
440     }
441
442     # Now, query the specified key
443     $keyInfo = Get-TargetResourceInternal -Key $Key -Verbose:$false    
444
445     # ----------------
446     # ENSURE = PRESENT
447     if ($Ensure -ieq "Present")
448     {              
449         # If key doesn't exist, the test fails
450         if ($keyInfo.Ensure -ieq "Absent")
451         {
452             Write-Verbose ($localizedData.RegKeyDoesNotExist -f $Key)
453
454             return $false
455         }        
456
457         # If $ValueName, $ValueType and $ValueData are not specified, the simple existence of the Regkey satisfies the Ensure=Present condition, test is successful        
458         if (!$ValueNameSpecified -and !$ValueDataSpecified -and !$ValueTypeSpecified)
459         {                        
460             Write-Verbose ($localizedData.RegKeyExists -f $Key)
461
462             return $true
463         }    
464
465         # IF THE CONTROL REACHED HERE, THE KEY EXISTS AND A REGVALUE ATTRIBUTE HAS BEEN SPECIFIED
466
467         # Get the appropriate display name for the specified ValueName (to handle the Default RegValue case)
468         $valDisplayName = GetValueDisplayName -ValueName $ValueName
469
470         # Now query the specified Reg Value        
471         $valData = Get-TargetResourceInternal -Key $Key -ValueName $ValueName -Verbose:$false
472         
473         # If the Value doesn't exist, the test has failed
474         if ($valData.Ensure -ieq "Absent")
475         {
476             Write-Verbose ($localizedData.RegValueDoesNotExist -f "$Key\$valDisplayName")             
477
478             return $false
479         }
480
481         # IF THE CONTROL REACHED HERE, THE KEY EXISTS AND THE SPECIFIED (or Default) VALUE EXISTS
482
483         # If the $ValueType has been specified and it doesn't match the type of the found RegValue, test fails
484         if ($ValueTypeSpecified -and ($ValueType -ine $valData.ValueType))
485         {
486             Write-Verbose ($localizedData.RegValueTypeMismatch -f "$Key\$valDisplayName", $ValueType)             
487
488             return $false                                
489         }
490
491         # If an explicit ValueType has not been specified, given the Value already exists in Registry, assume the ValueType to be of the existing Value
492         if (!$ValueTypeSpecified)
493         {
494             $ValueType = $valData.ValueType
495         }
496
497         # If $ValueData has been specified, match the data of the found Regvalue.
498         if ($ValueDataSpecified -and !(ValueDataMatches -RetrievedValue $valData -ValueType $ValueType -ValueData $ValueData))
499         {
500             # Since the $ValueData specified didn't match the data of the found RegValue, test failed
501             Write-Verbose ($localizedData.RegValueDataMismatch -f "$Key\$valDisplayName", $ValueType, (ArrayToString -Value $ValueData))             
502
503             return $false                    
504         }
505                 
506         # IF THE CONTROL REACHED HERE, ALL TESTS HAVE PASSED FOR THE SPECIFIED REGISTRY VALUE AND IT COMPLETELY MATCHES, REPORT SUCCESS
507
508         Write-Verbose ($localizedData.RegValueExists -f "$Key\$valDisplayName", $valData.ValueType, (ArrayToString -Value $valData.Data))             
509
510         return $true
511     }
512
513     # ---------------
514     # ENSURE = ABSENT
515     elseif ($Ensure -ieq "Absent")
516     {       
517         # If key doesn't exist, test is successful
518         if ($keyInfo.Ensure -ieq "Absent")
519         {
520             Write-Log ($localizedData.RegKeyDoesNotExist -f "$Key")
521
522             return $true
523         }
524
525         # IF CONTROL REACHED HERE, THE SPECIFIED KEY EXISTS
526         
527         # If $ValueName, $ValueType and $ValueData are not specified, the simple existence of the Regkey fails the test
528         if (!$ValueNameSpecified -and !$ValueDataSpecified -and !$ValueTypeSpecified)
529         {                        
530             Write-Verbose ($localizedData.RegKeyExists -f $Key)
531
532             return $false
533         }    
534
535         # IF THE CONTROL REACHED HERE, THE KEY EXISTS AND A REGVALUE ATTRIBUTE HAS BEEN SPECIFIED
536
537         # Get the appropriate display name for the specified ValueName (to handle the Default RegValue case)
538         $valDisplayName = GetValueDisplayName -ValueName $ValueName
539
540         # Now query the specified RegValue        
541         $valData = Get-TargetResourceInternal -Key $Key -ValueName $ValueName -Verbose:$false
542         
543         # If the Value doesn't exist, the test has passed
544         if ($valData.Ensure -ieq "Absent")
545         {
546             Write-Verbose ($localizedData.RegValueDoesNotExist -f "$Key\$valDisplayName")             
547
548             return $true
549         }
550
551         # IF THE CONTROL REACHED HERE, THE KEY EXISTS AND THE SPECIFIED (or Default) VALUE EXISTS, THUS REPORT FAILURE
552
553         Write-Verbose ($localizedData.RegValueExists -f "$Key\$valDisplayName", $valData.ValueType, (ArrayToString -Value $valData.Data))             
554
555         return $false
556     }
557 }
558
559
560 #--------------------------------------------
561 # Utility to create an arbitrary registry key
562 #--------------------------------------------
563 FUNCTION CreateRegistryKey
564 {    
565         param
566         (       
567                 [parameter(Mandatory = $true)]
568                 [ValidateNotNullOrEmpty()]
569                 [System.String]
570                 $Key
571     )
572
573     # Trim any "\" back-slash(es) at the end of the specified RegKey
574     $Key = ([string]$Key).TrimEnd('\')
575
576     # Extract the parent-key            
577     $slashIndex = $Key.LastIndexOf('\')
578     $parentKey = $Key.Substring(0, $slashIndex)
579         
580     # Check if the parent-key exists, if not first create that (recurse).
581     if ((Get-TargetResourceInternal -Key $parentKey -Verbose:$false).Ensure -eq "Absent")   
582     {
583         CreateRegistryKey -Key $parentKey | Out-Null
584     }
585
586     # Create the Regkey
587     $retVal = New-Item -Path $Key 2>&1
588
589     # Report any errors
590     if ($retVal -and $retVal.GetType().Name -ieq "ErrorRecord")
591     {
592          throw $retVal
593     }
594
595     # If the control reaches here, the key was created successfully
596     return (Get-TargetResourceInternal -Key $Key -Verbose:$false)
597 }
598
599
600 #-------------------------------------------
601 # Validate PSDrive specified in Registry Key
602 #-------------------------------------------
603 FUNCTION ValidatePSDrive
604 {    
605         param
606         (       
607                 [parameter(Mandatory = $true)]
608                 [ValidateNotNullOrEmpty()]
609                 [System.String]
610                 $Key
611     )
612
613     # Extract the PSDriveName from the specified Key
614     $psDriveName = $Key.Substring(0, $Key.IndexOf(':'))
615
616     # Query the specified PSDrive
617     $psDrive = Get-PSDrive $psDriveName -ErrorAction SilentlyContinue
618
619     # Validate that the specified psdrive is a valid  
620     if (($psDrive -eq $null) -or ($psDrive.Provider -eq $null) -or ($psDrive.Provider.Name -ine "Registry") -or !(IsValidRegistryRoot -PSDriveRoot $psDrive.Root))
621     {
622         $errorMessage = $localizedData.InvalidPSDriveSpecified -f $psDriveName, $Key
623         ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $errorMessage -ExceptionObject $Key -ErrorId "InvalidPSDrive" -ErrorCategory InvalidArgument
624     }
625 }
626
627
628 #--------------------------------------------------
629 # Check if the PSDriveRoot is a valid registry root
630 #--------------------------------------------------
631 FUNCTION IsValidRegistryRoot
632 {    
633         param
634         (                                       
635                 [System.String]
636                 $PSDriveRoot
637     )
638
639     # List of valid registry roots
640     $validRegistryRoots = @("HKEY_CLASSES_ROOT", "HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE", "HKEY_USERS", "HKEY_CURRENT_CONFIG")
641
642     # Extract the base of the PSDrive root
643     if ($PSDriveRoot.Contains('\'))
644     {
645         $PSDriveRoot = $PSDriveRoot.Substring(0, $PSDriveRoot.IndexOf('\'))
646     }
647
648     return ($validRegistryRoots -icontains $PSDriveRoot)    
649 }
650
651
652 #----------------------------------------
653 # Utility to write WhatIf or Verbose logs
654 #----------------------------------------
655 FUNCTION Write-Log
656 {
657     [CmdletBinding(SupportsShouldProcess=$true)]
658         param
659         (       
660                 [parameter(Mandatory = $true)]
661                 [ValidateNotNullOrEmpty()]
662                 [System.String]
663                 $Message
664     )
665
666     if ($PSCmdlet.ShouldProcess($Message, $null, $null))
667     {
668         Write-Verbose $Message        
669     }    
670 }
671
672
673 #------------------------------------
674 # Utility to throw an error/exception
675 #------------------------------------
676 FUNCTION ThrowError
677 {
678     [CmdletBinding()]
679     param
680     (        
681         [parameter(Mandatory = $true)]
682                 [ValidateNotNullOrEmpty()]
683                 [System.String]        
684         $ExceptionName,
685
686         [parameter(Mandatory = $true)]
687                 [ValidateNotNullOrEmpty()]
688                 [System.String]
689         $ExceptionMessage,
690         
691                 [System.Object]
692         $ExceptionObject,
693         
694         [parameter(Mandatory = $true)]
695         [ValidateNotNullOrEmpty()]
696         [System.String]
697         $ErrorId,
698
699         [parameter(Mandatory = $true)]
700         [ValidateNotNull()]
701         [System.Management.Automation.ErrorCategory]
702         $ErrorCategory
703     )
704         
705     $exception = New-Object $ExceptionName $ExceptionMessage;
706     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject
707     throw $errorRecord
708 }
709
710
711 #----------------------------------------------------------------------
712 # Utility to construct a strongly-typed object based on specified $Type
713 #----------------------------------------------------------------------
714 FUNCTION GetTypedObject
715 {
716     param
717         (               
718                 [parameter(Mandatory = $true)]
719                 [ValidateNotNullOrEmpty()]
720                 [System.String]
721                 $Type,
722                 
723                 [System.String[]]
724                 $Data,
725
726                 [ValidateNotNull()]
727                 [Boolean]
728                 $Hex,
729
730         [ref] $ReturnValue
731     )
732
733     $ArgumentExceptionScriptBlock = 
734     {
735         Param($ErrorId)
736
737         $errorMessage = $localizedData.ParameterValueInvalid -f "ValueData", (ArrayToString -Value $Data), $Type
738         Write-Verbose $errorMessage
739         ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $errorMessage -ExceptionObject $Data -ErrorId $ErrorId -ErrorCategory InvalidArgument
740     }
741
742     # The the $Type specified is not a multistring then we always expect a non-array $Data. If this is not the case, throw an error and let the user know.
743     if (($Type -ine "Multistring") -and ($Data -ne $null) -and ($Data.Count -gt 1))
744     {
745         Invoke-Command -ScriptBlock $ArgumentExceptionScriptBlock -ArgumentList ([String]::Format("ArrayNotExpectedForType{0}", $Type))    
746     }
747
748     Switch($Type)
749     {
750         # Case: String
751         "String"
752         {
753             if (($Data -eq $null) -or ($Data.Length -eq 0))
754             {
755                 $ReturnValue.Value = [String]::Empty
756
757                 return
758             }
759
760             $ReturnValue.Value = [String]$Data[0]            
761         }
762
763         # Case: ExpandString
764         "ExpandString"
765         {
766             if (($Data -eq $null) -or ($Data.Length -eq 0))
767             {
768                 $ReturnValue.Value = [String]::Empty
769                 
770                 return
771             }
772
773             $ReturnValue.Value = [String]$Data[0]            
774         }
775
776         # Case: MultiString
777         "MultiString"
778         {                        
779             if (($Data -eq $null) -or ($Data.Length -eq 0))
780             {
781                 $ReturnValue.Value = [String[]]@()
782
783                 return
784             }
785
786             $ReturnValue.Value = [String[]]$Data
787         }
788
789         # Case: DWord
790         "DWord"
791         {
792             if (($Data -eq $null) -or ($Data.Length -eq 0))
793             {
794                 $ReturnValue.Value = [Int32]0                
795             }
796             elseif ($Hex)
797             {
798                 $retVal = $null
799                 $val = $Data[0].TrimStart("0x")
800                     
801                 if ([Int32]::TryParse($val, "HexNumber", [System.Globalization.CultureInfo]::CurrentCulture, [ref] $retVal))
802                 {
803                     $ReturnValue.Value = $retVal                    
804                 }
805                 else
806                 {
807                     Invoke-Command -ScriptBlock $ArgumentExceptionScriptBlock -ArgumentList "ValueDataNotInHexFormat"
808                 }
809             }
810             else
811             {
812                 $ReturnValue.Value = [Int32]::Parse($Data[0])                
813             }
814         }
815
816         # Case: QWord
817         "QWord"
818         {
819             if (($Data -eq $null) -or ($Data.Length -eq 0))
820             {
821                 $ReturnValue.Value = [Int64]0                
822             }
823             elseif ($Hex)
824             {                
825                 $retVal = $null
826                 $val = $Data[0].TrimStart("0x")
827                     
828                 if ([Int64]::TryParse($val, "HexNumber", [System.Globalization.CultureInfo]::CurrentCulture, [ref] $retVal))
829                 {
830                     $ReturnValue.Value = $retVal
831                 }
832                 else
833                 {
834                     Invoke-Command -ScriptBlock $ArgumentExceptionScriptBlock -ArgumentList "ValueDataNotInHexFormat"
835                 }                                   
836             }
837             else
838             {
839                 $ReturnValue.Value = [Int64]::Parse($Data[0])
840             }
841         }
842
843         # Case: Binary
844         "Binary"
845         {
846             if (($Data -eq $null) -or ($Data.Length -eq 0))
847             {
848                 $ReturnValue.Value = [Byte[]]@()
849
850                 return
851             }
852
853             $binaryVal = $null
854             $val = $Data[0].TrimStart("0x")
855             if ($val.Length % 2 -ne 0)
856             {
857                 $val = $val.PadLeft($val.Length+1, "0")
858             }
859             
860             try
861             {
862                 $byteArray = [Byte[]]@()
863
864                 for ($i = 0 ; $i -lt ($val.Length-1) ; $i = $i+2)
865                 {
866                     $byteArray += [Byte]::Parse($val.Substring($i, 2), "HexNumber")                                    
867                 }
868
869                 $ReturnValue.Value = [Byte[]]$byteArray
870             }
871             catch [Exception]
872             {
873                 Invoke-Command -ScriptBlock $ArgumentExceptionScriptBlock -ArgumentList "ValueDataNotInHexFormat"
874             }
875         }
876     }    
877 }
878
879
880 #-------------------------------------------------------
881 # Utility to convert an array to a string representation
882 #-------------------------------------------------------
883 FUNCTION ArrayToString
884 {    
885         param
886         (       
887                 [parameter(Mandatory = $true)]
888         [AllowEmptyCollection()]
889                 [ValidateNotNull()]
890                 [object[]]
891                 $Value
892     )
893
894     if (!$Value.GetType().IsArray)
895     {
896         return $Value.ToString()
897     }
898     if ($Value.Length -eq 1)
899     {
900         return $Value[0].ToString()
901     }
902
903     [System.Text.StringBuilder]$retString = "("    
904
905     $Value | % {$retString = ($retString.ToString() + $_.ToString() + ", ")}
906
907     $retString = $retString.ToString().TrimEnd(", ") + ")"
908     
909     return $retString.ToString()    
910 }
911
912
913 #-------------------------------------------------------
914 # Utility to convert an array to a string representation
915 #-------------------------------------------------------
916 FUNCTION ConvertByteArrayToHexString
917 {    
918         param
919         (       
920                 [parameter(Mandatory = $true)]
921                 [ValidateNotNull()]
922                 [System.Object]
923                 $Data
924     )
925
926     $retString = ""
927     $Data | % {$retString += [String]::Format("{0:x2}", $_)}
928
929     return $retString
930 }
931
932
933 #--------------------------------------------------------------
934 # Utility to handle the display name for the (Default) RegValue
935 #--------------------------------------------------------------
936 FUNCTION GetValueDisplayName
937 {    
938         param
939         (       
940                 [System.String]
941                 $ValueName
942     )
943
944     if ([String]::IsNullOrEmpty($ValueName))
945     {
946         return $localizedData.DefaultValueDisplayName
947     }   
948
949     return $ValueName
950 }
951
952
953 #---------------------------------------------------------
954 # Utility to mount the optional Registry hives as PSDrives
955 #---------------------------------------------------------
956 FUNCTION MountRequiredRegistryHives
957 {
958     param
959         (               
960         [parameter(Mandatory = $true)]
961                 [ValidateNotNullOrEmpty()]
962                 [System.String]
963                 $KeyName
964     )           
965
966     $psDriveNames = (Get-PSDrive).Name.ToUpperInvariant()
967
968     if ($KeyName.StartsWith("HKCR","OrdinalIgnoreCase") -and !$psDriveNames.Contains("HKCR"))
969     {
970         New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT -Scope "Script" -WhatIf:$false | Out-Null
971     }
972     elseif ($KeyName.StartsWith("HKUS","OrdinalIgnoreCase") -and !$psDriveNames.Contains("HKUS"))
973     {
974         New-PSDrive -Name HKUS -PSProvider Registry -Root HKEY_USERS -Scope "Script" -WhatIf:$false | Out-Null
975     }
976     elseif ($KeyName.StartsWith("HKCC","OrdinalIgnoreCase") -and !$psDriveNames.Contains("HKCC"))
977     {
978         New-PSDrive -Name HKCC -PSProvider Registry -Root HKEY_CURRENT_CONFIG -Scope "Script" -WhatIf:$false | Out-Null
979     }
980     elseif ($KeyName.StartsWith("HKCU","OrdinalIgnoreCase") -and !$psDriveNames.Contains("HKCU"))
981     {
982         New-PSDrive -Name HKCU -PSProvider Registry -Root HKEY_CURRENT_USER -Scope "Script" -WhatIf:$false | Out-Null
983     }
984     elseif ($KeyName.StartsWith("HKLM","OrdinalIgnoreCase") -and !$psDriveNames.Contains("HKLM"))
985     {
986         New-PSDrive -Name HKLM -PSProvider Registry -Root HKEY_LOCAL_MACHINE -Scope "Script" -WhatIf:$false | Out-Null
987     }
988 }
989
990
991 #---------------------------------------------------------
992 # Utility to mount the optional Registry hives as PSDrives
993 #---------------------------------------------------------
994 FUNCTION SetupProvider
995 {
996     param
997         (               
998         [ValidateNotNull()]             
999                 [ref] $KeyName
1000     )
1001
1002     # Fix $KeyName if required
1003     if (!$KeyName.Value.ToString().Contains(":"))
1004     {
1005         if ($KeyName.Value.ToString().StartsWith("hkey_users","OrdinalIgnoreCase"))
1006         {
1007                 $KeyName.Value =  $KeyName.Value.ToString() -replace "hkey_users", "HKUS:"      
1008         }
1009         elseif ($KeyName.Value.ToString().StartsWith("hkey_current_config","OrdinalIgnoreCase"))
1010         {            
1011                 $KeyName.Value =  $KeyName.Value.ToString() -replace "hkey_current_config", "HKCC:"
1012         }
1013         elseif ($KeyName.Value.ToString().StartsWith("hkey_classes_root","OrdinalIgnoreCase"))
1014         {         
1015                 $KeyName.Value =  $KeyName.Value.ToString() -replace "hkey_classes_root", "HKCR:"
1016         }
1017         elseif ($KeyName.Value.ToString().StartsWith("hkey_local_machine","OrdinalIgnoreCase"))
1018         {         
1019                 $KeyName.Value =  $KeyName.Value.ToString() -replace "hkey_local_machine", "HKLM:"
1020         }
1021         elseif ($KeyName.Value.ToString().StartsWith("hkey_current_user","OrdinalIgnoreCase"))
1022         {         
1023                 $KeyName.Value =  $KeyName.Value.ToString() -replace "hkey_current_user", "HKCU:"
1024         }
1025         else
1026         {
1027             $errorMessage = $localizedData.InvalidRegistryHiveSpecified -f $Key
1028             ThrowError -ExceptionName "System.ArgumentException" -ExceptionMessage $errorMessage -ExceptionObject $KeyName -ErrorId "InvalidRegistryHive" -ErrorCategory InvalidArgument
1029         }        
1030     }    
1031
1032     # Mount any required registry hives
1033     MountRequiredRegistryHives -KeyName $KeyName.Value.ToString()
1034     
1035     # Check the target PSDrive to be a valid Registry Hive root
1036     ValidatePSDrive -Key $KeyName.Value.ToString()            
1037 }
1038
1039 #----------------------------------------------------------------------------------------
1040 # Refactored utility to decide if the ValueData specified matches the ValueData retrieved
1041 #----------------------------------------------------------------------------------------
1042 FUNCTION ValueDataMatches
1043 {
1044         param
1045         (       
1046         [parameter(Mandatory = $true)]
1047                 [ValidateNotNull()]
1048                 [System.Object]
1049                 $RetrievedValue,
1050
1051         [parameter(Mandatory = $true)]
1052                 [ValidateNotNullOrEmpty()]
1053                 [System.String]
1054                 $ValueType,
1055         
1056                 [System.String[]]
1057                 $ValueData
1058     )
1059
1060     # Convert the specified $ValueData into strongly-typed data for correct comparsion            
1061     $specifiedData = $null
1062     $retrievedData = $RetrievedValue.Data
1063
1064     GetTypedObject -Type $ValueType -Data $ValueData -Hex $Hex -ReturnValue ([ref]$specifiedData)
1065
1066     # Special case for binary comparison (do hex-string comparison)
1067     if ($ValueType -ieq "Binary")
1068     {
1069         $specifiedData = $ValueData[0].PadLeft($retrievedData.Length, '0')
1070     }
1071         
1072     # If the ValueType is not multistring, do a simple comparison
1073     if ($ValueType -ine "Multistring")
1074     {
1075         return ($specifiedData -ieq $retrievedData)            
1076     }        
1077
1078     # IF THE CONTROL REACHES HERE, THE ValueType IS A "MultiString" and we need a size-based and element-by-element comparsion for it
1079
1080     # Array-size comparison
1081     if ($specifiedData.Length -ne $retrievedData.Length)
1082     {
1083         # Size mismatch
1084         return $false
1085     }
1086
1087     # Element-by-Element comparison
1088     for ($i = 0 ; $i -lt $specifiedData.Length ; $i++)
1089     {
1090         if ($specifiedData[$i] -ine $retrievedData[$i])
1091         {
1092             return $false
1093         }
1094     }
1095
1096     # IF THE CONTROL REACHED HERE, THE Multistring COMPARISON WAS SUCCESSFUL
1097     return $true    
1098 }
1099
1100 Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource