Skip to content

Commit 9857ad2

Browse files
author
Andrew
committed
Added DscResourceCache
1 parent 9f92b0d commit 9857ad2

File tree

1 file changed

+148
-74
lines changed

1 file changed

+148
-74
lines changed

powershell-adapter/psDscAdapter/psDscAdapter.psm1

Lines changed: 148 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -37,98 +37,171 @@ function Invoke-DscCacheRefresh {
3737
$Module
3838
)
3939

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"
6044
}
6145
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"
6547
}
6648

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
6854

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)
7361
}
62+
else
63+
{
64+
$trace = @{'Debug' = "Checking cache for stale entries"} | ConvertTo-Json -Compress
65+
$host.ui.WriteErrorLine($trace)
7466

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
8069
$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}
8284
}
8385
}
86+
}
87+
else {
88+
$trace = @{'Debug' = "Cache file not found '$cacheFilePath'"} | ConvertTo-Json -Compress
89+
$host.ui.WriteErrorLine($trace)
8490

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)
96106
}
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
99112
}
100113
}
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\*" ) }
104118
}
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
114123
}
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+
}
124158
}
125-
}
126159

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+
}
130196
}
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
131203
}
204+
132205
return $dscResourceCache
133206
}
134207

@@ -188,7 +261,7 @@ function Invoke-DscOperation {
188261
[Parameter(Mandatory, ValueFromPipeline = $true)]
189262
[dscResourceObject]$DesiredState,
190263
[Parameter(Mandatory)]
191-
[dscResourceCache[]]$dscResourceCache
264+
[dscResourceCacheEntry[]]$dscResourceCache
192265
)
193266

194267
$osVersion = [System.Environment]::OSVersion.VersionString
@@ -376,9 +449,10 @@ function GetTypeInstanceFromModule {
376449
}
377450

378451
# cached resource
379-
class dscResourceCache {
452+
class dscResourceCacheEntry {
380453
[string] $Type
381454
[psobject] $DscResourceInfo
455+
[PSCustomObject] $LastWriteTimes
382456
}
383457

384458
# format expected for configuration and resource output

0 commit comments

Comments
 (0)