@@ -23,8 +23,7 @@ function Write-DscTrace {
2323if ($PSVersionTable.PSVersion.Major -gt 5 ) {
2424 $m = Get-Module PSDesiredStateConfiguration - ListAvailable | Sort-Object - Descending | Select-Object - First 1
2525 $PSDesiredStateConfiguration = Import-Module $m - Force - PassThru
26- }
27- else {
26+ } else {
2827 $env: PSModulePath = " $env: windir \System32\WindowsPowerShell\v1.0\Modules;$env: PSModulePath "
2928 $PSDesiredStateConfiguration = Import-Module - Name ' PSDesiredStateConfiguration' - RequiredVersion ' 1.1' - Force - PassThru - ErrorAction stop - ErrorVariable $importModuleError
3029 if (-not [string ]::IsNullOrEmpty($importModuleError )) {
@@ -63,16 +62,14 @@ function Invoke-DscCacheRefresh {
6362 if ($cache.CacheSchemaVersion -ne $script :CurrentCacheSchemaVersion ) {
6463 $refreshCache = $true
6564 " Incompatible version of cache in file '" + $cache.CacheSchemaVersion + " ' (expected '" + $script :CurrentCacheSchemaVersion + " ')" | Write-DscTrace
66- }
67- else {
65+ } else {
6866 $dscResourceCacheEntries = $cache.ResourceCache
6967
7068 if ($dscResourceCacheEntries.Count -eq 0 ) {
7169 # if there is nothing in the cache file - refresh cache
7270 $refreshCache = $true
7371 " Filtered DscResourceCache cache is empty" | Write-DscTrace
74- }
75- else {
72+ } else {
7673 " Checking cache for stale PSModulePath" | Write-DscTrace
7774
7875 $m = $env: PSModulePath -split [IO.Path ]::PathSeparator | ForEach-Object { Get-ChildItem - Directory - Path $_ - Depth 1 - ErrorAction Ignore }
@@ -104,8 +101,7 @@ function Invoke-DscCacheRefresh {
104101 $namedModules.Add ($cacheEntry.DscResourceInfo.ModuleName )
105102 break
106103 }
107- }
108- else {
104+ } else {
109105 " Detected non-existent cache entry '$ ( $_.Name ) '" | Write-DscTrace
110106 $namedModules.Add ($cacheEntry.DscResourceInfo.ModuleName )
111107 break
@@ -123,8 +119,7 @@ function Invoke-DscCacheRefresh {
123119 }
124120 }
125121 }
126- }
127- else {
122+ } else {
128123 " Cache file not found '$cacheFilePath '" | Write-DscTrace
129124 $refreshCache = $true
130125 }
@@ -159,8 +154,7 @@ function Invoke-DscCacheRefresh {
159154 $filteredResources += $dscResources | Where-Object - Property ModuleName -NE $null | ForEach-Object { [System.String ]::Concat($_.ModuleName , ' /' , $_.Name ) }
160155 # Exclude the one module that was passed in as a parameter
161156 $existingDscResourceCacheEntries = @ ($cache.ResourceCache | Where-Object - Property Type -NotIn $filteredResources )
162- }
163- else {
157+ } else {
164158 # if no module is specified, get all resources
165159 $DscResources = Get-DscResource
166160 $Modules = Get-Module - ListAvailable
@@ -194,16 +188,14 @@ function Invoke-DscCacheRefresh {
194188 $dscResource.PSObject.Properties | ForEach-Object - Process {
195189 if ($null -ne $_.Value ) {
196190 $DscResourceInfo .$ ($_.Name ) = $_.Value
197- }
198- else {
191+ } else {
199192 $DscResourceInfo .$ ($_.Name ) = ' '
200193 }
201194 }
202195
203196 if ($dscResource.ModuleName ) {
204197 $moduleName = $dscResource.ModuleName
205- }
206- elseif ($binaryBuiltInModulePaths -contains $dscResource.ParentPath ) {
198+ } elseif ($binaryBuiltInModulePaths -contains $dscResource.ParentPath ) {
207199 $moduleName = ' PSDesiredStateConfiguration'
208200 $DscResourceInfo.Module = ' PSDesiredStateConfiguration'
209201 $DscResourceInfo.ModuleName = ' PSDesiredStateConfiguration'
@@ -212,8 +204,7 @@ function Invoke-DscCacheRefresh {
212204 if ($PSVersionTable.PSVersion.Major -le 5 -and $DscResourceInfo.ImplementedAs -eq ' Binary' ) {
213205 $DscResourceInfo.ImplementationDetail = ' Binary'
214206 }
215- }
216- elseif ($binaryBuiltInModulePaths -notcontains $dscResource.ParentPath -and $null -ne $dscResource.ParentPath ) {
207+ } elseif ($binaryBuiltInModulePaths -notcontains $dscResource.ParentPath -and $null -ne $dscResource.ParentPath ) {
217208 # workaround: populate module name from parent path that is three levels up
218209 $moduleName = Split-Path $dscResource.ParentPath | Split-Path | Split-Path - Leaf
219210 $DscResourceInfo.Module = $moduleName
@@ -230,11 +221,15 @@ function Invoke-DscCacheRefresh {
230221 if ($classBased -and ($classBased.CustomAttributes.AttributeType.Name -eq ' DscResourceAttribute' )) {
231222 " Detected class-based resource: $ ( $dscResource.Name ) => Type: $ ( $classBased.BaseType.FullName ) " | Write-DscTrace
232223 $dscResourceInfo.ImplementationDetail = ' ClassBased'
224+ $properties = GetClassBasedProperties - filePath $dscResource.Path - className $dscResource.Name
225+ if ($null -ne $properties ) {
226+ $DscResourceInfo.Properties = $properties
227+ }
233228 }
234229
235230 # fill in resource files (and their last-write-times) that will be used for up-do-date checks
236231 $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 {
238233 $lastWriteTimes.Add ($_.FullName , $_.LastWriteTime.ToFileTime ())
239234 }
240235
@@ -254,7 +249,7 @@ function Invoke-DscCacheRefresh {
254249
255250 [dscResourceCache ]$cache = [dscResourceCache ]::new()
256251 $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 }
258253 $cache.PSModulePaths = $m.FullName
259254 $cache.CacheSchemaVersion = $script :CurrentCacheSchemaVersion
260255
@@ -361,12 +356,10 @@ function Invoke-DscOperation {
361356 exit 1
362357 }
363358 $property .$ ($_.Name ) = [System.Management.Automation.PSCredential ]::new($_.Value.Username , (ConvertTo-SecureString - AsPlainText $_.Value.Password - Force))
364- }
365- else {
359+ } else {
366360 $property .$ ($_.Name ) = $_.Value.psobject.properties | ForEach-Object - Begin { $propertyHash = @ {} } - Process { $propertyHash [$_.Name ] = $_.Value } - End { $propertyHash }
367361 }
368- }
369- else {
362+ } else {
370363 $property [$_.Name ] = $_.Value
371364 }
372365 }
@@ -378,16 +371,14 @@ function Invoke-DscOperation {
378371
379372 if ($invokeResult.GetType ().Name -eq ' Hashtable' ) {
380373 $invokeResult.keys | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
381- }
382- else {
374+ } else {
383375 # the object returned by WMI is a CIM instance with a lot of additional data. only return DSC properties
384376 $invokeResult.psobject.Properties.name | Where-Object { ' CimClass' , ' CimInstanceProperties' , ' CimSystemProperties' -notcontains $_ } | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
385377 }
386378
387379 # set the properties of the OUTPUT object from the result of Get-TargetResource
388380 $addToActualState.properties = $ResultProperties
389- }
390- catch {
381+ } catch {
391382 $_.Exception | Format-List * - Force | Out-String | Write-DscTrace - Operation Debug
392383 if ($_.Exception.MessageId -eq ' DscResourceNotFound' ) {
393384 Write-DscTrace - Operation Warn - Message ' For Windows PowerShell, DSC resources must be installed with scope AllUsers'
@@ -418,12 +409,10 @@ function Invoke-DscOperation {
418409 exit 1
419410 }
420411 $dscResourceInstance .$ ($_.Name ) = [System.Management.Automation.PSCredential ]::new($_.Value.Username , (ConvertTo-SecureString - AsPlainText $_.Value.Password - Force))
421- }
422- else {
412+ } else {
423413 $dscResourceInstance .$ ($_.Name ) = $_.Value.psobject.properties | ForEach-Object - Begin { $propertyHash = @ {} } - Process { $propertyHash [$_.Name ] = $_.Value } - End { $propertyHash }
424414 }
425- }
426- else {
415+ } else {
427416 $dscResourceInstance .$ ($_.Name ) = $_.Value
428417 }
429418 }
@@ -436,8 +425,7 @@ function Invoke-DscOperation {
436425 $ValidProperties | ForEach-Object {
437426 if ($raw_obj .$_ -is [System.Enum ]) {
438427 $Result [$_ ] = $raw_obj .$_.ToString ()
439- }
440- else {
428+ } else {
441429 $Result [$_ ] = $raw_obj .$_
442430 }
443431 }
@@ -459,8 +447,7 @@ function Invoke-DscOperation {
459447 $ValidProperties | ForEach-Object {
460448 if ($raw_obj .$_ -is [System.Enum ]) {
461449 $Result_obj [$_ ] = $raw_obj .$_.ToString ()
462- }
463- else {
450+ } else {
464451 $Result_obj [$_ ] = $raw_obj .$_
465452 }
466453 }
@@ -469,8 +456,7 @@ function Invoke-DscOperation {
469456 $addToActualState = $resultArray
470457 }
471458 }
472- }
473- catch {
459+ } catch {
474460 $_.Exception | Format-List * - Force | Out-String | Write-DscTrace - Operation Debug
475461 if ($_.Exception.MessageId -eq ' DscResourceNotFound' ) {
476462 Write-DscTrace - Operation Warn - Message ' For Windows PowerShell, DSC resources must be installed with scope AllUsers'
@@ -500,12 +486,10 @@ function Invoke-DscOperation {
500486 exit 1
501487 }
502488 $property .$ ($_.Name ) = [System.Management.Automation.PSCredential ]::new($_.Value.Username , (ConvertTo-SecureString - AsPlainText $_.Value.Password - Force))
503- }
504- else {
489+ } else {
505490 $property .$ ($_.Name ) = $_.Value.psobject.properties | ForEach-Object - Begin { $propertyHash = @ {} } - Process { $propertyHash [$_.Name ] = $_.Value } - End { $propertyHash }
506491 }
507- }
508- else {
492+ } else {
509493 $property [$_.Name ] = $_.Value
510494 }
511495 }
@@ -516,16 +500,14 @@ function Invoke-DscOperation {
516500 $invokeResult = Invoke-DscResource - Method $Operation - ModuleName $cachedDscResourceInfo.ModuleName - Name $cachedDscResourceInfo.Name - Property $property
517501 if ($invokeResult.GetType ().Name -eq ' Hashtable' ) {
518502 $invokeResult.keys | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
519- }
520- else {
503+ } else {
521504 # the object returned by WMI is a CIM instance with a lot of additional data. only return DSC properties
522505 $invokeResult.psobject.Properties.name | Where-Object { ' CimClass' , ' CimInstanceProperties' , ' CimSystemProperties' -notcontains $_ } | ForEach-Object - Begin { $ResultProperties = @ {} } - Process { $ResultProperties [$_ ] = $invokeResult .$_ }
523506 }
524507
525508 # set the properties of the OUTPUT object from the result of Get-TargetResource
526509 $addToActualState.properties = $ResultProperties
527- }
528- catch {
510+ } catch {
529511 ' Exception: ' + $_.Exception.Message | Write-DscTrace - Operation Error
530512 exit 1
531513 }
@@ -537,8 +519,7 @@ function Invoke-DscOperation {
537519 }
538520
539521 return $addToActualState
540- }
541- else {
522+ } else {
542523 $dsJSON = $DesiredState | ConvertTo-Json - Depth 10
543524 ' Can not find type "' + $DesiredState.type + ' " for resource "' + $dsJSON + ' ". Please ensure that Get-DscResource returns this resource type.' | Write-DscTrace - Operation Error
544525 exit 1
@@ -584,6 +565,72 @@ function ValidateMethod {
584565 return $method
585566}
586567
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+
587634# cached resource
588635class dscResourceCacheEntry {
589636 [string ] $Type
@@ -612,6 +659,13 @@ enum dscResourceType {
612659 Composite
613660}
614661
662+ class DscResourcePropertyInfo {
663+ [string ] $Name
664+ [string ] $PropertyType
665+ [bool ] $IsMandatory
666+ [System.Collections.Generic.List [string ]] $Values
667+ }
668+
615669# dsc resource type (settable clone)
616670class DscResourceInfo {
617671 [dscResourceType ] $ImplementationDetail
0 commit comments