@@ -37,98 +37,171 @@ function Invoke-DscCacheRefresh {
37
37
$Module
38
38
)
39
39
40
- # create a list object to store cache of Get-DscResource
41
- [dscResourceCache []]$dscResourceCache = [System.Collections.Generic.List [Object ]]::new()
42
-
43
- # improve by performance by having the option to only get details for named modules
44
- # workaround for File and SignatureValidation resources that ship in Windows
45
- if ($null -ne $module -and ' PSDesiredStateConfiguration' -ne $module ) {
46
- if ($module.gettype ().name -eq ' string' ) {
47
- $module = @ ($module )
48
- }
49
- $DscResources = [System.Collections.Generic.List [Object ]]::new()
50
- $Modules = [System.Collections.Generic.List [Object ]]::new()
51
- foreach ($m in $module ) {
52
- $DscResources += Get-DscResource - Module $m
53
- $Modules += Get-Module - Name $m - ListAvailable
54
- }
55
- }
56
- elseif (' PSDesiredStateConfiguration' -eq $module -and $PSVersionTable.PSVersion.Major -le 5 ) {
57
- # the resources in Windows should only load in Windows PowerShell
58
- # workaround: the binary modules don't have a module name, so we have to special case File and SignatureValidation resources that ship in Windows
59
- $DscResources = Get-DscResource | Where-Object { $_.modulename -eq ' PSDesiredStateConfiguration' -or ( $_.modulename -eq $null -and $_.parentpath -like " $env: windir \System32\Configuration\*" ) }
40
+ $refreshCache = $false
41
+
42
+ if ($IsWindows ) {
43
+ $cacheFilePath = Join-Path $env: LocalAppData " dscv3classcache.json"
60
44
}
61
45
else {
62
- # if no module is specified, get all resources
63
- $DscResources = Get-DscResource
64
- $Modules = Get-Module - ListAvailable
46
+ $cacheFilePath = Join-Path $env: HOME " .dsc" " dscv3classcache.json"
65
47
}
66
48
67
- $psdscVersion = Get-Module PSDesiredStateConfiguration | Sort-Object - descending | Select-Object - First 1 | ForEach-Object Version
49
+ if (Test-Path $cacheFilePath ) {
50
+ $trace = @ {' Debug' = " Reading from Get-DscResource cache file $cacheFilePath " } | ConvertTo-Json - Compress
51
+ $host.ui.WriteErrorLine ($trace )
52
+
53
+ $dscResourceCache = Get-Content - Raw $cacheFilePath | ConvertFrom-Json
68
54
69
- foreach ($dscResource in $DscResources ) {
70
- # resources that shipped in Windows should only be used with Windows PowerShell
71
- if ($dscResource.ParentPath -like " $env: windir \System32\*" -and $PSVersionTable.PSVersion.Major -gt 5 ) {
72
- continue
55
+ if ($dscResourceCache.Count -eq 0 ) {
56
+ # if there is nothing in the cache file - refresh cache
57
+ $refreshCache = $true
58
+
59
+ $trace = @ {' Debug' = " Filtered DscResourceCache cache is empty" } | ConvertTo-Json - Compress
60
+ $host.ui.WriteErrorLine ($trace )
73
61
}
62
+ else
63
+ {
64
+ $trace = @ {' Debug' = " Checking cache for stale entries" } | ConvertTo-Json - Compress
65
+ $host.ui.WriteErrorLine ($trace )
74
66
75
- # we can't run this check in PSDesiredStateConfiguration 1.1 because the property doesn't exist
76
- if ( $psdscVersion -ge ' 2.0.7' ) {
77
- # only support known dscResourceType
78
- if ([dscResourceType ].GetEnumNames() -notcontains $dscResource.ImplementationDetail ) {
79
- $trace = @ {' Debug' = ' WARNING: implementation detail not found: ' + $dscResource.ImplementationDetail } | ConvertTo-Json - Compress
67
+ foreach ($cacheEntry in $dscResourceCache ) {
68
+ $trace = @ {' Trace' = " Checking cache entry '$ ( $cacheEntry.Type ) $ ( $cacheEntry.LastWriteTimes ) '" } | ConvertTo-Json - Compress
80
69
$host.ui.WriteErrorLine ($trace )
81
- continue
70
+
71
+ $cacheEntry.LastWriteTimes.PSObject.Properties | ForEach-Object {
72
+
73
+ if (-not ((Get-Item $_.Name ).LastWriteTime.Equals([DateTime ]$_.Value )))
74
+ {
75
+ $trace = @ {' Debug' = " Detected stale cache entry '$ ( $_.Name ) '" } | ConvertTo-Json - Compress
76
+ $host.ui.WriteErrorLine ($trace )
77
+
78
+ $refreshCache = $true
79
+ break
80
+ }
81
+ }
82
+
83
+ if ($refreshCache ) {break }
82
84
}
83
85
}
86
+ }
87
+ else {
88
+ $trace = @ {' Debug' = " Cache file not found '$cacheFilePath '" } | ConvertTo-Json - Compress
89
+ $host.ui.WriteErrorLine ($trace )
84
90
85
- # workaround: if the resource does not have a module name, get it from parent path
86
- # workaround: modulename is not settable, so clone the object without being read-only
87
- # workaround: we have to special case File and SignatureValidation resources that ship in Windows
88
- $binaryBuiltInModulePaths = @ (
89
- " $env: windir \system32\Configuration\Schema\MSFT_FileDirectoryConfiguration"
90
- " $env: windir \system32\Configuration\BaseRegistration"
91
- )
92
- $DscResourceInfo = [DscResourceInfo ]::new()
93
- $dscResource.PSObject.Properties | ForEach-Object - Process {
94
- if ($null -ne $_.Value ) {
95
- $DscResourceInfo .$ ($_.Name ) = $_.Value
91
+ $refreshCache = $true
92
+ }
93
+
94
+ if ($refreshCache ) {
95
+ $trace = @ {' Debug' = ' Constructing Get-DscResource cache' } | ConvertTo-Json - Compress
96
+ $host.ui.WriteErrorLine ($trace )
97
+
98
+ # create a list object to store cache of Get-DscResource
99
+ [dscResourceCacheEntry []]$dscResourceCache = [System.Collections.Generic.List [Object ]]::new()
100
+
101
+ # improve by performance by having the option to only get details for named modules
102
+ # workaround for File and SignatureValidation resources that ship in Windows
103
+ if ($null -ne $module -and ' PSDesiredStateConfiguration' -ne $module ) {
104
+ if ($module.gettype ().name -eq ' string' ) {
105
+ $module = @ ($module )
96
106
}
97
- else {
98
- $DscResourceInfo .$ ($_.Name ) = ' '
107
+ $DscResources = [System.Collections.Generic.List [Object ]]::new()
108
+ $Modules = [System.Collections.Generic.List [Object ]]::new()
109
+ foreach ($m in $module ) {
110
+ $DscResources += Get-DscResource - Module $m
111
+ $Modules += Get-Module - Name $m - ListAvailable
99
112
}
100
113
}
101
-
102
- if ($dscResource.ModuleName ) {
103
- $moduleName = $dscResource.ModuleName
114
+ elseif (' PSDesiredStateConfiguration' -eq $module -and $PSVersionTable.PSVersion.Major -le 5 ) {
115
+ # the resources in Windows should only load in Windows PowerShell
116
+ # workaround: the binary modules don't have a module name, so we have to special case File and SignatureValidation resources that ship in Windows
117
+ $DscResources = Get-DscResource | Where-Object { $_.modulename -eq ' PSDesiredStateConfiguration' -or ( $_.modulename -eq $null -and $_.parentpath -like " $env: windir \System32\Configuration\*" ) }
104
118
}
105
- elseif ($binaryBuiltInModulePaths -contains $dscResource.ParentPath ) {
106
- $moduleName = ' PSDesiredStateConfiguration'
107
- $DscResourceInfo.Module = ' PSDesiredStateConfiguration'
108
- $DscResourceInfo.ModuleName = ' PSDesiredStateConfiguration'
109
- $DscResourceInfo.CompanyName = ' Microsoft Corporation'
110
- $DscResourceInfo.Version = ' 1.0.0'
111
- if ($PSVersionTable.PSVersion.Major -le 5 -and $DscResourceInfo.ImplementedAs -eq ' Binary' ) {
112
- $DscResourceInfo.ImplementationDetail = ' Binary'
113
- }
119
+ else {
120
+ # if no module is specified, get all resources
121
+ $DscResources = Get-DscResource
122
+ $Modules = Get-Module - ListAvailable
114
123
}
115
- elseif ($binaryBuiltInModulePaths -notcontains $dscResource.ParentPath -and $null -ne $dscResource.ParentPath ) {
116
- # workaround: populate module name from parent path that is three levels up
117
- $moduleName = Split-Path $dscResource.ParentPath | Split-Path | Split-Path - Leaf
118
- $DscResourceInfo.Module = $moduleName
119
- $DscResourceInfo.ModuleName = $moduleName
120
- # workaround: populate module version from psmoduleinfo if available
121
- if ($moduleInfo = $Modules | Where-Object { $_.Name -eq $moduleName }) {
122
- $moduleInfo = $moduleInfo | Sort-Object - Property Version - Descending | Select-Object - First 1
123
- $DscResourceInfo.Version = $moduleInfo.Version.ToString ()
124
+
125
+ $psdscVersion = Get-Module PSDesiredStateConfiguration | Sort-Object - descending | Select-Object - First 1 | ForEach-Object Version
126
+
127
+ foreach ($dscResource in $DscResources ) {
128
+ # resources that shipped in Windows should only be used with Windows PowerShell
129
+ if ($dscResource.ParentPath -like " $env: windir \System32\*" -and $PSVersionTable.PSVersion.Major -gt 5 ) {
130
+ continue
131
+ }
132
+
133
+ # we can't run this check in PSDesiredStateConfiguration 1.1 because the property doesn't exist
134
+ if ( $psdscVersion -ge ' 2.0.7' ) {
135
+ # only support known dscResourceType
136
+ if ([dscResourceType ].GetEnumNames() -notcontains $dscResource.ImplementationDetail ) {
137
+ $trace = @ {' Debug' = ' WARNING: implementation detail not found: ' + $dscResource.ImplementationDetail } | ConvertTo-Json - Compress
138
+ $host.ui.WriteErrorLine ($trace )
139
+ continue
140
+ }
141
+ }
142
+
143
+ # workaround: if the resource does not have a module name, get it from parent path
144
+ # workaround: modulename is not settable, so clone the object without being read-only
145
+ # workaround: we have to special case File and SignatureValidation resources that ship in Windows
146
+ $binaryBuiltInModulePaths = @ (
147
+ " $env: windir \system32\Configuration\Schema\MSFT_FileDirectoryConfiguration"
148
+ " $env: windir \system32\Configuration\BaseRegistration"
149
+ )
150
+ $DscResourceInfo = [DscResourceInfo ]::new()
151
+ $dscResource.PSObject.Properties | ForEach-Object - Process {
152
+ if ($null -ne $_.Value ) {
153
+ $DscResourceInfo .$ ($_.Name ) = $_.Value
154
+ }
155
+ else {
156
+ $DscResourceInfo .$ ($_.Name ) = ' '
157
+ }
124
158
}
125
- }
126
159
127
- $dscResourceCache += [dscResourceCache ]@ {
128
- Type = " $moduleName /$ ( $dscResource.Name ) "
129
- DscResourceInfo = $DscResourceInfo
160
+ if ($dscResource.ModuleName ) {
161
+ $moduleName = $dscResource.ModuleName
162
+ }
163
+ elseif ($binaryBuiltInModulePaths -contains $dscResource.ParentPath ) {
164
+ $moduleName = ' PSDesiredStateConfiguration'
165
+ $DscResourceInfo.Module = ' PSDesiredStateConfiguration'
166
+ $DscResourceInfo.ModuleName = ' PSDesiredStateConfiguration'
167
+ $DscResourceInfo.CompanyName = ' Microsoft Corporation'
168
+ $DscResourceInfo.Version = ' 1.0.0'
169
+ if ($PSVersionTable.PSVersion.Major -le 5 -and $DscResourceInfo.ImplementedAs -eq ' Binary' ) {
170
+ $DscResourceInfo.ImplementationDetail = ' Binary'
171
+ }
172
+ }
173
+ elseif ($binaryBuiltInModulePaths -notcontains $dscResource.ParentPath -and $null -ne $dscResource.ParentPath ) {
174
+ # workaround: populate module name from parent path that is three levels up
175
+ $moduleName = Split-Path $dscResource.ParentPath | Split-Path | Split-Path - Leaf
176
+ $DscResourceInfo.Module = $moduleName
177
+ $DscResourceInfo.ModuleName = $moduleName
178
+ # workaround: populate module version from psmoduleinfo if available
179
+ if ($moduleInfo = $Modules | Where-Object { $_.Name -eq $moduleName }) {
180
+ $moduleInfo = $moduleInfo | Sort-Object - Property Version - Descending | Select-Object - First 1
181
+ $DscResourceInfo.Version = $moduleInfo.Version.ToString ()
182
+ }
183
+ }
184
+
185
+ # fill in resource files (and their last-write-times) that will be used for up-do-date checks
186
+ $lastWriteTimes = @ {}
187
+ Get-ChildItem - Recurse - Path $dscResource.ParentPath | % {
188
+ $lastWriteTimes.Add ($_.FullName , $_.LastWriteTime )
189
+ }
190
+
191
+ $dscResourceCache += [dscResourceCacheEntry ]@ {
192
+ Type = " $moduleName /$ ( $dscResource.Name ) "
193
+ DscResourceInfo = $DscResourceInfo
194
+ LastWriteTimes = $lastWriteTimes
195
+ }
130
196
}
197
+
198
+ # save cache for future use
199
+ # TODO: replace this with a high-performance serializer
200
+ $trace = @ {' Debug' = " Saving Get-DscResource cache to '$cacheFilePath '" } | ConvertTo-Json - Compress
201
+ $host.ui.WriteErrorLine ($trace )
202
+ $dscResourceCache | ConvertTo-Json - Depth 90 | Out-File $cacheFilePath
131
203
}
204
+
132
205
return $dscResourceCache
133
206
}
134
207
@@ -188,7 +261,7 @@ function Invoke-DscOperation {
188
261
[Parameter (Mandatory , ValueFromPipeline = $true )]
189
262
[dscResourceObject ]$DesiredState ,
190
263
[Parameter (Mandatory )]
191
- [dscResourceCache []]$dscResourceCache
264
+ [dscResourceCacheEntry []]$dscResourceCache
192
265
)
193
266
194
267
$osVersion = [System.Environment ]::OSVersion.VersionString
@@ -376,9 +449,10 @@ function GetTypeInstanceFromModule {
376
449
}
377
450
378
451
# cached resource
379
- class dscResourceCache {
452
+ class dscResourceCacheEntry {
380
453
[string ] $Type
381
454
[psobject ] $DscResourceInfo
455
+ [PSCustomObject ] $LastWriteTimes
382
456
}
383
457
384
458
# format expected for configuration and resource output
0 commit comments