Skip to content

Commit 829beb2

Browse files
committed
Add group creation and vacation exclusion handling to CA policy
Introduces the ability to create groups if they do not exist when applying conditional access policies, controlled by the new CreateGroups parameter. Also preserves vacation exclusion groups when overwriting existing policies and optimizes user/group retrieval with bulk Graph requests. Updates standards template invocation to support CreateGroups.
1 parent 2d23f5c commit 829beb2

File tree

2 files changed

+75
-10
lines changed

2 files changed

+75
-10
lines changed

Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ function New-CIPPCAPolicy {
88
$Overwrite,
99
$ReplacePattern = 'none',
1010
$DisableSD = $false,
11+
$CreateGroups = $false,
1112
$APIName = 'Create CA Policy',
1213
$Headers
1314
)
@@ -39,7 +40,7 @@ function New-CIPPCAPolicy {
3940
}
4041
# Helper function to replace group display names with GUIDs
4142
function Replace-GroupNameWithId {
42-
param($groupNames)
43+
param($TenantFilter, $groupNames, $CreateGroups)
4344

4445
$GroupIds = [System.Collections.Generic.List[string]]::new()
4546
$groupNames | ForEach-Object {
@@ -54,6 +55,20 @@ function New-CIPPCAPolicy {
5455
$null = Write-LogMessage -Headers $User -API 'Create CA Policy' -message "Replaced group name $_ with ID $gid" -Sev 'Debug'
5556
$GroupIds.Add($gid) # add the ID to the list
5657
}
58+
} elseif ($CreateGroups) {
59+
Write-Warning "Creating group $_ as it does not exist in the tenant"
60+
$username = $_ -replace '[^a-zA-Z0-9]', ''
61+
if ($username.Length -gt 64) {
62+
$username = $username.Substring(0, 64)
63+
}
64+
$GroupObject = @{
65+
groupType = 'generic'
66+
displayName = $_
67+
username = $username
68+
securityEnabled = $true
69+
}
70+
$NewGroup = New-CIPPGroup -GroupObject $GroupObject -TenantFilter $TenantFilter -APIName 'New-CIPPCAPolicy'
71+
$GroupIds.Add($NewGroup.GroupId)
5772
} else {
5873
Write-Warning "Group $_ not found in the tenant"
5974
}
@@ -187,8 +202,22 @@ function New-CIPPCAPolicy {
187202
'displayName' {
188203
try {
189204
Write-Information 'Replacement pattern for inclusions and exclusions is displayName.'
190-
$users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/users?$select=id,displayName' -tenantid $TenantFilter -asApp $true
191-
$groups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName' -tenantid $TenantFilter -asApp $true
205+
$Requests = @(
206+
@{
207+
url = 'users?$select=id,displayName&$top=999'
208+
method = 'GET'
209+
id = 'users'
210+
}
211+
@{
212+
url = 'groups?$select=id,displayName&$top=999'
213+
method = 'GET'
214+
id = 'groups'
215+
}
216+
)
217+
$BulkResults = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter -asapp $true
218+
219+
$users = ($BulkResults | Where-Object { $_.id -eq 'users' }).body.value
220+
$groups = ($BulkResults | Where-Object { $_.id -eq 'groups' }).body.value
192221

193222
foreach ($userType in 'includeUsers', 'excludeUsers') {
194223
if ($JSONobj.conditions.users.PSObject.Properties.Name -contains $userType -and $JSONobj.conditions.users.$userType -notin 'All', 'None', 'GuestOrExternalUsers') {
@@ -199,7 +228,7 @@ function New-CIPPCAPolicy {
199228
# Check the included and excluded groups
200229
foreach ($groupType in 'includeGroups', 'excludeGroups') {
201230
if ($JSONobj.conditions.users.PSObject.Properties.Name -contains $groupType) {
202-
$JSONobj.conditions.users.$groupType = @(Replace-GroupNameWithId -groupNames $JSONobj.conditions.users.$groupType)
231+
$JSONobj.conditions.users.$groupType = @(Replace-GroupNameWithId -groupNames $JSONobj.conditions.users.$groupType -CreateGroups $CreateGroups -TenantFilter $TenantFilter)
203232
}
204233
}
205234
} catch {
@@ -246,18 +275,42 @@ function New-CIPPCAPolicy {
246275
Write-Information $RawJSON
247276
try {
248277
Write-Information 'Checking for existing policies'
249-
$CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $TenantFilter -asApp $true | Where-Object -Property displayName -EQ $displayname
250-
if ($CheckExististing) {
278+
$CheckExisting = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $TenantFilter -asApp $true | Where-Object -Property displayName -EQ $displayname
279+
if ($CheckExisting) {
251280
if ($Overwrite -ne $true) {
252281
throw "Conditional Access Policy with Display Name $($Displayname) Already exists"
253282
return $false
254283
} else {
255284
if ($State -eq 'donotchange') {
256-
$JSONobj.state = $CheckExististing.state
285+
$JSONobj.state = $CheckExisting.state
257286
$RawJSON = ConvertTo-Json -InputObject $JSONobj -Depth 10 -Compress
258287
}
259-
Write-Information "overwriting $($CheckExististing.id)"
260-
$null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -asApp $true
288+
# Preserve any exclusion groups named "Vacation Exclusion - <PolicyDisplayName>" from existing policy
289+
try {
290+
$ExistingVacationGroup = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=startsWith(displayName,'Vacation Exclusion')&`$select=id,displayName&`$top=999&`$count=true" -ComplexFilter -tenantid $TenantFilter -asApp $true |
291+
Where-Object { $CheckExisting.conditions.users.excludeGroups -contains $_.id }
292+
if ($ExistingVacationGroup) {
293+
if (-not ($JSONobj.conditions.users.PSObject.Properties.Name -contains 'excludeGroups')) {
294+
$JSONobj.conditions.users | Add-Member -NotePropertyName 'excludeGroups' -NotePropertyValue @() -Force
295+
}
296+
if ($JSONobj.conditions.users.excludeGroups -notcontains $ExistingVacationGroup.id) {
297+
Write-Information "Preserving vacation exclusion group $($ExistingVacationGroup.displayName)"
298+
$NewExclusions = [system.collections.generic.list[string]]::new()
299+
# Convert each item to string explicitly to avoid type conversion issues
300+
foreach ($group in $JSONobj.conditions.users.excludeGroups) {
301+
$NewExclusions.Add([string]$group)
302+
}
303+
$NewExclusions.Add($ExistingVacationGroup.id)
304+
$JSONobj.conditions.users.excludeGroups = $NewExclusions
305+
}
306+
# Re-render RawJSON after modification
307+
$RawJSON = ConvertTo-Json -InputObject $JSONobj -Depth 10 -Compress
308+
}
309+
} catch {
310+
Write-Information "Failed to preserve vacation exclusion group: $($_.Exception.Message)"
311+
}
312+
Write-Information "overwriting $($CheckExisting.id)"
313+
$null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExisting.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -asApp $true
261314
Write-LogMessage -Headers $User -API 'Create CA Policy' -tenant $($Tenant) -message "Updated Conditional Access Policy $($JSONobj.Displayname) to the template standard." -Sev 'Info'
262315
return "Updated policy $displayname for $tenantfilter"
263316
}

Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,19 @@ function Invoke-CIPPStandardConditionalAccessTemplate {
6666
continue
6767
}
6868
}
69-
$null = New-CIPPCAPolicy -replacePattern 'displayName' -TenantFilter $tenant -state $Setting.state -RawJSON $JSONObj -Overwrite $true -APIName $APIName -Headers $Request.Headers -DisableSD $Setting.DisableSD
69+
$NewCAPolicy = @{
70+
replacePattern = 'displayName'
71+
TenantFilter = $Tenant
72+
state = $Setting.state
73+
RawJSON = $JSONObj
74+
Overwrite = $true
75+
APIName = 'Standards'
76+
Headers = $Request.Headers
77+
DisableSD = $Setting.DisableSD
78+
CreateGroups = $Setting.CreateGroups ?? $false
79+
}
80+
81+
$null = New-CIPPCAPolicy @NewCAPolicy
7082
} catch {
7183
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
7284
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update conditional access rule $($JSONObj.displayName). Error: $ErrorMessage" -sev 'Error'

0 commit comments

Comments
 (0)