Skip to content

Commit c3d7e31

Browse files
authored
Merge pull request #765 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 9eeeadc + 9a84cdc commit c3d7e31

File tree

9 files changed

+219
-117
lines changed

9 files changed

+219
-117
lines changed

Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,38 @@ function Push-CIPPStandardsList {
180180
}
181181
}
182182

183+
$CAStandardFound = ($ComputedStandards.Keys.Where({ $_ -like '*ConditionalAccessTemplate*' }, 'First').Count -gt 0)
184+
if ($CAStandardFound) {
185+
$TestResult = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_general' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2')
186+
if (-not $TestResult) {
187+
$CAKeys = @($ComputedStandards.Keys | Where-Object { $_ -like '*ConditionalAccessTemplate*' })
188+
$BulkFields = [System.Collections.Generic.List[object]]::new()
189+
foreach ($Key in $CAKeys) {
190+
$TemplateKey = ($Key -split '\|', 2)[1]
191+
if ($TemplateKey) {
192+
$BulkFields.Add([PSCustomObject]@{
193+
FieldName = "standards.ConditionalAccessTemplate.$TemplateKey"
194+
FieldValue = 'This tenant does not have the required license for this standard.'
195+
})
196+
}
197+
[void]$ComputedStandards.Remove($Key)
198+
}
199+
if ($BulkFields.Count -gt 0) {
200+
Set-CIPPStandardsCompareField -TenantFilter $TenantFilter -BulkFields $BulkFields
201+
}
202+
203+
Write-Information "Removed ConditionalAccessTemplate standards for $TenantFilter - missing required license"
204+
} else {
205+
# License valid - update CIPPDB cache with latest CA information before we run so that standards have the most up to date info
206+
try {
207+
Write-Information "Updating CIPPDB cache for Conditional Access policies for $TenantFilter"
208+
Set-CIPPDBCacheConditionalAccessPolicies -TenantFilter $TenantFilter
209+
} catch {
210+
Write-Warning "Failed to update CA cache for $TenantFilter : $($_.Exception.Message)"
211+
}
212+
}
213+
}
214+
183215
Write-Host "Returning $($ComputedStandards.Count) standards for tenant $TenantFilter after filtering."
184216
# Return filtered standards
185217
$FilteredStandards = $ComputedStandards.Values | ForEach-Object {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
function Remove-EmptyArrays {
2+
<#
3+
.SYNOPSIS
4+
Recursively removes empty arrays and null properties from objects
5+
.DESCRIPTION
6+
This function recursively traverses an object (Array, Hashtable, or PSCustomObject) and removes:
7+
- Empty arrays
8+
- Null properties
9+
The function modifies the object in place.
10+
.PARAMETER Object
11+
The object to process (can be Array, Hashtable, or PSCustomObject)
12+
.FUNCTIONALITY
13+
Internal
14+
.EXAMPLE
15+
$obj = @{ items = @(); name = "test"; value = $null }
16+
Remove-EmptyArrays -Object $obj
17+
.EXAMPLE
18+
$obj = [PSCustomObject]@{ items = @(); name = "test" }
19+
Remove-EmptyArrays -Object $obj
20+
#>
21+
[CmdletBinding()]
22+
param(
23+
[Parameter(Mandatory = $true)]
24+
[object]$Object
25+
)
26+
27+
if ($Object -is [Array]) {
28+
foreach ($Item in $Object) {
29+
Remove-EmptyArrays -Object $Item
30+
}
31+
} elseif ($Object -is [HashTable]) {
32+
foreach ($Key in @($Object.get_Keys())) {
33+
if ($Object[$Key] -is [Array] -and $Object[$Key].get_Count() -eq 0) {
34+
$Object.Remove($Key)
35+
} else {
36+
Remove-EmptyArrays -Object $Object[$Key]
37+
}
38+
}
39+
} elseif ($Object -is [PSCustomObject]) {
40+
foreach ($Name in @($Object.PSObject.Properties.Name)) {
41+
if ($Object.$Name -is [Array] -and $Object.$Name.get_Count() -eq 0) {
42+
$Object.PSObject.Properties.Remove($Name)
43+
} elseif ($null -eq $Object.$Name) {
44+
$Object.PSObject.Properties.Remove($Name)
45+
} else {
46+
Remove-EmptyArrays -Object $Object.$Name
47+
}
48+
}
49+
}
50+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function Test-IsGuid {
2+
<#
3+
.SYNOPSIS
4+
Tests if a string is a valid GUID
5+
.DESCRIPTION
6+
This function checks if a string can be parsed as a valid GUID using .NET's Guid.TryParse method.
7+
.PARAMETER String
8+
The string to test for GUID format
9+
.FUNCTIONALITY
10+
Internal
11+
.EXAMPLE
12+
Test-IsGuid -String "123e4567-e89b-12d3-a456-426614174000"
13+
.EXAMPLE
14+
Test-IsGuid -String "not-a-guid"
15+
#>
16+
[CmdletBinding()]
17+
param(
18+
[Parameter(Mandatory = $true)]
19+
[string]$String
20+
)
21+
22+
return [guid]::TryParse($String, [ref][guid]::Empty)
23+
}

Modules/CIPPCore/Public/GraphHelper/New-GraphGetRequest.ps1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ function New-GraphGetRequest {
160160
$WaitTime = [int]$RetryAfterHeader
161161
Write-Warning "Rate limited (429). Waiting $WaitTime seconds before retry. Attempt $($RetryCount + 1) of $MaxRetries"
162162
$ShouldRetry = $true
163+
} else {
164+
# If no Retry-After header, use exponential backoff with jitter
165+
$WaitTime = Get-Random -Minimum 1.1 -Maximum 4.1 # Random sleep between 1-4 seconds
166+
Write-Warning "Rate limited (429) with no Retry-After header. Waiting $WaitTime seconds before retry. Attempt $($RetryCount + 1) of $MaxRetries. Headers: $(($HttpResponseDetails.Headers | ConvertTo-Json -Compress))"
167+
$ShouldRetry = $true
163168
}
164169
}
165170
# Check for "Resource temporarily unavailable"

Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,17 @@ function New-CIPPCAPolicy {
1111
$CreateGroups = $false,
1212
$APIName = 'Create CA Policy',
1313
$Headers,
14-
$PreloadedCAPolicies = $null
14+
$PreloadedCAPolicies = $null,
15+
$PreloadedLocations = $null
1516
)
1617

17-
function Remove-EmptyArrays ($Object) {
18-
if ($Object -is [Array]) {
19-
foreach ($Item in $Object) { Remove-EmptyArrays $Item }
20-
} elseif ($Object -is [HashTable]) {
21-
foreach ($Key in @($Object.get_Keys())) {
22-
if ($Object[$Key] -is [Array] -and $Object[$Key].get_Count() -eq 0) {
23-
$Object.Remove($Key)
24-
} else { Remove-EmptyArrays $Object[$Key] }
25-
}
26-
} elseif ($Object -is [PSCustomObject]) {
27-
foreach ($Name in @($Object.PSObject.Properties.Name)) {
28-
if ($Object.$Name -is [Array] -and $Object.$Name.get_Count() -eq 0) {
29-
$Object.PSObject.Properties.Remove($Name)
30-
} elseif ($null -eq $Object.$Name) {
31-
$Object.PSObject.Properties.Remove($Name)
32-
} else { Remove-EmptyArrays $Object.$Name }
33-
}
34-
}
35-
}
36-
# Function to check if a string is a GUID
37-
function Test-IsGuid($string) {
38-
return [guid]::TryParse($string, [ref][guid]::Empty)
39-
}
4018
# Helper function to replace group display names with GUIDs
4119
function Convert-GroupNameToId {
4220
param($TenantFilter, $groupNames, $CreateGroups, $GroupTemplates)
4321

4422
$GroupIds = [System.Collections.Generic.List[string]]::new()
4523
$groupNames | ForEach-Object {
46-
if (Test-IsGuid $_) {
24+
if (Test-IsGuid -String $_) {
4725
Write-LogMessage -Headers $Headers -API $APIName -message "Already GUID, no need to replace: $_" -Sev 'Debug'
4826
$GroupIds.Add($_) # it's a GUID, so we keep it
4927
} else {
@@ -89,7 +67,7 @@ function New-CIPPCAPolicy {
8967

9068
$UserIds = [System.Collections.Generic.List[string]]::new()
9169
$userNames | ForEach-Object {
92-
if (Test-IsGuid $_) {
70+
if (Test-IsGuid -String $_) {
9371
Write-LogMessage -Headers $Headers -API $APIName -message "Already GUID, no need to replace: $_" -Sev 'Debug'
9472
$UserIds.Add($_) # it's a GUID, so we keep it
9573
} else {
@@ -111,7 +89,7 @@ function New-CIPPCAPolicy {
11189
$displayName = ($RawJSON | ConvertFrom-Json).displayName
11290

11391
$JSONobj = $RawJSON | ConvertFrom-Json | Select-Object * -ExcludeProperty ID, GUID, *time*
114-
Remove-EmptyArrays $JSONobj
92+
Remove-EmptyArrays -Object $JSONobj
11593
#Remove context as it does not belong in the payload.
11694
try {
11795
$JSONobj.grantControls.PSObject.Properties.Remove('authenticationStrength@odata.context')
@@ -148,13 +126,18 @@ function New-CIPPCAPolicy {
148126
# Get named locations once if needed
149127
$AllNamedLocations = $null
150128
if ($JSONobj.LocationInfo) {
151-
try {
152-
Write-Information 'Fetching all named locations...'
153-
$AllNamedLocations = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter -asApp $true
154-
} catch {
155-
$ErrorMessage = Get-CippException -Exception $_
156-
Write-Information "Error fetching named locations: $($ErrorMessage | ConvertTo-Json -Depth 10 -Compress)"
157-
throw "Failed to fetch named locations: $($ErrorMessage.NormalizedError)"
129+
if ($PreloadedLocations) {
130+
Write-Information 'Using preloaded named locations'
131+
$AllNamedLocations = $PreloadedLocations
132+
} else {
133+
try {
134+
Write-Information 'Fetching all named locations...'
135+
$AllNamedLocations = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter -asApp $true
136+
} catch {
137+
$ErrorMessage = Get-CippException -Exception $_
138+
Write-Information "Error fetching named locations: $($ErrorMessage | ConvertTo-Json -Depth 10 -Compress)"
139+
throw "Failed to fetch named locations: $($ErrorMessage.NormalizedError)"
140+
}
158141
}
159142
}
160143

Modules/CIPPCore/Public/New-CIPPCATemplate.ps1

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ function New-CIPPCATemplate {
1818
Write-Information "Processing CA Template for tenant $TenantFilter"
1919
Write-Information ($JSON | ConvertTo-Json -Depth 10)
2020

21-
# Function to check if a string is a GUID
22-
function Test-IsGuid($string) {
23-
return [guid]::tryparse($string, [ref][guid]::Empty)
24-
}
25-
2621
if ($preloadedUsers) {
2722
$users = $preloadedUsers
2823
} else {
@@ -94,15 +89,15 @@ function New-CIPPCATemplate {
9489
if ($isPSCustomObject -and $null -ne $JSON.conditions.users.includeGroups) {
9590
$JSON.conditions.users.includeGroups = @($JSON.conditions.users.includeGroups | ForEach-Object {
9691
$originalID = $_
97-
if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid $_)) { return $_ }
92+
if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid -String $_)) { return $_ }
9893
$match = $groups | Where-Object { $_.id -eq $originalID }
9994
if ($match) { $match.displayName } else { $originalID }
10095
})
10196
}
10297
if ($isPSCustomObject -and $null -ne $JSON.conditions.users.excludeGroups) {
10398
$JSON.conditions.users.excludeGroups = @($JSON.conditions.users.excludeGroups | ForEach-Object {
10499
$originalID = $_
105-
if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid $_)) { return $_ }
100+
if ($_ -in 'All', 'None', 'GuestOrExternalUsers' -or -not (Test-IsGuid -String $_)) { return $_ }
106101
$match = $groups | Where-Object { $_.id -eq $originalID }
107102
if ($match) { $match.displayName } else { $originalID }
108103
})

0 commit comments

Comments
 (0)