@@ -23,8 +23,7 @@ function Write-DscTrace {
23
23
if ($PSVersionTable.PSVersion.Major -gt 5 ) {
24
24
$m = Get-Module PSDesiredStateConfiguration - ListAvailable | Sort-Object - Descending | Select-Object - First 1
25
25
$PSDesiredStateConfiguration = Import-Module $m - Force - PassThru
26
- }
27
- else {
26
+ } else {
28
27
$env: PSModulePath = " $env: windir \System32\WindowsPowerShell\v1.0\Modules;$env: PSModulePath "
29
28
$PSDesiredStateConfiguration = Import-Module - Name ' PSDesiredStateConfiguration' - RequiredVersion ' 1.1' - Force - PassThru - ErrorAction stop - ErrorVariable $importModuleError
30
29
if (-not [string ]::IsNullOrEmpty($importModuleError )) {
@@ -63,16 +62,14 @@ function Invoke-DscCacheRefresh {
63
62
if ($cache.CacheSchemaVersion -ne $script :CurrentCacheSchemaVersion ) {
64
63
$refreshCache = $true
65
64
" Incompatible version of cache in file '" + $cache.CacheSchemaVersion + " ' (expected '" + $script :CurrentCacheSchemaVersion + " ')" | Write-DscTrace
66
- }
67
- else {
65
+ } else {
68
66
$dscResourceCacheEntries = $cache.ResourceCache
69
67
70
68
if ($dscResourceCacheEntries.Count -eq 0 ) {
71
69
# if there is nothing in the cache file - refresh cache
72
70
$refreshCache = $true
73
71
" Filtered DscResourceCache cache is empty" | Write-DscTrace
74
- }
75
- else {
72
+ } else {
76
73
" Checking cache for stale PSModulePath" | Write-DscTrace
77
74
78
75
$m = $env: PSModulePath -split [IO.Path ]::PathSeparator | ForEach-Object { Get-ChildItem - Directory - Path $_ - Depth 1 - ErrorAction Ignore }
@@ -104,8 +101,7 @@ function Invoke-DscCacheRefresh {
104
101
$namedModules.Add ($cacheEntry.DscResourceInfo.ModuleName )
105
102
break
106
103
}
107
- }
108
- else {
104
+ } else {
109
105
" Detected non-existent cache entry '$ ( $_.Name ) '" | Write-DscTrace
110
106
$namedModules.Add ($cacheEntry.DscResourceInfo.ModuleName )
111
107
break
@@ -123,8 +119,7 @@ function Invoke-DscCacheRefresh {
123
119
}
124
120
}
125
121
}
126
- }
127
- else {
122
+ } else {
128
123
" Cache file not found '$cacheFilePath '" | Write-DscTrace
129
124
$refreshCache = $true
130
125
}
@@ -159,8 +154,7 @@ function Invoke-DscCacheRefresh {
159
154
$filteredResources += $dscResources | Where-Object - Property ModuleName -NE $null | ForEach-Object { [System.String ]::Concat($_.ModuleName , ' /' , $_.Name ) }
160
155
# Exclude the one module that was passed in as a parameter
161
156
$existingDscResourceCacheEntries = @ ($cache.ResourceCache | Where-Object - Property Type -NotIn $filteredResources )
162
- }
163
- else {
157
+ } else {
164
158
# if no module is specified, get all resources
165
159
$DscResources = Get-DscResource
166
160
$Modules = Get-Module - ListAvailable
@@ -194,16 +188,14 @@ function Invoke-DscCacheRefresh {
194
188
$dscResource.PSObject.Properties | ForEach-Object - Process {
195
189
if ($null -ne $_.Value ) {
196
190
$DscResourceInfo .$ ($_.Name ) = $_.Value
197
- }
198
- else {
191
+ } else {
199
192
$DscResourceInfo .$ ($_.Name ) = ' '
200
193
}
201
194
}
202
195
203
196
if ($dscResource.ModuleName ) {
204
197
$moduleName = $dscResource.ModuleName
205
- }
206
- elseif ($binaryBuiltInModulePaths -contains $dscResource.ParentPath ) {
198
+ } elseif ($binaryBuiltInModulePaths -contains $dscResource.ParentPath ) {
207
199
$moduleName = ' PSDesiredStateConfiguration'
208
200
$DscResourceInfo.Module = ' PSDesiredStateConfiguration'
209
201
$DscResourceInfo.ModuleName = ' PSDesiredStateConfiguration'
@@ -212,8 +204,7 @@ function Invoke-DscCacheRefresh {
212
204
if ($PSVersionTable.PSVersion.Major -le 5 -and $DscResourceInfo.ImplementedAs -eq ' Binary' ) {
213
205
$DscResourceInfo.ImplementationDetail = ' Binary'
214
206
}
215
- }
216
- elseif ($binaryBuiltInModulePaths -notcontains $dscResource.ParentPath -and $null -ne $dscResource.ParentPath ) {
207
+ } elseif ($binaryBuiltInModulePaths -notcontains $dscResource.ParentPath -and $null -ne $dscResource.ParentPath ) {
217
208
# workaround: populate module name from parent path that is three levels up
218
209
$moduleName = Split-Path $dscResource.ParentPath | Split-Path | Split-Path - Leaf
219
210
$DscResourceInfo.Module = $moduleName
@@ -230,11 +221,15 @@ function Invoke-DscCacheRefresh {
230
221
if ($classBased -and ($classBased.CustomAttributes.AttributeType.Name -eq ' DscResourceAttribute' )) {
231
222
" Detected class-based resource: $ ( $dscResource.Name ) => Type: $ ( $classBased.BaseType.FullName ) " | Write-DscTrace
232
223
$dscResourceInfo.ImplementationDetail = ' ClassBased'
224
+ $properties = GetClassBasedProperties - filePath $dscResource.Path - className $dscResource.Name
225
+ if ($null -ne $properties ) {
226
+ $DscResourceInfo.Properties = $properties
227
+ }
233
228
}
234
229
235
230
# fill in resource files (and their last-write-times) that will be used for up-do-date checks
236
231
$lastWriteTimes = @ {}
237
- Get-ChildItem - Recurse - File - Path $dscResource.ParentPath - Include " *.ps1" , " *.psd1" , " *.psm1" , " *.mof" - ea Ignore | % {
232
+ Get-ChildItem - Recurse - File - Path $dscResource.ParentPath - Include " *.ps1" , " *.psd1" , " *.psm1" , " *.mof" - ea Ignore | ForEach-Object {
238
233
$lastWriteTimes.Add ($_.FullName , $_.LastWriteTime.ToFileTime ())
239
234
}
240
235
@@ -254,7 +249,7 @@ function Invoke-DscCacheRefresh {
254
249
255
250
[dscResourceCache ]$cache = [dscResourceCache ]::new()
256
251
$cache.ResourceCache = $dscResourceCacheEntries.ToArray ()
257
- $m = $env: PSModulePath -split [IO.Path ]::PathSeparator | % { Get-ChildItem - Directory - Path $_ - Depth 1 - ea SilentlyContinue }
252
+ $m = $env: PSModulePath -split [IO.Path ]::PathSeparator | ForEach-Object { Get-ChildItem - Directory - Path $_ - Depth 1 - ea SilentlyContinue }
258
253
$cache.PSModulePaths = $m.FullName
259
254
$cache.CacheSchemaVersion = $script :CurrentCacheSchemaVersion
260
255
@@ -361,12 +356,10 @@ function Invoke-DscOperation {
361
356
exit 1
362
357
}
363
358
$property .$ ($_.Name ) = [System.Management.Automation.PSCredential ]::new($_.Value.Username , (ConvertTo-SecureString - AsPlainText $_.Value.Password - Force))
364
- }
365
- else {
359
+ } else {
366
360
$property .$ ($_.Name ) = $_.Value.psobject.properties | ForEach-Object - Begin { $propertyHash = @ {} } - Process { $propertyHash [$_.Name ] = $_.Value } - End { $propertyHash }
367
361
}
368
- }
369
- else {
362
+ } else {
370
363
$property [$_.Name ] = $_.Value
371
364
}
372
365
}
@@ -378,16 +371,14 @@ function Invoke-DscOperation {
378
371
379
372
if ($invokeResult.GetType ().Name -eq ' Hashtable' ) {
380
373
$invokeResult.keys | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
381
- }
382
- else {
374
+ } else {
383
375
# the object returned by WMI is a CIM instance with a lot of additional data. only return DSC properties
384
376
$invokeResult.psobject.Properties.name | Where-Object { ' CimClass' , ' CimInstanceProperties' , ' CimSystemProperties' -notcontains $_ } | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
385
377
}
386
378
387
379
# set the properties of the OUTPUT object from the result of Get-TargetResource
388
380
$addToActualState.properties = $ResultProperties
389
- }
390
- catch {
381
+ } catch {
391
382
$_.Exception | Format-List * - Force | Out-String | Write-DscTrace - Operation Debug
392
383
if ($_.Exception.MessageId -eq ' DscResourceNotFound' ) {
393
384
Write-DscTrace - Operation Warn - Message ' For Windows PowerShell, DSC resources must be installed with scope AllUsers'
@@ -418,12 +409,10 @@ function Invoke-DscOperation {
418
409
exit 1
419
410
}
420
411
$dscResourceInstance .$ ($_.Name ) = [System.Management.Automation.PSCredential ]::new($_.Value.Username , (ConvertTo-SecureString - AsPlainText $_.Value.Password - Force))
421
- }
422
- else {
412
+ } else {
423
413
$dscResourceInstance .$ ($_.Name ) = $_.Value.psobject.properties | ForEach-Object - Begin { $propertyHash = @ {} } - Process { $propertyHash [$_.Name ] = $_.Value } - End { $propertyHash }
424
414
}
425
- }
426
- else {
415
+ } else {
427
416
$dscResourceInstance .$ ($_.Name ) = $_.Value
428
417
}
429
418
}
@@ -436,8 +425,7 @@ function Invoke-DscOperation {
436
425
$ValidProperties | ForEach-Object {
437
426
if ($raw_obj .$_ -is [System.Enum ]) {
438
427
$Result [$_ ] = $raw_obj .$_.ToString ()
439
- }
440
- else {
428
+ } else {
441
429
$Result [$_ ] = $raw_obj .$_
442
430
}
443
431
}
@@ -459,8 +447,7 @@ function Invoke-DscOperation {
459
447
$ValidProperties | ForEach-Object {
460
448
if ($raw_obj .$_ -is [System.Enum ]) {
461
449
$Result_obj [$_ ] = $raw_obj .$_.ToString ()
462
- }
463
- else {
450
+ } else {
464
451
$Result_obj [$_ ] = $raw_obj .$_
465
452
}
466
453
}
@@ -469,8 +456,7 @@ function Invoke-DscOperation {
469
456
$addToActualState = $resultArray
470
457
}
471
458
}
472
- }
473
- catch {
459
+ } catch {
474
460
$_.Exception | Format-List * - Force | Out-String | Write-DscTrace - Operation Debug
475
461
if ($_.Exception.MessageId -eq ' DscResourceNotFound' ) {
476
462
Write-DscTrace - Operation Warn - Message ' For Windows PowerShell, DSC resources must be installed with scope AllUsers'
@@ -500,12 +486,10 @@ function Invoke-DscOperation {
500
486
exit 1
501
487
}
502
488
$property .$ ($_.Name ) = [System.Management.Automation.PSCredential ]::new($_.Value.Username , (ConvertTo-SecureString - AsPlainText $_.Value.Password - Force))
503
- }
504
- else {
489
+ } else {
505
490
$property .$ ($_.Name ) = $_.Value.psobject.properties | ForEach-Object - Begin { $propertyHash = @ {} } - Process { $propertyHash [$_.Name ] = $_.Value } - End { $propertyHash }
506
491
}
507
- }
508
- else {
492
+ } else {
509
493
$property [$_.Name ] = $_.Value
510
494
}
511
495
}
@@ -516,16 +500,14 @@ function Invoke-DscOperation {
516
500
$invokeResult = Invoke-DscResource - Method $Operation - ModuleName $cachedDscResourceInfo.ModuleName - Name $cachedDscResourceInfo.Name - Property $property
517
501
if ($invokeResult.GetType ().Name -eq ' Hashtable' ) {
518
502
$invokeResult.keys | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
519
- }
520
- else {
503
+ } else {
521
504
# the object returned by WMI is a CIM instance with a lot of additional data. only return DSC properties
522
505
$invokeResult.psobject.Properties.name | Where-Object { ' CimClass' , ' CimInstanceProperties' , ' CimSystemProperties' -notcontains $_ } | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
523
506
}
524
507
525
508
# set the properties of the OUTPUT object from the result of Get-TargetResource
526
509
$addToActualState.properties = $ResultProperties
527
- }
528
- catch {
510
+ } catch {
529
511
' Exception: ' + $_.Exception.Message | Write-DscTrace - Operation Error
530
512
exit 1
531
513
}
@@ -537,8 +519,7 @@ function Invoke-DscOperation {
537
519
}
538
520
539
521
return $addToActualState
540
- }
541
- else {
522
+ } else {
542
523
$dsJSON = $DesiredState | ConvertTo-Json - Depth 10
543
524
' Can not find type "' + $DesiredState.type + ' " for resource "' + $dsJSON + ' ". Please ensure that Get-DscResource returns this resource type.' | Write-DscTrace - Operation Error
544
525
exit 1
@@ -584,6 +565,72 @@ function ValidateMethod {
584
565
return $method
585
566
}
586
567
568
+ function GetClassBasedProperties {
569
+ param (
570
+ [Parameter (Mandatory = $true )]
571
+ [string ] $filePath ,
572
+
573
+ [Parameter (Mandatory = $true )]
574
+ [string ] $className
575
+ )
576
+
577
+ if (" .psd1" -notcontains ([System.IO.Path ]::GetExtension($filePath ))) {
578
+ return @ (' get' , ' set' , ' test' )
579
+ }
580
+
581
+ $module = Import-Module $filePath - PassThru - Force - ErrorAction Ignore
582
+
583
+ $properties = [System.Collections.Generic.List [DscResourcePropertyInfo ]]::new()
584
+
585
+ if (Test-Path $module.Path - ErrorAction Ignore) {
586
+ [System.Management.Automation.Language.Token []] $tokens = $null
587
+ [System.Management.Automation.Language.ParseError []] $errors = $null
588
+ $ast = [System.Management.Automation.Language.Parser ]::ParseFile($module.Path , [ref ]$tokens , [ref ]$errors )
589
+ foreach ($e in $errors ) {
590
+ $e | Out-String | Write-DscTrace - Operation Warn
591
+ }
592
+
593
+ $typeDefinitions = $ast.FindAll (
594
+ {
595
+ $typeAst = $args [0 ] -as [System.Management.Automation.Language.TypeDefinitionAst ]
596
+ return $null -ne $typeAst ;
597
+ },
598
+ $false );
599
+
600
+ $typeDefinition = $typeDefinitions | Where-Object - Property Name -EQ $className
601
+
602
+ foreach ($member in $typeDefinition.Members ) {
603
+ $property = $member -as [System.Management.Automation.Language.PropertyMemberAst ]
604
+ if (($null -eq $property ) -or ($property.IsStatic )) {
605
+ continue ;
606
+ }
607
+ $skipProperty = $true
608
+ $isKeyProperty = $false
609
+ foreach ($attr in $property.Attributes ) {
610
+ if ($attr.TypeName.Name -eq ' DscProperty' ) {
611
+ $skipProperty = $false
612
+ foreach ($attrArg in $attr.NamedArguments ) {
613
+ if ($attrArg.ArgumentName -eq ' Key' ) {
614
+ $isKeyProperty = $true
615
+ break
616
+ }
617
+ }
618
+ }
619
+ }
620
+ if ($skipProperty ) {
621
+ continue ;
622
+ }
623
+
624
+ [DscResourcePropertyInfo ]$prop = [DscResourcePropertyInfo ]::new()
625
+ $prop.Name = $property.Name
626
+ $prop.PropertyType = $property.PropertyType.TypeName.Name
627
+ $prop.IsMandatory = $isKeyProperty
628
+ $properties.Add ($prop )
629
+ }
630
+ return $properties
631
+ }
632
+ }
633
+
587
634
# cached resource
588
635
class dscResourceCacheEntry {
589
636
[string ] $Type
@@ -612,6 +659,13 @@ enum dscResourceType {
612
659
Composite
613
660
}
614
661
662
+ class DscResourcePropertyInfo {
663
+ [string ] $Name
664
+ [string ] $PropertyType
665
+ [bool ] $IsMandatory
666
+ [System.Collections.Generic.List [string ]] $Values
667
+ }
668
+
615
669
# dsc resource type (settable clone)
616
670
class DscResourceInfo {
617
671
[dscResourceType ] $ImplementationDetail
0 commit comments