Skip to content

Commit 1798eeb

Browse files
authored
Merge pull request #608 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 559898d + 0bfa456 commit 1798eeb

File tree

6 files changed

+194
-55
lines changed

6 files changed

+194
-55
lines changed

Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,31 @@ function Get-CippApiAuth {
44
[string]$FunctionAppName
55
)
66

7-
if ($env:MSI_SECRET) {
8-
Disable-AzContextAutosave -Scope Process | Out-Null
9-
$null = Connect-AzAccount -Identity
10-
$SubscriptionId = $env:WEBSITE_OWNER_NAME -split '\+' | Select-Object -First 1
11-
$Context = Set-AzContext -SubscriptionId $SubscriptionId
12-
} else {
13-
$Context = Get-AzContext
14-
$SubscriptionId = $Context.Subscription.Id
7+
if ($env:WEBSITE_AUTH_V2_CONFIG_JSON) {
8+
$AuthSettings = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
159
}
1610

17-
# Get auth settings
18-
$AuthSettings = Invoke-AzRestMethod -Uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2/list?api-version=2020-06-01" -ErrorAction Stop | Select-Object -ExpandProperty Content | ConvertFrom-Json
11+
if (-not $AuthSettings) {
12+
if ($env:MSI_SECRET) {
13+
Disable-AzContextAutosave -Scope Process | Out-Null
14+
$null = Connect-AzAccount -Identity
15+
$SubscriptionId = $env:WEBSITE_OWNER_NAME -split '\+' | Select-Object -First 1
16+
$Context = Set-AzContext -SubscriptionId $SubscriptionId
17+
} else {
18+
$Context = Get-AzContext
19+
$SubscriptionId = $Context.Subscription.Id
20+
}
21+
22+
# Get auth settings
23+
$AuthSettings = (Invoke-AzRestMethod -Uri "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2/list?api-version=2020-06-01" -ErrorAction Stop | Select-Object -ExpandProperty Content | ConvertFrom-Json).properties
24+
}
1925

20-
if ($AuthSettings.properties) {
26+
if ($AuthSettings) {
2127
[PSCustomObject]@{
2228
ApiUrl = "https://$($env:WEBSITE_HOSTNAME)"
23-
TenantID = $AuthSettings.properties.identityProviders.azureActiveDirectory.registration.openIdIssuer -replace 'https://sts.windows.net/', '' -replace '/v2.0', ''
24-
ClientIDs = $AuthSettings.properties.identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedApplications
25-
Enabled = $AuthSettings.properties.identityProviders.azureActiveDirectory.enabled
29+
TenantID = $AuthSettings.identityProviders.azureActiveDirectory.registration.openIdIssuer -replace 'https://sts.windows.net/', '' -replace '/v2.0', ''
30+
ClientIDs = $AuthSettings.identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedApplications
31+
Enabled = $AuthSettings.identityProviders.azureActiveDirectory.enabled
2632
}
2733
} else {
2834
throw 'No auth settings found'

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-CIPPStandardsRun.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ function Invoke-CIPPStandardsRun {
4949
Write-Information 'Classic Standards Run'
5050

5151
$GetStandardParams = @{
52-
TenantFilter = $TenantFilter
53-
runManually = $runManually
52+
TenantFilter = $TenantFilter
53+
runManually = $runManually
54+
LicenseChecks = $true
5455
}
5556

5657
if ($TemplateID) {

Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogProcessingOrchestrator.ps1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ function Start-AuditLogProcessingOrchestrator {
2828
$ProcessBatch = foreach ($TenantGroup in $TenantGroups) {
2929
$TenantFilter = $TenantGroup.Name
3030
$RowIds = @($TenantGroup.Group.RowKey)
31-
for ($i = 0; $i -lt $RowIds.Count; $i += 1000) {
32-
Write-Host "Processing $TenantFilter with $($RowIds.Count) row IDs. We're processing id $($RowIds[$i]) to $($RowIds[[Math]::Min($i + 999, $RowIds.Count - 1)])"
33-
$BatchRowIds = $RowIds[$i..([Math]::Min($i + 999, $RowIds.Count - 1))]
31+
for ($i = 0; $i -lt $RowIds.Count; $i += 500) {
32+
Write-Host "Processing $TenantFilter with $($RowIds.Count) row IDs. We're processing id $($RowIds[$i]) to $($RowIds[[Math]::Min($i + 499, $RowIds.Count - 1)])"
33+
$BatchRowIds = $RowIds[$i..([Math]::Min($i + 499, $RowIds.Count - 1))]
3434
[PSCustomObject]@{
3535
TenantFilter = $TenantFilter
3636
RowIds = $BatchRowIds

Modules/CIPPCore/Public/Standards/Get-CIPPStandards.ps1

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -415,11 +415,107 @@ function Get-CIPPStandards {
415415
}
416416
Write-Host "We're removing Intune templates as the correct license is not present for this standard. We do this to not run unneeded cycles. If you're reading this don't touch."
417417
foreach ($Key in $IntuneKeys) { [void]$ComputedStandards.Remove($Key) }
418-
#After the license check we're now going to do a compare for the remaining standards.
419-
#This compare works by bulk requesting the top=1 entries for intune policies inside of the tenant.
420-
#if the 'lastModified' timestamp is the same we skip processing this standard too, there's no need because the tenant hasn't changed anything.
421-
#However, we also check the timestamp of our standardTemplate, if that is newer than the cached lastModified timestamp we also process the standard again, because the template has changed.
422-
#If our cache for this tenant is blank, we also process the standard.
418+
} else {
419+
# Bulk check policy timestamps per type
420+
$TypeMap = @{ Device = 'deviceManagement/deviceConfigurations'; Catalog = 'deviceManagement/configurationPolicies'; Admin = 'deviceManagement/groupPolicyConfigurations'; deviceCompliancePolicies = 'deviceManagement/deviceCompliancePolicies'; AppProtection_Android = 'deviceAppManagement/androidManagedAppProtections'; AppProtection_iOS = 'deviceAppManagement/iosManagedAppProtections' }
421+
$BulkRequests = $TypeMap.GetEnumerator() | ForEach-Object {
422+
@{ id = $_.Key; url = "$($_.Value)?`$orderby=lastModifiedDateTime desc&`$select=id,lastModifiedDateTime&`$top=999"; method = 'GET' }
423+
}
424+
try {
425+
$TrackingTable = Get-CippTable -tablename 'IntunePolicyTypeTracking'
426+
$BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantName -NoPaginateIds @($BulkRequests.id)
427+
$PolicyTimestamps = @{}
428+
429+
foreach ($Result in $BulkResults) {
430+
$GraphTime = $Result.body.value[0].lastModifiedDateTime
431+
$GraphId = $Result.body.value[0].id
432+
$GraphCount = ($Result.body.value | Measure-Object).Count
433+
$Cached = Get-CIPPAzDataTableEntity @TrackingTable -Filter "PartitionKey eq '$TenantName' and RowKey eq '$($Result.id)'"
434+
435+
# Check if count changed (indicates addition/deletion)
436+
$CountChanged = $false
437+
if ($Cached -and $Cached.PolicyCount -ne $null) {
438+
$CountChanged = ($GraphCount -ne $Cached.PolicyCount)
439+
if ($CountChanged) {
440+
Write-Host "Policy count changed for $($Result.id): $($Cached.PolicyCount) -> $GraphCount"
441+
}
442+
}
443+
444+
# Check if ID changed (different policy is now most recent)
445+
$IdChanged = $false
446+
if ($GraphId -and $Cached -and $Cached.LatestPolicyId) {
447+
$IdChanged = ($GraphId -ne $Cached.LatestPolicyId)
448+
if ($IdChanged) {
449+
Write-Host "Policy ID changed for $($Result.id): $($Cached.LatestPolicyId) -> $GraphId"
450+
}
451+
}
452+
453+
# Convert both to UTC DateTime for consistent comparison
454+
if ($GraphTime) {
455+
$GraphTimeUtc = ([DateTime]$GraphTime).ToUniversalTime()
456+
if ($Cached -and $Cached.LatestPolicyModified -and -not $IdChanged -and -not $CountChanged) {
457+
$CachedTimeUtc = ([DateTimeOffset]$Cached.LatestPolicyModified).UtcDateTime
458+
$TimeDiff = [Math]::Abs(($GraphTimeUtc - $CachedTimeUtc).TotalSeconds)
459+
$Changed = ($TimeDiff -gt 60) # Changed if difference > 1 minute
460+
} else {
461+
$Changed = $true # No cache, ID changed, count changed, or treat as changed
462+
}
463+
Add-CIPPAzDataTableEntity @TrackingTable -Entity @{ PartitionKey = $TenantName; RowKey = $Result.id; LatestPolicyModified = $GraphTime; LatestPolicyId = $GraphId; PolicyCount = $GraphCount } -Force | Out-Null
464+
} else {
465+
$Changed = $true # No Graph data means policies deleted or not yet created - always treat as changed
466+
}
467+
468+
$PolicyTimestamps[$Result.id] = $Changed
469+
}
470+
# Remove templates whose specific policy type hasn't changed
471+
$TemplateTable = Get-CippTable -tablename 'templates'
472+
$StandardTemplateTable = Get-CippTable -tablename 'templates'
473+
$IntuneKeys = @($ComputedStandards.Keys | Where-Object { $_ -like '*IntuneTemplate*' })
474+
foreach ($Key in $IntuneKeys) {
475+
$Template = $ComputedStandards[$Key]
476+
$TemplateEntity = Get-CIPPAzDataTableEntity @TemplateTable -Filter "PartitionKey eq 'IntuneTemplate' and RowKey eq '$($Template.TemplateList.value)'"
477+
478+
if (-not $TemplateEntity) { continue }
479+
480+
# Parse JSON to get Type property
481+
$ParsedTemplate = $TemplateEntity.JSON | ConvertFrom-Json
482+
if (-not $ParsedTemplate.Type) { continue }
483+
484+
$PolicyType = $ParsedTemplate.Type
485+
$PolicyChanged = if ($PolicyType -eq 'AppProtection') {
486+
[bool]($PolicyTimestamps['AppProtection_Android'] -or $PolicyTimestamps['AppProtection_iOS'])
487+
} else {
488+
[bool]$PolicyTimestamps[$PolicyType]
489+
}
490+
491+
# Check if StandardTemplate changed
492+
$StandardTemplate = Get-CIPPAzDataTableEntity @StandardTemplateTable -Filter "PartitionKey eq 'StandardsTemplateV2' and RowKey eq '$($Template.TemplateId)'"
493+
$StandardTemplateChanged = $false
494+
if ($StandardTemplate) {
495+
$StandardTimeUtc = ([DateTimeOffset]$StandardTemplate.Timestamp).UtcDateTime
496+
$CachedStandardTemplate = Get-CIPPAzDataTableEntity @TrackingTable -Filter "PartitionKey eq '$TenantName' and RowKey eq 'StandardTemplate_$($Template.TemplateId)'"
497+
498+
if ($CachedStandardTemplate -and $CachedStandardTemplate.CachedTimestamp) {
499+
$CachedStandardTimeUtc = ([DateTimeOffset]$CachedStandardTemplate.CachedTimestamp).UtcDateTime
500+
$TimeDiff = [Math]::Abs(($StandardTimeUtc - $CachedStandardTimeUtc).TotalSeconds)
501+
$StandardTemplateChanged = ($TimeDiff -gt 60) # Changed if difference > 1 minute
502+
} else {
503+
$StandardTemplateChanged = $true # No cache, treat as changed
504+
}
505+
506+
Add-CIPPAzDataTableEntity @TrackingTable -Entity @{ PartitionKey = $TenantName; RowKey = "StandardTemplate_$($Template.TemplateId)"; CachedTimestamp = $StandardTemplate.Timestamp } -Force | Out-Null
507+
}
508+
509+
# Remove if BOTH policy unchanged AND StandardTemplate unchanged
510+
if (-not $PolicyChanged -and -not $StandardTemplateChanged) {
511+
[void]$ComputedStandards.Remove($Key)
512+
} else {
513+
514+
}
515+
}
516+
} catch {
517+
Write-Host "Timestamp check failed for $TenantName`: $($_.Exception.Message)"
518+
}
423519
}
424520
}
425521

@@ -442,4 +538,3 @@ function Get-CIPPStandards {
442538
}
443539
}
444540
}
445-

Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -148,39 +148,76 @@ function Test-CIPPAuditLogRules {
148148
}
149149
}
150150

151-
# Collect bulk data for users/groups/devices/applications
152-
$Requests = @(
153-
@{
154-
id = 'users'
155-
url = '/users?$select=id,displayName,userPrincipalName,accountEnabled&$top=999'
156-
method = 'GET'
157-
}
158-
@{
159-
id = 'groups'
160-
url = '/groups?$select=id,displayName,mailEnabled,securityEnabled&$top=999'
161-
method = 'GET'
162-
}
163-
@{
164-
id = 'devices'
165-
url = '/devices?$select=id,displayName,deviceId&$top=999'
166-
method = 'GET'
167-
}
168-
@{
169-
id = 'servicePrincipals'
170-
url = '/servicePrincipals?$select=id,displayName&$top=999'
171-
method = 'GET'
172-
}
173-
)
174-
$Response = New-GraphBulkRequest -TenantId $TenantFilter -Requests $Requests
151+
$Table = Get-CIPPTable -tablename 'cacheauditloglookups'
152+
$1dayago = (Get-Date).AddDays(-1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
153+
$Lookups = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$TenantFilter' and Timestamp gt datetime'$1dayago'"
154+
if (!$Lookups) {
155+
# Collect bulk data for users/groups/devices/applications
156+
$Requests = @(
157+
@{
158+
id = 'users'
159+
url = '/users?$select=id,displayName,userPrincipalName,accountEnabled&$top=999'
160+
method = 'GET'
161+
}
162+
@{
163+
id = 'groups'
164+
url = '/groups?$select=id,displayName,mailEnabled,securityEnabled&$top=999'
165+
method = 'GET'
166+
}
167+
@{
168+
id = 'devices'
169+
url = '/devices?$select=id,displayName,deviceId&$top=999'
170+
method = 'GET'
171+
}
172+
@{
173+
id = 'servicePrincipals'
174+
url = '/servicePrincipals?$select=id,displayName&$top=999'
175+
method = 'GET'
176+
}
177+
)
178+
$Response = New-GraphBulkRequest -TenantId $TenantFilter -Requests $Requests
179+
$Users = ($Response | Where-Object { $_.id -eq 'users' }).body.value
180+
$Groups = ($Response | Where-Object { $_.id -eq 'groups' }).body.value ?? @()
181+
$Devices = ($Response | Where-Object { $_.id -eq 'devices' }).body.value ?? @()
182+
$ServicePrincipals = ($Response | Where-Object { $_.id -eq 'servicePrincipals' }).body.value
183+
# Cache the lookups for 1 day
184+
$Entities = @(
185+
@{
186+
PartitionKey = $TenantFilter
187+
RowKey = 'users'
188+
Data = [string]($Users | ConvertTo-Json -Compress)
189+
}
190+
@{
191+
PartitionKey = $TenantFilter
192+
RowKey = 'groups'
193+
Data = [string]($Groups | ConvertTo-Json -Compress)
194+
}
195+
@{
196+
PartitionKey = $TenantFilter
197+
RowKey = 'devices'
198+
Data = [string]($Devices | ConvertTo-Json -Compress)
199+
}
200+
@{
201+
PartitionKey = $TenantFilter
202+
RowKey = 'servicePrincipals'
203+
Data = [string]($ServicePrincipals | ConvertTo-Json -Compress)
204+
}
205+
)
206+
# Save the cached lookups
207+
Add-CIPPAzDataTableEntity @Table -Entity $Entities -Force
208+
Write-Information "Cached directory lookups for tenant $TenantFilter"
209+
} else {
210+
# Use cached lookups
211+
$Users = ($Lookups | Where-Object { $_.RowKey -eq 'users' }).Data | ConvertFrom-Json
212+
$Groups = ($Lookups | Where-Object { $_.RowKey -eq 'groups' }).Data | ConvertFrom-Json
213+
$Devices = ($Lookups | Where-Object { $_.RowKey -eq 'devices' }).Data | ConvertFrom-Json
214+
$ServicePrincipals = ($Lookups | Where-Object { $_.RowKey -eq 'servicePrincipals' }).Data | ConvertFrom-Json
215+
Write-Information "Using cached directory lookups for tenant $TenantFilter"
216+
}
175217

176218
# partner users
177219
$PartnerUsers = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$select=id,displayName,userPrincipalName,accountEnabled&`$top=999" -AsApp $true -NoAuthCheck $true
178220

179-
$Users = ($Response | Where-Object { $_.id -eq 'users' }).body.value
180-
$Groups = ($Response | Where-Object { $_.id -eq 'groups' }).body.value ?? @()
181-
$Devices = ($Response | Where-Object { $_.id -eq 'devices' }).body.value ?? @()
182-
$ServicePrincipals = ($Response | Where-Object { $_.id -eq 'servicePrincipals' }).body.value
183-
184221
Write-Warning '## Audit Log Configuration ##'
185222
Write-Information ($Configuration | ConvertTo-Json -Depth 10)
186223

Modules/CippEntrypoints/CippEntrypoints.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ function Receive-CippOrchestrationTrigger {
228228
if (($Output | Measure-Object).Count -gt 0) {
229229
Write-Information "Waiting for ($($Output.Count)) activity functions to complete..."
230230
foreach ($Task in $Output) {
231-
Write-Information ($Task | ConvertTo-Json -Depth 10 -Compress)
231+
#Write-Information ($Task | ConvertTo-Json -Depth 10 -Compress)
232232
try {
233233
$Results = Wait-ActivityFunction -Task $Task
234234
} catch {}

0 commit comments

Comments
 (0)