Skip to content

Commit b6be9de

Browse files
authored
Merge pull request #1084 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 78508a8 + c59b130 commit b6be9de

8 files changed

Lines changed: 183 additions & 64 deletions

File tree

Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,10 @@ function Get-CIPPTenantAlignment {
228228
$TagValue = if ($Tag.value) { $Tag.value } else { $Tag }
229229
$TagTemplate = if ($TagValue -and $TemplatesByPackage.ContainsKey($TagValue)) { $TemplatesByPackage[$TagValue] } else { @() }
230230
$TagTemplate | ForEach-Object {
231-
$TagStandardId = "standards.IntuneTemplate.$($_.GUID)"
231+
# RowKey, not the GUID column: the standards engine deploys tag members with
232+
# TemplateList.value = RowKey and writes the compare row under that id, so the
233+
# expected id must match it. The GUID column can be missing or stale.
234+
$TagStandardId = "standards.IntuneTemplate.$($_.RowKey)"
232235
[PSCustomObject]@{
233236
StandardId = $TagStandardId
234237
ReportingEnabled = $IntuneReportingEnabled
@@ -260,7 +263,9 @@ function Get-CIPPTenantAlignment {
260263
$TagValue = if ($Tag.value) { $Tag.value } else { $Tag }
261264
$TagTemplate = if ($CATemplatesByPackage.ContainsKey($TagValue)) { $CATemplatesByPackage[$TagValue] } else { @() }
262265
$TagTemplate | ForEach-Object {
263-
$TagStandardId = "standards.ConditionalAccessTemplate.$($_.GUID)"
266+
# RowKey, not the GUID column - must match the id the standards engine
267+
# deploys with and writes the compare row under (see Intune block above)
268+
$TagStandardId = "standards.ConditionalAccessTemplate.$($_.RowKey)"
264269
[PSCustomObject]@{
265270
StandardId = $TagStandardId
266271
ReportingEnabled = $CAReportingEnabled

Modules/CIPPCore/Public/Get-CIPPDrift.ps1

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ function Get-CIPPDrift {
5656
$data | Add-Member -NotePropertyName 'Type' -NotePropertyValue $JSONData.Type -Force
5757
$data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $RawTemplate.RowKey -Force
5858
$IntuneTemplatesByGuid[$RawTemplate.RowKey] = $data
59+
# Built-in templates are seeded with RowKey = '<guid>.IntuneTemplate.json'; also index
60+
# by the bare guid so display-name lookups that extract the guid from a standard key hit
61+
if ($RawTemplate.RowKey -match '^([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})\.' -and -not $IntuneTemplatesByGuid.ContainsKey($Matches[1])) {
62+
$IntuneTemplatesByGuid[$Matches[1]] = $data
63+
}
5964
$data
6065
} catch {
6166
# Skip invalid templates
@@ -80,6 +85,11 @@ function Get-CIPPDrift {
8085
$data = $RawTemplate.JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue
8186
$data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $RawTemplate.RowKey -Force
8287
$CATemplatesByGuid[$RawTemplate.RowKey] = $data
88+
# Built-in templates are seeded with RowKey = '<guid>.CATemplate.json'; also index
89+
# by the bare guid so display-name lookups that extract the guid from a standard key hit
90+
if ($RawTemplate.RowKey -match '^([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})\.' -and -not $CATemplatesByGuid.ContainsKey($Matches[1])) {
91+
$CATemplatesByGuid[$Matches[1]] = $data
92+
}
8393
$data
8494
} catch {
8595
# Skip invalid templates

Modules/CIPPCore/Public/New-CIPPGroup.ps1

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,18 @@ function New-CIPPGroup {
275275
}
276276

277277
$GraphRequest = New-ExoRequest -tenantid $TenantFilter -cmdlet 'New-DistributionGroup' -cmdParams $ExoParams
278+
279+
$Aliases = @($GroupObject.aliases -split '\r?\n' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
280+
$SetParams = @{ Identity = $GraphRequest.Identity }
281+
if ($GroupObject.hideFromGAL) {
282+
$SetParams.HiddenFromAddressListsEnabled = $true
283+
}
284+
if ($Aliases.Count -gt 0) {
285+
$SetParams.EmailAddresses = @{ '@odata.type' = '#Exchange.GenericHashTable'; Add = @($Aliases | ForEach-Object { "smtp:$_" }) }
286+
}
287+
if ($SetParams.Keys.Count -gt 1) {
288+
$null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-DistributionGroup' -cmdParams $SetParams
289+
}
278290
}
279291

280292
$Result = [PSCustomObject]@{

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-AddGroupTemplate.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ function Invoke-AddGroupTemplate {
5353
allowExternal = $Request.Body.allowExternal
5454
username = $Request.Body.username # Can contain variables like @%tenantfilter%
5555
licenses = $Request.Body.licenses
56+
aliases = $Request.Body.aliases # One per line, variables allowed
57+
hideFromGAL = $Request.Body.hideFromGAL
5658
GUID = $GUID
5759
} | ConvertTo-Json -Depth 10
5860
$Table = Get-CippTable -tablename 'templates'

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroupTemplates.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ function Invoke-ListGroupTemplates {
4848
allowExternal = $data.allowExternal
4949
username = $data.username
5050
licenses = $data.licenses
51+
aliases = $data.aliases
52+
hideFromGAL = $data.hideFromGAL
5153
GUID = $_.RowKey
5254
source = $_.Source
5355
isSynced = (![string]::IsNullOrEmpty($_.SHA))

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecUpdateDriftDeviation.ps1

Lines changed: 104 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)