@@ -11,6 +11,49 @@ function Invoke-ExecUpdateDriftDeviation {
1111 $APIName = $TriggerMetadata.FunctionName
1212 Write-LogMessage - Headers $Request.Headers - API $APINAME - message ' Accessed this API' - Sev ' Debug'
1313
14+ function Find-CIPPTagBundleEntry {
15+ param ($Entries , $TemplateId , $TemplatePartition )
16+
17+ if (-not $Entries ) { return $null }
18+
19+ $BundleEntry = $null
20+ $TemplatesTable = Get-CippTable - tablename ' templates'
21+ $LiveTemplate = Get-CIPPAzDataTableEntity @TemplatesTable - Filter " PartitionKey eq '$TemplatePartition ' and RowKey eq '$TemplateId '"
22+ if (-not $LiveTemplate ) {
23+ # Built-in templates are keyed as '<guid>.IntuneTemplate.json' / '<guid>.CATemplate.json',
24+ # while deviation ids may carry the bare guid - match by prefix as a fallback
25+ $LiveTemplate = Get-CIPPAzDataTableEntity @TemplatesTable - Filter " PartitionKey eq '$TemplatePartition '" |
26+ Where-Object { $_.RowKey -like " $TemplateId *" } | Select-Object - First 1
27+ }
28+ if ($LiveTemplate.Package ) {
29+ $BundleEntry = $Entries | Where-Object {
30+ $TagValues = foreach ($Tag in @ ($_ .' TemplateList-Tags' )) {
31+ if ($Tag.value ) { $Tag.value } elseif ($Tag -is [string ]) { $Tag }
32+ }
33+ @ ($TagValues ) -contains $LiveTemplate.Package
34+ } | Select-Object - First 1
35+ }
36+
37+ if (-not $BundleEntry ) {
38+ # Fallback: the stored snapshot, for templates whose live row no longer exists
39+ $BundleEntry = $Entries | Where-Object {
40+ ($_ .' TemplateList-Tags' .rawData.templates | Where-Object { $_.GUID -like " *$TemplateId *" }) -or
41+ ($_ .' TemplateList-Tags' .addedFields.templates | Where-Object { $_.GUID -like " *$TemplateId *" })
42+ } | Select-Object - First 1
43+ }
44+
45+ if ($BundleEntry ) {
46+ $Matched = $BundleEntry.PSObject.Copy ()
47+ $Matched.PSObject.Properties.Remove (' TemplateList-Tags' )
48+ $Matched | Add-Member - NotePropertyName TemplateList - NotePropertyValue ([pscustomobject ]@ {
49+ label = $TemplateId
50+ value = $TemplateId
51+ }) - Force
52+ return $Matched
53+ }
54+ return $null
55+ }
56+
1457 try {
1558 $TenantFilter = $Request.Body.TenantFilter
1659
@@ -44,23 +87,17 @@ function Invoke-ExecUpdateDriftDeviation {
4487 $Setting = $Deviation.standardName -replace ' standards\.' , ' '
4588 $StandardTemplate = Get-CIPPTenantAlignment - TenantFilter $TenantFilter | Where-Object - Property standardType -EQ ' drift'
4689 $DriftTemplateId = $StandardTemplate.StandardId
90+ $Settings = $null
4791 if ($Setting -like ' *IntuneTemplate*' ) {
4892 $Setting = ' IntuneTemplate'
49- $TemplateId = $Deviation.standardName.split (' .' ) | Select-Object - Index 2
93+ # Take everything after the prefix: template ids can contain dots
94+ # (built-in templates are keyed '<guid>.IntuneTemplate.json')
95+ $TemplateId = $Deviation.standardName -replace ' ^(standards\.)?IntuneTemplates?\.' , ' '
5096 $MatchedTemplate = $StandardTemplate.standardSettings.IntuneTemplate | Where-Object { $_.TemplateList.value -like " *$TemplateId *" } | Select-Object - First 1
5197 if (-not $MatchedTemplate ) {
52- # Template may be inside a TemplateList-Tags bundle, expand it
53- $BundleEntry = $StandardTemplate.standardSettings.IntuneTemplate | Where-Object {
54- $_ .' TemplateList-Tags' .rawData.templates | Where-Object { $_.GUID -like " *$TemplateId *" }
55- } | Select-Object - First 1
56- if ($BundleEntry ) {
57- $MatchedTemplate = $BundleEntry.PSObject.Copy ()
58- $MatchedTemplate.PSObject.Properties.Remove (' TemplateList-Tags' )
59- $MatchedTemplate | Add-Member - NotePropertyName TemplateList - NotePropertyValue ([pscustomobject ]@ {
60- label = $TemplateId
61- value = $TemplateId
62- }) - Force
63- }
98+ # Template may be referenced through a TemplateList-Tags bundle - resolve the
99+ # tag membership live so members added after the bundle was saved still remediate
100+ $MatchedTemplate = Find-CIPPTagBundleEntry - Entries $StandardTemplate.standardSettings.IntuneTemplate - TemplateId $TemplateId - TemplatePartition ' IntuneTemplate'
64101 }
65102 if (-not $MatchedTemplate ) {
66103 Write-LogMessage - tenant $TenantFilter - Headers $Request.Headers - API $APINAME - message " Could not find IntuneTemplate $TemplateId in drift standard settings for remediation" - Sev ' Warning'
@@ -71,11 +108,21 @@ function Invoke-ExecUpdateDriftDeviation {
71108 }
72109 } elseif ($Setting -like ' *ConditionalAccessTemplate*' ) {
73110 $Setting = ' ConditionalAccessTemplate'
74- $TemplateId = $Deviation.standardName.split (' .' ) | Select-Object - Index 2
75- $StandardTemplate = $StandardTemplate.standardSettings.ConditionalAccessTemplate | Where-Object { $_.TemplateList.value -like " *$TemplateId *" }
76- $StandardTemplate | Add-Member - MemberType NoteProperty - Name ' remediate' - Value $true - Force
77- $StandardTemplate | Add-Member - MemberType NoteProperty - Name ' report' - Value $true - Force
78- $Settings = $StandardTemplate
111+ # Take everything after the prefix: template ids can contain dots
112+ # (built-in templates are keyed '<guid>.CATemplate.json')
113+ $TemplateId = $Deviation.standardName -replace ' ^(standards\.)?ConditionalAccessTemplates?\.' , ' '
114+ $MatchedTemplate = $StandardTemplate.standardSettings.ConditionalAccessTemplate | Where-Object { $_.TemplateList.value -like " *$TemplateId *" } | Select-Object - First 1
115+ if (-not $MatchedTemplate ) {
116+ # CA templates can be referenced through a TemplateList-Tags bundle too
117+ $MatchedTemplate = Find-CIPPTagBundleEntry - Entries $StandardTemplate.standardSettings.ConditionalAccessTemplate - TemplateId $TemplateId - TemplatePartition ' CATemplate'
118+ }
119+ if (-not $MatchedTemplate ) {
120+ Write-LogMessage - tenant $TenantFilter - Headers $Request.Headers - API $APINAME - message " Could not find ConditionalAccessTemplate $TemplateId in drift standard settings for remediation" - Sev ' Warning'
121+ } else {
122+ $MatchedTemplate | Add-Member - MemberType NoteProperty - Name ' remediate' - Value $true - Force
123+ $MatchedTemplate | Add-Member - MemberType NoteProperty - Name ' report' - Value $true - Force
124+ $Settings = $MatchedTemplate
125+ }
79126 } else {
80127 $StandardTemplate = $StandardTemplate.standardSettings .$Setting
81128 # If the addedComponent values are stored nested under standards.<setting> instead of
@@ -90,52 +137,62 @@ function Invoke-ExecUpdateDriftDeviation {
90137 $StandardTemplate | Add-Member - MemberType NoteProperty - Name ' report' - Value $true - Force
91138 $Settings = $StandardTemplate
92139 }
93- $TaskBody = @ {
94- TenantFilter = $TenantFilter
95- Name = " One Off Drift Remediation: $Setting - $TenantFilter - $DriftTemplateId "
96- Tag = " DriftRemediation_$DriftTemplateId "
97- Command = @ {
98- value = " Invoke-CIPPStandard$Setting "
99- label = " Invoke-CIPPStandard$Setting "
100- }
101-
102- Parameters = [pscustomobject ]@ {
103- Tenant = $TenantFilter
104- Settings = $Settings
105- }
106- ScheduledTime = ' 0'
107- PostExecution = @ {
108- Webhook = $false
109- Email = $false
110- PSA = $false
111- }
112- }
113- Add-CIPPScheduledTask - Task $TaskBody - hidden $false
114- Write-LogMessage - tenant $TenantFilter - Headers $Request.Headers - API $APINAME - message " Scheduled drift remediation task for $Setting " - Sev ' Info'
115-
116- if ($PersistentDeny ) {
117- $PersistentTaskBody = @ {
140+ if ($Settings ) {
141+ $TaskBody = @ {
118142 TenantFilter = $TenantFilter
119- Name = " Persistent Drift Remediation: $Setting - $TenantFilter - $DriftTemplateId "
143+ Name = " One Off Drift Remediation: $Setting - $TenantFilter - $DriftTemplateId "
120144 Tag = " DriftRemediation_$DriftTemplateId "
121145 Command = @ {
122146 value = " Invoke-CIPPStandard$Setting "
123147 label = " Invoke-CIPPStandard$Setting "
124148 }
149+
125150 Parameters = [pscustomobject ]@ {
126151 Tenant = $TenantFilter
127152 Settings = $Settings
128153 }
129154 ScheduledTime = ' 0'
130- Recurrence = ' 12h'
131155 PostExecution = @ {
132156 Webhook = $false
133157 Email = $false
134158 PSA = $false
135159 }
136160 }
137- Add-CIPPScheduledTask - Task $PersistentTaskBody - hidden $false
138- Write-LogMessage - tenant $TenantFilter - Headers $Request.Headers - API $APINAME - message " Scheduled persistent drift remediation task (12h recurrence) for $Setting " - Sev ' Info'
161+ Add-CIPPScheduledTask - Task $TaskBody - hidden $false
162+ Write-LogMessage - tenant $TenantFilter - Headers $Request.Headers - API $APINAME - message " Scheduled drift remediation task for $Setting " - Sev ' Info'
163+
164+ if ($PersistentDeny ) {
165+ $PersistentTaskBody = @ {
166+ TenantFilter = $TenantFilter
167+ Name = " Persistent Drift Remediation: $Setting - $TenantFilter - $DriftTemplateId "
168+ Tag = " DriftRemediation_$DriftTemplateId "
169+ Command = @ {
170+ value = " Invoke-CIPPStandard$Setting "
171+ label = " Invoke-CIPPStandard$Setting "
172+ }
173+ Parameters = [pscustomobject ]@ {
174+ Tenant = $TenantFilter
175+ Settings = $Settings
176+ }
177+ ScheduledTime = ' 0'
178+ Recurrence = ' 12h'
179+ PostExecution = @ {
180+ Webhook = $false
181+ Email = $false
182+ PSA = $false
183+ }
184+ }
185+ Add-CIPPScheduledTask - Task $PersistentTaskBody - hidden $false
186+ Write-LogMessage - tenant $TenantFilter - Headers $Request.Headers - API $APINAME - message " Scheduled persistent drift remediation task (12h recurrence) for $Setting " - Sev ' Info'
187+ }
188+ } else {
189+ # Surface the failure to the caller - previously this silently scheduled a
190+ # remediation task with empty settings that could never do anything
191+ [PSCustomObject ]@ {
192+ standardName = $Deviation.standardName
193+ success = $false
194+ error = " The deviation status was updated, but no remediation task was scheduled: the template could not be resolved from the drift template settings. Verify the template still exists in the template library, or re-save the drift template."
195+ }
139196 }
140197 }
141198 if ($Deviation.status -eq ' deniedDelete' ) {
0 commit comments