From ee0398db8fb1dc0f3e068d57044486d90b03c992 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Thu, 14 May 2026 11:28:32 +0200 Subject: [PATCH 01/90] feat(endpoint): add Apple ADE and Android enrollment profile listing and deletion endpoints --- .../Invoke-ExecRemoveEnrollmentProfile.ps1 | 46 ++++++++++ .../Invoke-ListAndroidEnrollmentProfiles.ps1 | 55 ++++++++++++ .../Invoke-ListAppleEnrollmentProfiles.ps1 | 83 +++++++++++++++++++ cspell.json | 2 + 4 files changed, 186 insertions(+) create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveEnrollmentProfile.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAndroidEnrollmentProfiles.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAppleEnrollmentProfiles.ps1 diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveEnrollmentProfile.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveEnrollmentProfile.ps1 new file mode 100644 index 000000000000..2d1fa7e8521c --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ExecRemoveEnrollmentProfile.ps1 @@ -0,0 +1,46 @@ +function Invoke-ExecRemoveEnrollmentProfile { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Endpoint.MEM.ReadWrite + .DESCRIPTION + Deletes an Apple ADE or Android Enterprise enrollment profile. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $TenantFilter = $Request.Body.tenantFilter + $ProfileId = $Request.Body.profileId ?? $Request.Body.id + $ProfileType = $Request.Body.profileType ?? 'apple' + $TokenId = $Request.Body.tokenId + $DisplayName = $Request.Body.displayName ?? $ProfileId + + try { + if ([string]::IsNullOrWhiteSpace($ProfileId)) { throw 'No profile id was supplied.' } + + if ($ProfileType -eq 'android') { + $Uri = "https://graph.microsoft.com/beta/deviceManagement/androidDeviceOwnerEnrollmentProfiles/$ProfileId" + } else { + if ([string]::IsNullOrWhiteSpace($TokenId)) { throw 'No Apple ADE token id was supplied.' } + $Uri = "https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings/$TokenId/enrollmentProfiles/$ProfileId" + } + + $null = New-GraphPOSTRequest -uri $Uri -tenantid $TenantFilter -type DELETE + $Result = "Deleted enrollment profile $DisplayName" + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev Info + $StatusCode = [HttpStatusCode]::OK + } catch { + $StatusCode = [HttpStatusCode]::InternalServerError + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to delete enrollment profile ${DisplayName}: $($ErrorMessage.NormalizedMessage)" + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev Error -LogData $ErrorMessage + } + + return ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{ Results = $Result } + }) +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAndroidEnrollmentProfiles.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAndroidEnrollmentProfiles.ps1 new file mode 100644 index 000000000000..3cd4218a50fc --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAndroidEnrollmentProfiles.ps1 @@ -0,0 +1,55 @@ +function Invoke-ListAndroidEnrollmentProfiles { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Endpoint.MEM.Read + .DESCRIPTION + Lists Android Enterprise enrollment profiles and hydrates token fields when Graph omits them from the list response. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + $Select = 'id,displayName,description,enrollmentMode,enrollmentTokenType,enrolledDeviceCount,tokenExpirationDateTime,lastModifiedDateTime,tokenValue,qrCodeContent,qrCodeImage' + $EncodedSelect = [System.Uri]::EscapeDataString($Select) + $BaseUri = 'https://graph.microsoft.com/beta/deviceManagement/androidDeviceOwnerEnrollmentProfiles' + + try { + $EnrollmentProfiles = @(New-GraphGetRequest -uri "${BaseUri}?`$select=$EncodedSelect" -tenantid $TenantFilter) + $Results = foreach ($EnrollmentProfile in $EnrollmentProfiles) { + $ProfileObject = $EnrollmentProfile | Select-Object * + $MissingTokenData = [string]::IsNullOrWhiteSpace($ProfileObject.tokenValue) -and + [string]::IsNullOrWhiteSpace($ProfileObject.qrCodeContent) -and + [string]::IsNullOrWhiteSpace($ProfileObject.qrCodeImage.value) + + if (($ProfileObject.enrollmentMode -eq 'corporateOwnedAOSPUserlessDevice' -or $ProfileObject.enrollmentMode -eq 'corporateOwnedAOSPUserAssociatedDevice') -and $MissingTokenData -and -not [string]::IsNullOrWhiteSpace($ProfileObject.id)) { + try { + $ProfileDetails = New-GraphGetRequest -uri "$BaseUri/$($ProfileObject.id)?`$select=$EncodedSelect" -tenantid $TenantFilter + foreach ($Property in $ProfileDetails.PSObject.Properties) { + $ProfileObject | Add-Member -NotePropertyName $Property.Name -NotePropertyValue $Property.Value -Force + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to hydrate Android enrollment token fields for profile $($ProfileObject.displayName ?? $ProfileObject.id)" -Sev Warning -LogData $ErrorMessage + } + } + + $ProfileObject + } + + $StatusCode = [HttpStatusCode]::OK + } catch { + $StatusCode = [HttpStatusCode]::InternalServerError + $ErrorMessage = Get-CippException -Exception $_ + $Results = "Failed to list Android enrollment profiles: $($ErrorMessage.NormalizedMessage)" + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Results -Sev Error -LogData $ErrorMessage + } + + return ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{ Results = @($Results) } + }) +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAppleEnrollmentProfiles.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAppleEnrollmentProfiles.ps1 new file mode 100644 index 000000000000..feb5afc8d9d8 --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListAppleEnrollmentProfiles.ps1 @@ -0,0 +1,83 @@ +function Invoke-ListAppleEnrollmentProfiles { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Endpoint.MEM.Read + .DESCRIPTION + Lists Apple Automated Device Enrollment tokens and enrollment profiles. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + + try { + $DepOnboardingSettings = @(New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings' -tenantid $TenantFilter) + $Tokens = foreach ($DepSetting in $DepOnboardingSettings) { + $Token = $DepSetting | Select-Object * + $Token | Add-Member -NotePropertyName 'daysUntilExpiration' -NotePropertyValue $( + if ($Token.tokenExpirationDateTime) { + [math]::Floor(([datetime]$Token.tokenExpirationDateTime - [datetime]::UtcNow).TotalDays) + } else { + $null + } + ) -Force + $Token | Add-Member -NotePropertyName 'isExpired' -NotePropertyValue $( + if ($Token.tokenExpirationDateTime) { ([datetime]$Token.tokenExpirationDateTime) -lt [datetime]::UtcNow } else { $false } + ) -Force + $Token + } + + $Profiles = foreach ($DepSetting in $DepOnboardingSettings) { + if ([string]::IsNullOrWhiteSpace($DepSetting.id)) { continue } + + try { + $EnrollmentProfiles = @(New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/depOnboardingSettings/$($DepSetting.id)/enrollmentProfiles" -tenantid $TenantFilter) + foreach ($EnrollmentProfile in $EnrollmentProfiles) { + $ProfileType = $EnrollmentProfile.'@odata.type' + $Platform = switch -Regex ($ProfileType) { + 'depMacOSEnrollmentProfile' { 'macOS'; break } + 'depIOSEnrollmentProfile' { 'iOS/iPadOS'; break } + 'depVisionOSEnrollmentProfile' { 'visionOS'; break } + 'depTvOSEnrollmentProfile' { 'tvOS'; break } + default { 'Unknown' } + } + + $ProfileObject = $EnrollmentProfile | Select-Object * + $ProfileObject | Add-Member -NotePropertyName 'platform' -NotePropertyValue $Platform -Force + $ProfileObject | Add-Member -NotePropertyName 'profileType' -NotePropertyValue 'apple' -Force + $ProfileObject | Add-Member -NotePropertyName 'tokenId' -NotePropertyValue $DepSetting.id -Force + $ProfileObject | Add-Member -NotePropertyName 'tokenName' -NotePropertyValue $DepSetting.tokenName -Force + $ProfileObject | Add-Member -NotePropertyName 'appleIdentifier' -NotePropertyValue $DepSetting.appleIdentifier -Force + $ProfileObject | Add-Member -NotePropertyName 'tokenExpirationDateTime' -NotePropertyValue $DepSetting.tokenExpirationDateTime -Force + $ProfileObject | Add-Member -NotePropertyName 'tokenType' -NotePropertyValue $DepSetting.tokenType -Force + $ProfileObject + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Failed to list Apple ADE profiles for token $($DepSetting.tokenName)" -Sev Warning -LogData $ErrorMessage + } + } + + $StatusCode = [HttpStatusCode]::OK + $Body = @{ + Results = @{ + Tokens = @($Tokens) + Profiles = @($Profiles) + } + } + } catch { + $StatusCode = [HttpStatusCode]::InternalServerError + $ErrorMessage = Get-CippException -Exception $_ + $Body = @{ Results = "Failed to list Apple ADE enrollment profiles: $($ErrorMessage.NormalizedMessage)" } + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Body.Results -Sev Error -LogData $ErrorMessage + } + + return ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $Body + }) +} diff --git a/cspell.json b/cspell.json index 16d358d26a90..42bdb4bb4fa4 100644 --- a/cspell.json +++ b/cspell.json @@ -7,6 +7,7 @@ "adminapi", "ADMS", "AITM", + "AOSP", "Autotask", "Bluetrait", "cipp", @@ -50,6 +51,7 @@ "Standardcal", "Terrl", "TNEF", + "Userless", "weburl", "winmail", "Yubikey" From 5ccf15a9197c67cb8fdde40606dd7b26a476ae1a Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Thu, 14 May 2026 20:23:40 +0200 Subject: [PATCH 02/90] fix: missing odata path error in the returned json Aka fix my own mistake lol --- .../Endpoint/MEM/Invoke-ListIntunePolicy.ps1 | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntunePolicy.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntunePolicy.ps1 index c01f718c2a5f..8461ef571cdf 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntunePolicy.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntunePolicy.ps1 @@ -78,8 +78,14 @@ function Invoke-ListIntunePolicy { } if ($DefinitionRequests.Count -gt 0) { + $HasDefinitionFailures = $false $DefinitionResults = New-GraphBulkRequest -Requests @($DefinitionRequests) -tenantid $TenantFilter foreach ($DefinitionResult in $DefinitionResults) { + if ($DefinitionResult.status -ne 200) { + $HasDefinitionFailures = $true + continue + } + $SettingId = $SettingIdMap[$DefinitionResult.id] $Setting = $GraphRequest.settings | Where-Object { $_.id -eq $SettingId } | Select-Object -First 1 if ($Setting) { @@ -87,6 +93,34 @@ function Invoke-ListIntunePolicy { $Setting | Add-Member -NotePropertyName settingDefinitions -NotePropertyValue $Definitions -Force } } + + if ($HasDefinitionFailures -and $GraphRequest.templateReference.templateId) { + try { + $TemplateSettingsResponse = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicyTemplates('$($GraphRequest.templateReference.templateId)')/settingTemplates?`$expand=settingDefinitions&`$top=1000" -tenantid $TenantFilter + $TemplateSettings = @($TemplateSettingsResponse.value ?? $TemplateSettingsResponse) + $TemplateDefinitionsByInstanceId = @{} + + foreach ($TemplateSetting in $TemplateSettings) { + $TemplateInstanceId = $TemplateSetting.settingInstanceTemplate.settingInstanceTemplateId + $TemplateDefinitions = @($TemplateSetting.settingDefinitions | Where-Object { $_.id }) + if ($TemplateInstanceId -and $TemplateDefinitions.Count -gt 0) { + $TemplateDefinitionsByInstanceId[$TemplateInstanceId] = $TemplateDefinitions + } + } + + foreach ($Setting in $GraphRequest.settings) { + $ExistingDefinitions = @($Setting.settingDefinitions | Where-Object { $_.id }) + if ($ExistingDefinitions.Count -gt 0) { continue } + + $TemplateInstanceId = $Setting.settingInstance.settingInstanceTemplateReference.settingInstanceTemplateId + if ($TemplateInstanceId -and $TemplateDefinitionsByInstanceId.ContainsKey($TemplateInstanceId)) { + $Setting | Add-Member -NotePropertyName settingDefinitions -NotePropertyValue $TemplateDefinitionsByInstanceId[$TemplateInstanceId] -Force + } + } + } catch { + Write-Information "Could not retrieve configuration policy template definitions for ${ID}: $($_.Exception.Message)" + } + } } } } elseif ($URLName -ieq 'GroupPolicyConfigurations') { From 5289a3092d34df70035ab12a2cc70daabeb2e77f Mon Sep 17 00:00:00 2001 From: Integrated Solutions Date: Fri, 15 May 2026 15:57:00 +1000 Subject: [PATCH 03/90] feat: ability to add/remove nested groups in group memberships --- .../HTTP Functions/Invoke-ListUsersAndGroups.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListUsersAndGroups.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListUsersAndGroups.ps1 index 5abd101abc09..4064ec8345bd 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListUsersAndGroups.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListUsersAndGroups.ps1 @@ -22,12 +22,12 @@ function Invoke-ListUsersAndGroups { @{ id = 'groups' method = 'GET' - url = "groups?`$select=id,displayName&`$top=999" + url = "groups?`$select=id,displayName,groupTypes,mailEnabled,securityEnabled&`$top=999" } ) $BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantFilter $Users = ($BulkResults | Where-Object { $_.id -eq 'users' }).body.value | Select-Object *, @{Name = '@odata.type'; Expression = { '#microsoft.graph.user' } } - $Groups = ($BulkResults | Where-Object { $_.id -eq 'groups' }).body.value | Select-Object id, displayName, @{Name = 'userPrincipalName'; Expression = { $null } }, @{Name = '@odata.type'; Expression = { '#microsoft.graph.group' } } + $Groups = ($BulkResults | Where-Object { $_.id -eq 'groups' }).body.value | Where-Object { $_.groupTypes -notcontains 'Unified' } | Select-Object id, displayName, mailEnabled, securityEnabled, @{Name = 'userPrincipalName'; Expression = { $null } }, @{Name = '@odata.type'; Expression = { '#microsoft.graph.group' } } $GraphRequest = @($Users) + @($Groups) | Sort-Object displayName $StatusCode = [HttpStatusCode]::OK } catch { @@ -37,6 +37,6 @@ function Invoke-ListUsersAndGroups { } return [HttpResponseContext]@{ StatusCode = $StatusCode - Body = @($GraphRequest) + Body = @{ Results = @($GraphRequest) } } } From 7ae35c2bd5c8f11169722651e69c18224dc8fde8 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Fri, 15 May 2026 02:28:44 -0500 Subject: [PATCH 04/90] post exec tweaks for dedupe queue names --- Config/FeatureFlags.json | 6 +- .../Push-CIPPDBCacheApplyBatch.ps1 | 3 +- .../Tests/Push-CIPPTestsApplyBatch.ps1 | 3 +- .../Start-CIPPDBTestsRun.ps1 | 6 +- .../CIPP/Settings/Invoke-ListWorkerHealth.ps1 | 110 ++++++++++++++++++ .../HTTP Functions/Invoke-ExecTestRun.ps1 | 3 +- 6 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 diff --git a/Config/FeatureFlags.json b/Config/FeatureFlags.json index 618c8c242094..35c51a44e865 100644 --- a/Config/FeatureFlags.json +++ b/Config/FeatureFlags.json @@ -34,13 +34,15 @@ "ListCIPPUsers", "ExecSSOSetup", "ExecContainerManagement", - "ListContainerLogs" + "ListContainerLogs", + "ListWorkerHealth" ], "Pages": [ "/cipp/advanced/super-admin/cipp-users", "/cipp/advanced/super-admin/sso", "/cipp/advanced/super-admin/container", - "/cipp/advanced/container-logs" + "/cipp/advanced/container-logs", + "/cipp/advanced/worker-health" ], "Hidden": true } diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheApplyBatch.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheApplyBatch.ps1 index 52b60b72436a..5a277a22d886 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheApplyBatch.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheApplyBatch.ps1 @@ -35,8 +35,9 @@ function Push-CIPPDBCacheApplyBatch { Write-Information "Aggregated $($AllTasks.Count) cache tasks from all tenants" # Start a single flat orchestrator to execute all cache tasks + $TenantSuffix = if ($Item.Parameters.TenantFilter) { "_$($Item.Parameters.TenantFilter)" } else { '' } $InputObject = [PSCustomObject]@{ - OrchestratorName = 'CIPPDBCacheExecute' + OrchestratorName = "CIPPDBCacheExecute$TenantSuffix" Batch = @($AllTasks) SkipLog = $true } diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsApplyBatch.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsApplyBatch.ps1 index aa12830a0a06..41236b25c316 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsApplyBatch.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsApplyBatch.ps1 @@ -35,8 +35,9 @@ function Push-CIPPTestsApplyBatch { Write-Information "Aggregated $($AllTasks.Count) test tasks from all tenants" # Start a single flat orchestrator to execute all test tasks + $TenantSuffix = if ($Item.Parameters.TenantFilter) { "_$($Item.Parameters.TenantFilter)" } else { '' } $InputObject = [PSCustomObject]@{ - OrchestratorName = 'CIPPTestsExecute' + OrchestratorName = "CIPPTestsExecute$TenantSuffix" Batch = @($AllTasks) SkipLog = $true } diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 index abf3057fb386..f310233164ba 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 @@ -69,12 +69,16 @@ function Start-CIPPDBTestsRun { Write-Information "Built batch of $($Batch.Count) tenant test list activities" # Phase 2 via PostExecution: Aggregate all task lists and start flat execution orchestrator + $NameSuffix = if ($TenantFilter -ne 'allTenants') { "-$TenantFilter" } else { '' } $InputObject = [PSCustomObject]@{ - OrchestratorName = 'TestsList' + OrchestratorName = "TestsList$NameSuffix" Batch = @($Batch) SkipLog = $true PostExecution = @{ FunctionName = 'CIPPTestsApplyBatch' + Parameters = @{ + TenantFilter = $TenantFilter + } } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 new file mode 100644 index 000000000000..66862547597b --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 @@ -0,0 +1,110 @@ +function Invoke-ListWorkerHealth { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + CIPP.SuperAdmin.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Action = $Request.Query.Action ?? 'Snapshot' + + try { + switch ($Action) { + 'Snapshot' { + $Snapshot = [Craft.Services.WorkerMetricsBridge]::GetSnapshot() + $Body = @{ Results = $Snapshot } + } + 'Summary' { + $Summary = [Craft.Services.WorkerMetricsBridge]::GetSummary() + $Body = @{ Results = $Summary } + } + 'Pool' { + $PoolType = $Request.Query.PoolType ?? 'http' + $Pool = [Craft.Services.WorkerMetricsBridge]::GetPoolMetrics($PoolType) + $Body = @{ Results = $Pool } + } + 'Jobs' { + $RunName = $Request.Query.RunName + $Status = $Request.Query.Status + $Limit = if ($Request.Query.Limit) { [int]$Request.Query.Limit } else { 100 } + $Jobs = [Craft.Services.WorkerMetricsBridge]::GetJobDetails($RunName, $Status, $Limit) + $Body = @{ Results = $Jobs } + } + 'Runs' { + $Runs = [Craft.Services.WorkerMetricsBridge]::GetRunSummaries() + $Body = @{ Results = $Runs } + } + 'CancelJob' { + $JobId = $Request.Query.JobId ?? $Request.Body.JobId + if (-not $JobId) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'JobId is required' } + } + } + $Result = [Craft.Services.WorkerMetricsBridge]::CancelJob($JobId) + $Body = @{ Results = @{ Success = $Result; JobId = $JobId } } + } + 'CancelRun' { + $RunName = $Request.Query.RunName ?? $Request.Body.RunName + if (-not $RunName) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'RunName is required' } + } + } + $Cancelled = [Craft.Services.WorkerMetricsBridge]::CancelRun($RunName) + $Body = @{ Results = @{ Success = $true; RunName = $RunName; CancelledCount = $Cancelled } } + } + 'DeleteJob' { + $JobId = $Request.Query.JobId ?? $Request.Body.JobId + if (-not $JobId) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'JobId is required' } + } + } + $Result = [Craft.Services.WorkerMetricsBridge]::DeleteJob($JobId) + $Body = @{ Results = @{ Success = $Result; JobId = $JobId } } + } + 'PurgeCompleted' { + $Purged = [Craft.Services.WorkerMetricsBridge]::PurgeCompleted() + $Body = @{ Results = @{ Success = $true; PurgedCount = $Purged } } + } + 'ChangePriority' { + $JobId = $Request.Query.JobId ?? $Request.Body.JobId + $NewPriority = $Request.Query.Priority ?? $Request.Body.Priority + if (-not $JobId -or $null -eq $NewPriority) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'JobId and Priority are required' } + } + } + $Result = [Craft.Services.WorkerMetricsBridge]::ChangePriority($JobId, [int]$NewPriority) + $Body = @{ Results = @{ Success = $Result; JobId = $JobId; NewPriority = [int]$NewPriority } } + } + default { + $Body = @{ Results = "Unknown action: $Action" } + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = $Body + } + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -message "Worker health error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::InternalServerError + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecTestRun.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecTestRun.ps1 index bd7fe721f34e..47eac1c03f7d 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecTestRun.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecTestRun.ps1 @@ -17,12 +17,11 @@ function Invoke-ExecTestRun { @{ FunctionName = 'CIPPDBCacheData' TenantFilter = $TenantFilter - QueueId = $Queue.RowKey QueueName = "Cache - $TenantFilter" } ) $InputObject = [PSCustomObject]@{ - OrchestratorName = 'TestDataCollectionAndRun' + OrchestratorName = "TestDataCollectionAndRun-$TenantFilter" Batch = $Batch SkipLog = $false PostExecution = @{ From fd6e30f62fb209f2b706b359bd1b91f5ef368081 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Fri, 15 May 2026 20:29:45 +0200 Subject: [PATCH 05/90] fix(standards): target azureADRegistration in intuneRestrictUserDeviceRegistration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standard was writing to azureADJoin.allowedToJoin despite its name — that's the join scope, not registration. Re-point reads and writes to azureADRegistration.allowedToRegister. Co-Authored-By: Claude Opus 4.7 --- ...rdintuneRestrictUserDeviceRegistration.ps1 | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceRegistration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceRegistration.ps1 index bf9b6263633e..345ee1092b1b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceRegistration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceRegistration.ps1 @@ -40,7 +40,8 @@ function Invoke-CIPPStandardintuneRestrictUserDeviceRegistration { return } # Current M365 Config - $CurrentOdataType = $PreviousSetting.azureADJoin.allowedToJoin.'@odata.type' + $CurrentOdataType = $PreviousSetting.azureADRegistration.allowedToRegister.'@odata.type' + $IsAdminConfigurable = [bool]$PreviousSetting.azureADRegistration.isAdminConfigurable # Standards Config $DisableUserDeviceRegistration = [bool]$Settings.disableUserDeviceRegistration @@ -53,29 +54,31 @@ function Invoke-CIPPStandardintuneRestrictUserDeviceRegistration { if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device registration restriction is already configured (registering users allowed to join: $DesiredStateText)." -sev Info + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device registration restriction is already configured (users allowed to register: $DesiredStateText)." -sev Info + } elseif ($IsAdminConfigurable -eq $false) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Cannot remediate device registration restriction: azureADRegistration.isAdminConfigurable is false for this tenant (commonly because Intune is enabled). Skipping remediation.' -sev Warn } else { try { - $PreviousSetting.azureADJoin.allowedToJoin = @{ '@odata.type' = $DesiredOdataType; users = $null; groups = $null } + $PreviousSetting.azureADRegistration.allowedToRegister = @{ '@odata.type' = $DesiredOdataType; users = $null; groups = $null } $NewBody = ConvertTo-Json -Compress -InputObject $PreviousSetting -Depth 10 New-GraphPostRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -Type PUT -Body $NewBody -ContentType 'application/json' $CurrentOdataType = $DesiredOdataType $CurrentDisableUserDeviceRegistration = ($CurrentOdataType -eq '#microsoft.graph.noDeviceRegistrationMembership') $StateIsCorrect = ($CurrentOdataType -eq $DesiredOdataType) - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set device registration restriction (registering users allowed to join: $DesiredStateText)." -sev Info + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set device registration restriction (users allowed to register: $DesiredStateText)." -sev Info } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set device registration restriction (registering users allowed to join: $DesiredStateText). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set device registration restriction (users allowed to register: $DesiredStateText). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage } } } if ($Settings.alert -eq $true) { if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device registration restriction is configured as expected (registering users allowed to join: $DesiredStateText)." -sev Info + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device registration restriction is configured as expected (users allowed to register: $DesiredStateText)." -sev Info } else { - Write-StandardsAlert -message "Device registration restriction is not configured as expected (registering users allowed to join: $DesiredStateText)" -object @{ current = @{ disableUserDeviceRegistration = $CurrentDisableUserDeviceRegistration }; desired = @{ disableUserDeviceRegistration = $DisableUserDeviceRegistration } } -tenant $Tenant -standardName 'intuneRestrictUserDeviceRegistration' -standardId $Settings.standardId - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device registration restriction is not configured as expected (registering users allowed to join: $DesiredStateText)." -sev Info + Write-StandardsAlert -message "Device registration restriction is not configured as expected (users allowed to register: $DesiredStateText)" -object @{ current = @{ disableUserDeviceRegistration = $CurrentDisableUserDeviceRegistration }; desired = @{ disableUserDeviceRegistration = $DisableUserDeviceRegistration } } -tenant $Tenant -standardName 'intuneRestrictUserDeviceRegistration' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device registration restriction is not configured as expected (users allowed to register: $DesiredStateText)." -sev Info } } From c67bc8dd9b892ebfaeaf80a9037941d46d53d745 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Fri, 15 May 2026 20:29:46 +0200 Subject: [PATCH 06/90] feat(standards): add intuneRestrictUserDeviceJoin standard Restores the azureADJoin.allowedToJoin behavior that previously lived (mislabeled) inside intuneRestrictUserDeviceRegistration. Join and registration are independent scopes, so each needs its own standard. Co-Authored-By: Claude Opus 4.7 --- ...PPStandardintuneRestrictUserDeviceJoin.ps1 | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceJoin.ps1 diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceJoin.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceJoin.ps1 new file mode 100644 index 000000000000..e036f2577e91 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneRestrictUserDeviceJoin.ps1 @@ -0,0 +1,95 @@ +function Invoke-CIPPStandardintuneRestrictUserDeviceJoin { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) intuneRestrictUserDeviceJoin + .SYNOPSIS + (Label) Configure user restriction for Entra device join + .DESCRIPTION + (Helptext) Controls whether users can join devices to Entra. + (DocsDescription) Configures whether users can join devices to Entra. When disabled, users are unable to Entra-join devices, which prevents them from creating new Entra-joined (cloud-managed) device identities. + .NOTES + CAT + Entra (AAD) Standards + TAG + EXECUTIVETEXT + Controls whether employees can join their devices to the corporate Entra directory. Disabling user device join prevents unauthorized or unmanaged devices from becoming corporate-managed identities, enhancing overall security posture. + ADDEDCOMPONENT + {"type":"switch","name":"standards.intuneRestrictUserDeviceJoin.disableUserDeviceJoin","label":"Disable users from joining devices","defaultValue":true} + IMPACT + High Impact + ADDEDDATE + 2026-05-15 + POWERSHELLEQUIVALENT + Update-MgBetaPolicyDeviceRegistrationPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param($Tenant, $Settings) + + try { + $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the intuneRestrictUserDeviceJoin state for $Tenant. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + return + } + # Current M365 Config + $CurrentOdataType = $PreviousSetting.azureADJoin.allowedToJoin.'@odata.type' + $IsAdminConfigurable = [bool]$PreviousSetting.azureADJoin.isAdminConfigurable + + # Standards Config + $DisableUserDeviceJoin = [bool]$Settings.disableUserDeviceJoin + + # State comparison + $DesiredOdataType = if ($DisableUserDeviceJoin) { '#microsoft.graph.noDeviceRegistrationMembership' } else { '#microsoft.graph.allDeviceRegistrationMembership' } + $CurrentDisableUserDeviceJoin = ($CurrentOdataType -eq '#microsoft.graph.noDeviceRegistrationMembership') + $StateIsCorrect = ($CurrentOdataType -eq $DesiredOdataType) + $DesiredStateText = if ($DisableUserDeviceJoin) { 'disabled' } else { 'enabled' } + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device join restriction is already configured (users allowed to join: $DesiredStateText)." -sev Info + } elseif ($IsAdminConfigurable -eq $false) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Cannot remediate device join restriction: azureADJoin.isAdminConfigurable is false for this tenant. Skipping remediation.' -sev Warn + } else { + try { + $PreviousSetting.azureADJoin.allowedToJoin = @{ '@odata.type' = $DesiredOdataType; users = $null; groups = $null } + $NewBody = ConvertTo-Json -Compress -InputObject $PreviousSetting -Depth 10 + New-GraphPostRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -Type PUT -Body $NewBody -ContentType 'application/json' + $CurrentOdataType = $DesiredOdataType + $CurrentDisableUserDeviceJoin = ($CurrentOdataType -eq '#microsoft.graph.noDeviceRegistrationMembership') + $StateIsCorrect = ($CurrentOdataType -eq $DesiredOdataType) + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set device join restriction (users allowed to join: $DesiredStateText)." -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set device join restriction (users allowed to join: $DesiredStateText). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device join restriction is configured as expected (users allowed to join: $DesiredStateText)." -sev Info + } else { + Write-StandardsAlert -message "Device join restriction is not configured as expected (users allowed to join: $DesiredStateText)" -object @{ current = @{ disableUserDeviceJoin = $CurrentDisableUserDeviceJoin }; desired = @{ disableUserDeviceJoin = $DisableUserDeviceJoin } } -tenant $Tenant -standardName 'intuneRestrictUserDeviceJoin' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Device join restriction is not configured as expected (users allowed to join: $DesiredStateText)." -sev Info + } + } + + if ($Settings.report -eq $true) { + $CurrentValue = @{ + disableUserDeviceJoin = $CurrentDisableUserDeviceJoin + } + $ExpectedValue = @{ + disableUserDeviceJoin = $DisableUserDeviceJoin + } + Set-CIPPStandardsCompareField -FieldName 'standards.intuneRestrictUserDeviceJoin' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant + Add-CIPPBPAField -FieldName 'intuneRestrictUserDeviceJoin' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} From 90b6457d590ea6f6b96c62e8e0315ccd1f56f640 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 18 May 2026 07:16:57 -0400 Subject: [PATCH 07/90] Add AutoExpandingArchiveScope property showing org-level vs mailbox-level enablement --- .../Users/Invoke-ListUserMailboxDetails.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 index 2cc8b8d3978e..01d95a67a2d5 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserMailboxDetails.ps1 @@ -83,11 +83,16 @@ function Invoke-ListUserMailboxDetails { $ArchiveEnabled = $false } - # Get organization config of auto-expanding archive if it's disabled on user level - if (-not $MailboxDetailedRequest.AutoExpandingArchiveEnabled -and $ArchiveEnabled) { - $AutoExpandingArchiveEnabled = $OrgConfig.AutoExpandingArchiveEnabled + # Check org-level first; if enabled org-wide, report that. Otherwise use mailbox-specific value. + if ($OrgConfig.AutoExpandingArchiveEnabled) { + $AutoExpandingArchiveEnabled = $true + $AutoExpandingArchiveScope = 'Organization' + } elseif ($MailboxDetailedRequest.AutoExpandingArchiveEnabled) { + $AutoExpandingArchiveEnabled = $true + $AutoExpandingArchiveScope = 'Mailbox' } else { - $AutoExpandingArchiveEnabled = $MailboxDetailedRequest.AutoExpandingArchiveEnabled + $AutoExpandingArchiveEnabled = $false + $AutoExpandingArchiveScope = 'None' } } catch { $ArchiveEnabled = $false @@ -260,6 +265,7 @@ function Invoke-ListUserMailboxDetails { BlockedForSpam = $BlockedForSpam ArchiveMailBox = $ArchiveEnabled AutoExpandingArchive = $AutoExpandingArchiveEnabled + AutoExpandingArchiveScope = $AutoExpandingArchiveScope RecipientTypeDetails = $MailboxDetailedRequest.RecipientTypeDetails Mailbox = $MailboxDetailedRequest RetentionPolicy = $MailboxDetailedRequest.RetentionPolicy From ab83a2bb5dbcfba32770827466b17a882398898d Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 18 May 2026 07:56:08 -0400 Subject: [PATCH 08/90] Update Update-CIPPSAMRedirectUri.ps1 --- .../Public/Authentication/Update-CIPPSAMRedirectUri.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Update-CIPPSAMRedirectUri.ps1 b/Modules/CIPPCore/Public/Authentication/Update-CIPPSAMRedirectUri.ps1 index 356e922c876d..d6ed545baf51 100644 --- a/Modules/CIPPCore/Public/Authentication/Update-CIPPSAMRedirectUri.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Update-CIPPSAMRedirectUri.ps1 @@ -29,7 +29,7 @@ function Update-CIPPSAMRedirectUri { ) try { - $AppResponse = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appId='$($env:ApplicationID)')?`$select=id,web" -tenantid $env:TenantID -NoAuthCheck $true + $AppResponse = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appId='$($env:ApplicationID)')?`$select=id,web" -tenantid $env:TenantID -NoAuthCheck $true -AsApp $true $ExistingUris = @($AppResponse.web.redirectUris) $MissingUris = $RequiredUris | Where-Object { $_ -notin $ExistingUris } @@ -46,7 +46,7 @@ function Update-CIPPSAMRedirectUri { web = @{ redirectUris = $UpdatedUris } } | ConvertTo-Json -Depth 5 - New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($AppResponse.id)" -body $Body -tenantid $env:TenantID -type PATCH -NoAuthCheck $true + New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($AppResponse.id)" -body $Body -tenantid $env:TenantID -type PATCH -NoAuthCheck $true -AsApp $true Write-Information "[SAM-Redirect] Added redirect URIs: $($MissingUris -join ', ')" Write-LogMessage -API 'SAM-Redirect' -message "Added redirect URIs: $($MissingUris -join ', ')" -sev Info } catch { From d7cda8a309a9c133e98de54257482bb4ef399abc Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 18 May 2026 07:57:41 -0400 Subject: [PATCH 09/90] Update Initialize-CIPPAuth.ps1 --- .../Authentication/Initialize-CIPPAuth.ps1 | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 index f5f48e325add..9f618899c237 100644 --- a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 @@ -108,7 +108,58 @@ function Initialize-CIPPAuth { } } - # 5. Post-migration cleanup: if CIPP_SSO_MIGRATION_APPID is still set but EasyAuth + # 5. Reconcile EasyAuth issuer with SSOMultiTenant setting: if EasyAuth is already + # configured, check whether the issuer URL matches the current SSOMultiTenant value + # from Key Vault. If it changed (e.g. toggled from single to multi-tenant), update + # the EasyAuth config via ARM and restart. + if ($EasyAuthEnabled -and $AuthState.HasSAMCredentials -and -not $env:CIPP_SSO_MIGRATION_APPID) { + try { + $AuthConfigJson = $env:WEBSITE_AUTH_V2_CONFIG_JSON + if ($AuthConfigJson) { + $AuthConfig = $AuthConfigJson | ConvertFrom-Json -ErrorAction Stop + $CurrentIssuer = $AuthConfig.identityProviders.azureActiveDirectory.registration.openIdIssuer + $ConfiguredAppId = $AuthConfig.identityProviders.azureActiveDirectory.registration.clientId + + if ($CurrentIssuer -and $ConfiguredAppId) { + # Read SSOMultiTenant from KV/DevSecrets + $SSOMultiTenant = $false + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + try { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + $SSOMultiTenant = $Secret.SSOMultiTenant -eq 'True' + } catch { } + } elseif ($KVName) { + try { + $MtVal = Get-CippKeyVaultSecret -VaultName $KVName -Name 'SSOMultiTenant' -AsPlainText -ErrorAction Stop + $SSOMultiTenant = $MtVal -eq 'True' + } catch { } + } + + $ExpectedIssuer = if ($SSOMultiTenant) { + 'https://login.microsoftonline.com/common/v2.0' + } else { + "https://login.microsoftonline.com/$($env:TenantID)/v2.0" + } + + if ($CurrentIssuer -ne $ExpectedIssuer) { + Write-Information "[Auth-Init] EasyAuth issuer mismatch: current=$CurrentIssuer expected=$ExpectedIssuer — updating" + $Configured = Set-CIPPSSOEasyAuth -AppId $ConfiguredAppId -MultiTenant $SSOMultiTenant -TenantId $env:TenantID + if ($Configured) { + Write-Information '[Auth-Init] EasyAuth issuer updated — requesting container restart' + [Craft.Services.AppLifecycleBridge]::RequestRestart('EasyAuth issuer updated to match SSOMultiTenant setting during warmup') + } + } else { + Write-Information "[Auth-Init] EasyAuth issuer matches SSOMultiTenant setting ($SSOMultiTenant) — no update needed" + } + } + } + } catch { + Write-Information "[Auth-Init] EasyAuth issuer reconciliation failed (non-fatal): $_" + } + } + + # 6. Post-migration cleanup: if CIPP_SSO_MIGRATION_APPID is still set but EasyAuth # is now configured, check whether the EasyAuth clientId still matches the migration # app. If it differs, the customer's own CIPP-SSO app is active and we can remove # the migration trigger env var. From ab5e5155681acb8e14994a80fd287a17f1aa3544 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 18 May 2026 09:24:48 -0400 Subject: [PATCH 10/90] Switch to app auth for authentication changes standard --- Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 | 4 ++-- ...nvoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 index 75d7971388a4..b3fedd2310ab 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 @@ -21,7 +21,7 @@ function Set-CIPPAuthenticationPolicy { $State = if ($Enabled) { 'enabled' } else { 'disabled' } # Get current state of the called authentication method and Set state of authentication method to input state try { - $CurrentInfo = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/$AuthenticationMethodId" -tenantid $Tenant + $CurrentInfo = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/$AuthenticationMethodId" -tenantid $Tenant -AsApp $True $CurrentInfo.state = $State } catch { $ErrorMessage = Get-CippException -Exception $_ @@ -137,7 +137,7 @@ function Set-CIPPAuthenticationPolicy { try { if ($PSCmdlet.ShouldProcess($AuthenticationMethodId, "Set state to $State $OptionalLogMessage")) { # Convert body to JSON and send request - $null = New-GraphPostRequest -tenantid $Tenant -Uri "https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/$AuthenticationMethodId" -Type PATCH -Body (ConvertTo-Json -InputObject $CurrentInfo -Compress -Depth 10) -ContentType 'application/json' + $null = New-GraphPostRequest -tenantid $Tenant -Uri "https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/$AuthenticationMethodId" -Type PATCH -Body (ConvertTo-Json -InputObject $CurrentInfo -Compress -Depth 10) -ContentType 'application/json' -AsApp $True Write-LogMessage -headers $Headers -API $APIName -tenant $Tenant -message "Set $AuthenticationMethodId state to $State $OptionalLogMessage" -sev Info } return "Set $AuthenticationMethodId state to $State $OptionalLogMessage" diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 index f698d0753527..6f40f3945f2f 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPWdisplayAppInformationRequiredState.ps1 @@ -48,7 +48,7 @@ function Invoke-CIPPStandardPWdisplayAppInformationRequiredState { param($Tenant, $Settings) try { - $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant + $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator' -tenantid $Tenant -AsApp $True } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the PWdisplayAppInformationRequiredState state for $Tenant. Error: $ErrorMessage" -Sev Error From 1b1ee689e9442896d9767f1cba736d6aa8b5d56f Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 19 May 2026 07:20:16 -0400 Subject: [PATCH 11/90] cache PowerShell enabled status and use cached data for standard --- .../DBCache/Set-CIPPDBCacheMailboxes.ps1 | 34 ++++++++++++------- ...tandardDisableExchangeOnlinePowerShell.ps1 | 4 +-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 index 5a75850f7412..4da4e1784514 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 @@ -25,19 +25,26 @@ function Set-CIPPDBCacheMailboxes { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching mailboxes' -sev Debug - # Get mailboxes with select properties - $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox,ForwardingSmtpAddress,DeliverToMailboxAndForward,ForwardingAddress,HiddenFromAddressListsEnabled,ExternalDirectoryObjectId,MessageCopyForSendOnBehalfEnabled,MessageCopyForSentAsEnabled,GrantSendOnBehalfTo,PersistedCapabilities,LitigationHoldEnabled,LitigationHoldDate,LitigationHoldDuration,ComplianceTagHoldApplied,RetentionHoldEnabled,InPlaceHolds,RetentionPolicy' - $ExoRequest = @{ - tenantid = $TenantFilter - cmdlet = 'Get-Mailbox' - cmdParams = @{} - Select = $Select + # Get mailboxes and user details in a single bulk request + $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox,ForwardingSmtpAddress,DeliverToMailboxAndForward,ForwardingAddress,HiddenFromAddressListsEnabled,ExternalDirectoryObjectId,MessageCopyForSendOnBehalfEnabled,MessageCopyForSentAsEnabled,GrantSendOnBehalfTo,PersistedCapabilities,LitigationHoldEnabled,LitigationHoldDate,LitigationHoldDuration,ComplianceTagHoldApplied,RetentionHoldEnabled,InPlaceHolds,RetentionPolicy,RemotePowerShellEnabled,Guid,Identity' + $BulkRequests = @( + @{ CmdletInput = @{ CmdletName = 'Get-Mailbox'; Parameters = @{} } } + @{ CmdletInput = @{ CmdletName = 'Get-User'; Parameters = @{} } } + ) + $BulkResults = New-ExoBulkRequest -tenantid $TenantFilter -cmdletArray $BulkRequests -useSystemMailbox $true -Select $Select -ReturnWithCommand $true + + # Build a lookup hashtable from Get-User results for O(1) matching + $UserLookup = @{} + foreach ($User in @($BulkResults.'Get-User')) { + if ($User.ExternalDirectoryObjectId) { + $UserLookup[$User.ExternalDirectoryObjectId] = $User + } } - # Use Generic List for better memory efficiency with large datasets - $Mailboxes = [System.Collections.Generic.List[PSObject]]::new() - $RawMailboxes = New-ExoRequest @ExoRequest - foreach ($Mailbox in $RawMailboxes) { + # Transform Get-Mailbox results and merge Get-User properties + $Mailboxes = [System.Collections.Generic.List[PSObject]]::new() + foreach ($Mailbox in @($BulkResults.'Get-Mailbox')) { + $MatchedUser = $UserLookup[$Mailbox.ExternalDirectoryObjectId] $Mailboxes.Add(($Mailbox | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, @{ Name = 'displayName'; Expression = { $_.'DisplayName' } }, @@ -60,7 +67,10 @@ function Set-CIPPDBCacheMailboxes { RetentionHoldEnabled, InPlaceHolds, RetentionPolicy, - GrantSendOnBehalfTo)) + GrantSendOnBehalfTo, + @{ Name = 'RemotePowerShellEnabled'; Expression = { $MatchedUser.RemotePowerShellEnabled } }, + @{ Name = 'Guid'; Expression = { $MatchedUser.Guid } }, + @{ Name = 'Identity'; Expression = { $MatchedUser.Identity } })) } $Mailboxes | Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Mailboxes' -AddCount diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 index c801a9924e67..e19319160827 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 @@ -66,7 +66,7 @@ function Invoke-CIPPStandardDisableExchangeOnlinePowerShell { } $AdminUsers = @($DirectAdminUPNs) + @($GroupMemberUPNs) | Where-Object { $_ } | Select-Object -Unique - $UsersWithPowerShell = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-User' -Select 'userPrincipalName, identity, guid, remotePowerShellEnabled' | Where-Object { $_.RemotePowerShellEnabled -eq $true -and $_.userPrincipalName -notin $AdminUsers } + $UsersWithPowerShell = New-CIPPDbRequest -TenantFilter $Tenant -Type 'Mailboxes' | Where-Object { $_.RemotePowerShellEnabled -eq $true -and $_.UPN -notin $AdminUsers } $PowerShellEnabledCount = ($UsersWithPowerShell | Measure-Object).Count $StateIsCorrect = $PowerShellEnabledCount -eq 0 } catch { @@ -83,7 +83,7 @@ function Invoke-CIPPStandardDisableExchangeOnlinePowerShell { @{ CmdletInput = @{ CmdletName = 'Set-User' - Parameters = @{Identity = $User.Guid; RemotePowerShellEnabled = $false } + Parameters = @{Identity = $User.Guid ?? $User.UPN; RemotePowerShellEnabled = $false } } } } From 6b8ebd4814625fa4902844dca619cb6deb1720eb Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 19 May 2026 07:40:37 -0400 Subject: [PATCH 12/90] refactor calls to use new onepass method to store DB data --- .../DBCache/Set-CIPPDBCacheAdminConsentRequestPolicy.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheAppRoleAssignments.ps1 | 3 +-- Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheApps.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheAuthenticationFlowsPolicy.ps1 | 3 +-- .../Set-CIPPDBCacheAuthenticationMethodsPolicy.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheAuthorizationPolicy.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheB2BManagementPolicy.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheBitlockerKeys.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 | 9 +++------ .../DBCache/Set-CIPPDBCacheCopilotReadinessActivity.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheCopilotUsageUserDetail.ps1 | 5 ++--- .../DBCache/Set-CIPPDBCacheCopilotUserCountSummary.ps1 | 5 ++--- .../DBCache/Set-CIPPDBCacheCopilotUserCountTrend.ps1 | 5 ++--- .../Set-CIPPDBCacheCredentialUserRegistrationDetails.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheCrossTenantAccessPolicy.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheCsExternalAccessPolicy.ps1 | 3 +-- .../Set-CIPPDBCacheCsTeamsAppPermissionPolicy.ps1 | 3 +-- .../Set-CIPPDBCacheCsTeamsClientConfiguration.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheCsTeamsMeetingPolicy.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheCsTeamsMessagingPolicy.ps1 | 3 +-- .../Set-CIPPDBCacheCsTenantFederationConfiguration.ps1 | 3 +-- .../Set-CIPPDBCacheDefaultAppManagementPolicy.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 | 9 +++------ .../DBCache/Set-CIPPDBCacheDeviceRegistrationPolicy.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheDeviceSettings.ps1 | 3 +-- Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDevices.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheDirectoryRecommendations.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 | 3 +-- Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDomains.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheExoAcceptedDomains.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoAdminAuditLogConfig.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoAntiPhishPolicies.ps1 | 6 ++---- .../DBCache/Set-CIPPDBCacheExoAtpPolicyForO365.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoDkimSigningConfig.ps1 | 3 +-- .../Set-CIPPDBCacheExoHostedContentFilterPolicy.ps1 | 3 +-- .../Set-CIPPDBCacheExoHostedOutboundSpamFilterPolicy.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoMalwareFilterPolicies.ps1 | 6 ++---- .../DBCache/Set-CIPPDBCacheExoOrganizationConfig.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoQuarantinePolicy.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheExoRemoteDomain.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoSafeAttachmentPolicies.ps1 | 6 ++---- .../DBCache/Set-CIPPDBCacheExoSafeLinksPolicies.ps1 | 6 ++---- .../Public/DBCache/Set-CIPPDBCacheExoSharingPolicy.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheExoTenantAllowBlockList.ps1 | 6 ++---- .../Public/DBCache/Set-CIPPDBCacheExoTransportRules.ps1 | 3 +-- Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGroups.ps1 | 6 ++---- Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGuests.ps1 | 3 +-- .../Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 | 9 +++------ .../Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 | 6 ++---- .../DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 | 6 ++---- .../Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 | 6 ++---- .../Public/DBCache/Set-CIPPDBCacheLicenseOverview.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheMDEOnboarding.ps1 | 3 +-- .../CIPPDB/Public/DBCache/Set-CIPPDBCacheMFAState.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheMailboxUsage.ps1 | 3 +-- .../Set-CIPPDBCacheManagedDeviceEncryptionStates.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheManagedDevices.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheOAuth2PermissionGrants.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheOfficeActivations.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 | 6 ++---- .../Public/DBCache/Set-CIPPDBCacheOrganization.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheOwaMailboxPolicy.ps1 | 3 +-- .../CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 | 6 ++---- .../DBCache/Set-CIPPDBCacheReportSubmissionPolicy.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheRiskDetections.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheRiskyServicePrincipals.ps1 | 3 +-- .../CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyUsers.ps1 | 3 +-- .../Set-CIPPDBCacheRoleAssignmentScheduleInstances.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheRoleEligibilitySchedules.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheRoleManagementPolicies.ps1 | 3 +-- Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoles.ps1 | 6 ++---- .../CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenant.ps1 | 3 +-- .../Set-CIPPDBCacheSPOTenantSyncClientRestriction.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 | 3 +-- .../Set-CIPPDBCacheServicePrincipalRiskDetections.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheServicePrincipals.ps1 | 3 +-- .../CIPPDB/Public/DBCache/Set-CIPPDBCacheSettings.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 | 6 ++---- Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeams.ps1 | 3 +-- .../Public/DBCache/Set-CIPPDBCacheTeamsActivity.ps1 | 3 +-- .../CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsVoice.ps1 | 3 +-- .../DBCache/Set-CIPPDBCacheUserRegistrationDetails.ps1 | 3 +-- 86 files changed, 108 insertions(+), 213 deletions(-) diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAdminConsentRequestPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAdminConsentRequestPolicy.ps1 index b14ae9e2113d..66f32dbde1c0 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAdminConsentRequestPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAdminConsentRequestPolicy.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheAdminConsentRequestPolicy { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching admin consent request policy' -sev Debug $ConsentPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/adminConsentRequestPolicy' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AdminConsentRequestPolicy' -Data @($ConsentPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AdminConsentRequestPolicy' -Data @($ConsentPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AdminConsentRequestPolicy' -Data @($ConsentPolicy) -AddCount $ConsentPolicy = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached admin consent request policy successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAppRoleAssignments.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAppRoleAssignments.ps1 index 93433d6c27f9..6db0d82a8769 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAppRoleAssignments.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAppRoleAssignments.ps1 @@ -39,8 +39,7 @@ function Set-CIPPDBCacheAppRoleAssignments { } if ($AllAppRoleAssignments.Count -gt 0) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AppRoleAssignments' -Data $AllAppRoleAssignments - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AppRoleAssignments' -Data $AllAppRoleAssignments -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AppRoleAssignments' -Data $AllAppRoleAssignments -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AllAppRoleAssignments.Count) app role assignments" -sev Debug } $AllAppRoleAssignments = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheApps.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheApps.ps1 index ef8acc12acf4..56b65c6c07d1 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheApps.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheApps.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheApps { $Apps = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/applications?$top=999&expand=owners' -tenantid $TenantFilter if (!$Apps) { $Apps = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Apps' -Data $Apps - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Apps' -Data $Apps -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Apps' -Data $Apps -AddCount $Apps = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached applications successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationFlowsPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationFlowsPolicy.ps1 index cac9230929b9..d25be4186873 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationFlowsPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationFlowsPolicy.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheAuthenticationFlowsPolicy { $AuthFlowPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationFlowsPolicy' -tenantid $TenantFilter -AsApp $true if ($AuthFlowPolicy) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationFlowsPolicy' -Data @($AuthFlowPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationFlowsPolicy' -Data @($AuthFlowPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationFlowsPolicy' -Data @($AuthFlowPolicy) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached authentication flows policy successfully' -sev Debug } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationMethodsPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationMethodsPolicy.ps1 index 2ae7c00b416b..e1b63f15b8ca 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationMethodsPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthenticationMethodsPolicy.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheAuthenticationMethodsPolicy { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching authentication methods policy' -sev Debug $AuthMethodsPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationMethodsPolicy' -Data @($AuthMethodsPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationMethodsPolicy' -Data @($AuthMethodsPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationMethodsPolicy' -Data @($AuthMethodsPolicy) -AddCount $AuthMethodsPolicy = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached authentication methods policy successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthorizationPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthorizationPolicy.ps1 index 6aedd8b765c4..7811b92867b9 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthorizationPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheAuthorizationPolicy.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheAuthorizationPolicy { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching authorization policy' -sev Debug $AuthPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authorizationPolicy' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthorizationPolicy' -Data @($AuthPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthorizationPolicy' -Data @($AuthPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthorizationPolicy' -Data @($AuthPolicy) -AddCount $AuthPolicy = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached authorization policy successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheB2BManagementPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheB2BManagementPolicy.ps1 index b85af4dac1c1..0a3b901af6f2 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheB2BManagementPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheB2BManagementPolicy.ps1 @@ -23,8 +23,7 @@ function Set-CIPPDBCacheB2BManagementPolicy { $B2BManagementPolicy = $LegacyPolicies if ($B2BManagementPolicy) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'B2BManagementPolicy' -Data @($B2BManagementPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'B2BManagementPolicy' -Data @($B2BManagementPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'B2BManagementPolicy' -Data @($B2BManagementPolicy) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached B2B management policy successfully' -sev Debug } else { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No B2B management policy found' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheBitlockerKeys.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheBitlockerKeys.ps1 index 8bb2bf9971fd..71b70fd9a250 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheBitlockerKeys.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheBitlockerKeys.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheBitlockerKeys { $BitlockerKeys = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/informationProtection/bitlocker/recoveryKeys' -tenantid $TenantFilter if (!$BitlockerKeys) { $BitlockerKeys = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'BitlockerKeys' -Data $BitlockerKeys - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'BitlockerKeys' -Data $BitlockerKeys -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'BitlockerKeys' -Data $BitlockerKeys -AddCount $BitlockerKeys = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached BitLocker recovery keys successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 index d5bd3ec19cde..a4407e0b82d3 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 @@ -29,8 +29,7 @@ function Set-CIPPDBCacheConditionalAccessPolicies { try { $CAPolicies = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies?$top=999' -tenantid $TenantFilter if ($CAPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ConditionalAccessPolicies' -Data $CAPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ConditionalAccessPolicies' -Data $CAPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ConditionalAccessPolicies' -Data $CAPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($CAPolicies.Count) CA policies" -sev Debug } $CAPolicies = $null @@ -42,8 +41,7 @@ function Set-CIPPDBCacheConditionalAccessPolicies { $NamedLocations = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations?$top=999' -tenantid $TenantFilter if ($NamedLocations) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'NamedLocations' -Data $NamedLocations - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'NamedLocations' -Data $NamedLocations -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'NamedLocations' -Data $NamedLocations -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($NamedLocations.Count) named locations" -sev Debug } $NamedLocations = $null @@ -55,8 +53,7 @@ function Set-CIPPDBCacheConditionalAccessPolicies { $AuthStrengths = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/authenticationStrength/policies' -tenantid $TenantFilter if ($AuthStrengths) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationStrengths' -Data $AuthStrengths - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationStrengths' -Data $AuthStrengths -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'AuthenticationStrengths' -Data $AuthStrengths -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AuthStrengths.Count) authentication strengths" -sev Debug } $AuthStrengths = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotReadinessActivity.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotReadinessActivity.ps1 index c055e34ef989..48131fe4b39b 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotReadinessActivity.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotReadinessActivity.ps1 @@ -44,8 +44,7 @@ function Set-CIPPDBCacheCopilotReadinessActivity { } } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotReadinessActivity' -Data $FlattenedData - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotReadinessActivity' -Data $FlattenedData -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotReadinessActivity' -Data $FlattenedData -AddCount $FlattenedData = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Copilot readiness activity successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUsageUserDetail.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUsageUserDetail.ps1 index 7f3be5239050..04757c42bda0 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUsageUserDetail.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUsageUserDetail.ps1 @@ -22,12 +22,11 @@ function Set-CIPPDBCacheCopilotUsageUserDetail { $Data = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMicrosoft365CopilotUsageUserDetail(period='D30')" -tenantid $TenantFilter -AsApp $true if ($Data) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUsageUserDetail' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUsageUserDetail' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUsageUserDetail' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Data.Count) Copilot usage user detail records" -sev Debug } else { # Write an empty marker so tests can distinguish "no data yet" from "cache not run" - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUsageUserDetail' -Data @() -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUsageUserDetail' -Data @() -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Copilot usage user detail: no records returned (no active Copilot usage)' -sev Debug } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountSummary.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountSummary.ps1 index a667626c32b4..4fc0c9f40265 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountSummary.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountSummary.ps1 @@ -22,11 +22,10 @@ function Set-CIPPDBCacheCopilotUserCountSummary { $Data = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMicrosoft365CopilotUserCountSummary(period='D30')" -tenantid $TenantFilter -AsApp $true if ($Data) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountSummary' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountSummary' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountSummary' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Copilot user count summary' -sev Debug } else { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountSummary' -Data @() -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountSummary' -Data @() -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Copilot user count summary: no records returned (no active Copilot usage)' -sev Debug } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountTrend.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountTrend.ps1 index 6f6034b958c3..227402889db4 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountTrend.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCopilotUserCountTrend.ps1 @@ -22,11 +22,10 @@ function Set-CIPPDBCacheCopilotUserCountTrend { $Data = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMicrosoft365CopilotUserCountTrend(period='D7')?`$format=application/json" -tenantid $TenantFilter -AsApp $true if ($Data) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountTrend' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountTrend' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountTrend' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Data.Count) Copilot user count trend records" -sev Debug } else { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountTrend' -Data @() -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CopilotUserCountTrend' -Data @() -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Copilot user count trend: no records returned (no active Copilot usage)' -sev Debug } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCredentialUserRegistrationDetails.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCredentialUserRegistrationDetails.ps1 index 5242dffdbbf7..198e467ebcc2 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCredentialUserRegistrationDetails.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCredentialUserRegistrationDetails.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheCredentialUserRegistrationDetails { $CredentialUserRegistrationDetails = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails' -tenantid $TenantFilter if ($CredentialUserRegistrationDetails) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CredentialUserRegistrationDetails' -Data $CredentialUserRegistrationDetails - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CredentialUserRegistrationDetails' -Data $CredentialUserRegistrationDetails -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CredentialUserRegistrationDetails' -Data $CredentialUserRegistrationDetails -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($CredentialUserRegistrationDetails.Count) credential user registration details" -sev Debug } $CredentialUserRegistrationDetails = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCrossTenantAccessPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCrossTenantAccessPolicy.ps1 index 1578be33762c..beeacb65cfba 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCrossTenantAccessPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCrossTenantAccessPolicy.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheCrossTenantAccessPolicy { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching cross-tenant access policy' -sev Debug $CrossTenantPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/crossTenantAccessPolicy/default' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CrossTenantAccessPolicy' -Data @($CrossTenantPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CrossTenantAccessPolicy' -Data @($CrossTenantPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CrossTenantAccessPolicy' -Data @($CrossTenantPolicy) -AddCount $CrossTenantPolicy = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached cross-tenant access policy successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsExternalAccessPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsExternalAccessPolicy.ps1 index 9889b37b7a6c..96afe6b7eab9 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsExternalAccessPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsExternalAccessPolicy.ps1 @@ -28,8 +28,7 @@ function Set-CIPPDBCacheCsExternalAccessPolicy { if ($ExternalAccess) { $Data = @($ExternalAccess) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsExternalAccessPolicy' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsExternalAccessPolicy' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsExternalAccessPolicy' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Teams External Access Policy' -sev Debug } $ExternalAccess = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsAppPermissionPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsAppPermissionPolicy.ps1 index 206c1a806741..66c2a171aeca 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsAppPermissionPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsAppPermissionPolicy.ps1 @@ -28,8 +28,7 @@ function Set-CIPPDBCacheCsTeamsAppPermissionPolicy { if ($AppPermissionPolicies) { $Data = @($AppPermissionPolicies) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsAppPermissionPolicy' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsAppPermissionPolicy' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsAppPermissionPolicy' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Data.Count) Teams App Permission Policies" -sev Debug } $AppPermissionPolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsClientConfiguration.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsClientConfiguration.ps1 index de1d018ff9c9..1db2ec626fe9 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsClientConfiguration.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsClientConfiguration.ps1 @@ -29,8 +29,7 @@ function Set-CIPPDBCacheCsTeamsClientConfiguration { if ($ClientConfig) { $Data = @($ClientConfig) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsClientConfiguration' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsClientConfiguration' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsClientConfiguration' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Teams Client Configuration' -sev Debug } $ClientConfig = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMeetingPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMeetingPolicy.ps1 index 7dcea7b968ee..5a67acdb1ddb 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMeetingPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMeetingPolicy.ps1 @@ -28,8 +28,7 @@ function Set-CIPPDBCacheCsTeamsMeetingPolicy { if ($MeetingPolicy) { $Data = @($MeetingPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsMeetingPolicy' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsMeetingPolicy' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsMeetingPolicy' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Teams Meeting Policy' -sev Debug } $MeetingPolicy = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMessagingPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMessagingPolicy.ps1 index e3696593c918..2fc1ef784c23 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMessagingPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTeamsMessagingPolicy.ps1 @@ -29,8 +29,7 @@ function Set-CIPPDBCacheCsTeamsMessagingPolicy { if ($MessagingPolicy) { $Data = @($MessagingPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsMessagingPolicy' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsMessagingPolicy' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTeamsMessagingPolicy' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Teams Messaging Policy' -sev Debug } $MessagingPolicy = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTenantFederationConfiguration.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTenantFederationConfiguration.ps1 index b5d76c0f89e5..47d09881239e 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTenantFederationConfiguration.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheCsTenantFederationConfiguration.ps1 @@ -29,8 +29,7 @@ function Set-CIPPDBCacheCsTenantFederationConfiguration { if ($Federation) { $Data = @($Federation) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTenantFederationConfiguration' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTenantFederationConfiguration' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'CsTenantFederationConfiguration' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Teams Tenant Federation Configuration' -sev Debug } $Federation = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDefaultAppManagementPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDefaultAppManagementPolicy.ps1 index c8126a8744d2..56886caa2735 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDefaultAppManagementPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDefaultAppManagementPolicy.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheDefaultAppManagementPolicy { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching default app management policy' -sev Debug $AppMgmtPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/defaultAppManagementPolicy' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DefaultAppManagementPolicy' -Data @($AppMgmtPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DefaultAppManagementPolicy' -Data @($AppMgmtPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DefaultAppManagementPolicy' -Data @($AppMgmtPolicy) -AddCount $AppMgmtPolicy = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached default app management policy successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 index c79b183c09c8..8783690f4b61 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDetectedApps.ps1 @@ -60,8 +60,7 @@ function Set-CIPPDBCacheDetectedApps { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Retrieved $($DetectedApps.Count) detected apps (expected $TotalCount)" -sev Debug if ($DetectedApps.Count -eq 0) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data @() - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data @() -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data @() -AddCount return } @@ -85,13 +84,11 @@ function Set-CIPPDBCacheDetectedApps { $App } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data $DetectedAppsWithDevices - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data $DetectedAppsWithDevices -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data $DetectedAppsWithDevices -AddCount $DetectedApps = $null $DetectedAppsWithDevices = $null } else { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data $DetectedApps - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data $DetectedApps -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DetectedApps' -Data $DetectedApps -AddCount $DetectedApps = $null } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceRegistrationPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceRegistrationPolicy.ps1 index 3ef85cfe05c6..876244bae3dc 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceRegistrationPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceRegistrationPolicy.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheDeviceRegistrationPolicy { $DeviceRegistrationPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $TenantFilter if ($DeviceRegistrationPolicy) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DeviceRegistrationPolicy' -Data @($DeviceRegistrationPolicy) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DeviceRegistrationPolicy' -Data @($DeviceRegistrationPolicy) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DeviceRegistrationPolicy' -Data @($DeviceRegistrationPolicy) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached device registration policy successfully' -sev Debug } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceSettings.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceSettings.ps1 index 2a0b4a480d91..79dd4c4cff82 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceSettings.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDeviceSettings.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheDeviceSettings { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching device settings' -sev Debug $DeviceSettings = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/directory/deviceLocalCredentials' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DeviceSettings' -Data @($DeviceSettings) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DeviceSettings' -Data @($DeviceSettings) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DeviceSettings' -Data @($DeviceSettings) -AddCount $DeviceSettings = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached device settings successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDevices.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDevices.ps1 index c0d056617209..cb2bec81698f 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDevices.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDevices.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheDevices { $Devices = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/devices?$top=999&$select=id,displayName,operatingSystem,operatingSystemVersion,trustType,accountEnabled,approximateLastSignInDateTime' -tenantid $TenantFilter if (!$Devices) { $Devices = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Devices' -Data $Devices - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Devices' -Data $Devices -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Devices' -Data $Devices -AddCount $Devices = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Azure AD devices successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDirectoryRecommendations.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDirectoryRecommendations.ps1 index 1a1e973cd695..75074c9ab3b4 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDirectoryRecommendations.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDirectoryRecommendations.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheDirectoryRecommendations { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching directory recommendations' -sev Debug $Recommendations = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/directory/recommendations?$top=999' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DirectoryRecommendations' -Data $Recommendations - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DirectoryRecommendations' -Data $Recommendations -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DirectoryRecommendations' -Data $Recommendations -AddCount $Recommendations = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached directory recommendations successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 index 776b868f163d..13d9a9927377 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 @@ -30,8 +30,7 @@ function Set-CIPPDBCacheDlpCompliancePolicies { $Policies = New-ExoRequest -TenantId $Tenant.customerId -cmdlet 'Get-DlpCompliancePolicy' -Compliance -Select 'Name,DisplayName,Mode,Enabled,Workload,CreatedBy,WhenCreatedUTC,WhenChangedUTC' if ($Policies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DlpCompliancePolicies' -Data $Policies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DlpCompliancePolicies' -Data $Policies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'DlpCompliancePolicies' -Data $Policies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Policies.Count) DLP compliance policies" -sev Debug } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDomains.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDomains.ps1 index 405342b9eefa..0a67432e77b8 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDomains.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDomains.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheDomains { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching domains' -sev Debug $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Domains' -Data @($Domains) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Domains' -Data @($Domains) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Domains' -Data @($Domains) -AddCount $Domains = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached domains successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAcceptedDomains.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAcceptedDomains.ps1 index b2af3cdbe3b8..be47cf4c3714 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAcceptedDomains.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAcceptedDomains.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoAcceptedDomains { $AcceptedDomains = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-AcceptedDomain' if ($AcceptedDomains) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAcceptedDomains' -Data $AcceptedDomains - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAcceptedDomains' -Data $AcceptedDomains -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAcceptedDomains' -Data $AcceptedDomains -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AcceptedDomains.Count) Accepted Domains" -sev Debug } $AcceptedDomains = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAdminAuditLogConfig.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAdminAuditLogConfig.ps1 index 2c0b2a10e863..d51a81c26ba2 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAdminAuditLogConfig.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAdminAuditLogConfig.ps1 @@ -24,8 +24,7 @@ function Set-CIPPDBCacheExoAdminAuditLogConfig { if ($AuditConfig) { # AdminAuditLogConfig returns a single object, wrap in array for consistency $AuditConfigArray = @($AuditConfig) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAdminAuditLogConfig' -Data $AuditConfigArray - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAdminAuditLogConfig' -Data $AuditConfigArray -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAdminAuditLogConfig' -Data $AuditConfigArray -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Exchange Admin Audit Log configuration' -sev Debug } $AuditConfig = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAntiPhishPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAntiPhishPolicies.ps1 index f35ba843e566..2e7fa2dbee1b 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAntiPhishPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAntiPhishPolicies.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoAntiPhishPolicies { # Get Anti-Phishing policies $AntiPhishPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-AntiPhishPolicy' if ($AntiPhishPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAntiPhishPolicies' -Data $AntiPhishPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAntiPhishPolicies' -Data $AntiPhishPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAntiPhishPolicies' -Data $AntiPhishPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AntiPhishPolicies.Count) Anti-Phishing policies" -sev Debug } $AntiPhishPolicies = $null @@ -31,8 +30,7 @@ function Set-CIPPDBCacheExoAntiPhishPolicies { # Get Anti-Phishing rules $AntiPhishRules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-AntiPhishRule' if ($AntiPhishRules) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAntiPhishRules' -Data $AntiPhishRules - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAntiPhishRules' -Data $AntiPhishRules -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAntiPhishRules' -Data $AntiPhishRules -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AntiPhishRules.Count) Anti-Phishing rules" -sev Debug } $AntiPhishRules = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAtpPolicyForO365.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAtpPolicyForO365.ps1 index c48fd4baab3a..b93320868475 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAtpPolicyForO365.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoAtpPolicyForO365.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheExoAtpPolicyForO365 { $AtpPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-AtpPolicyForO365' if ($AtpPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAtpPolicyForO365' -Data $AtpPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAtpPolicyForO365' -Data $AtpPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoAtpPolicyForO365' -Data $AtpPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AtpPolicies.Count) ATP policies for Office 365" -sev Debug } $AtpPolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoDkimSigningConfig.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoDkimSigningConfig.ps1 index aa7ea3bd4561..a0e3b6b2350a 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoDkimSigningConfig.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoDkimSigningConfig.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoDkimSigningConfig { $DkimConfig = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DkimSigningConfig' if ($DkimConfig) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoDkimSigningConfig' -Data $DkimConfig - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoDkimSigningConfig' -Data $DkimConfig -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoDkimSigningConfig' -Data $DkimConfig -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($DkimConfig.Count) DKIM configurations" -sev Debug } $DkimConfig = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedContentFilterPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedContentFilterPolicy.ps1 index cd71f9a00bc5..958648690c0e 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedContentFilterPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedContentFilterPolicy.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheExoHostedContentFilterPolicy { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Exchange Hosted Content Filter policies' -sev Debug $HostedContentFilterPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-HostedContentFilterPolicy' if ($HostedContentFilterPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoHostedContentFilterPolicy' -Data $HostedContentFilterPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoHostedContentFilterPolicy' -Data $HostedContentFilterPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoHostedContentFilterPolicy' -Data $HostedContentFilterPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($HostedContentFilterPolicies.Count) Hosted Content Filter policies" -sev Debug } $HostedContentFilterPolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedOutboundSpamFilterPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedOutboundSpamFilterPolicy.ps1 index a550c9031c8e..2a53545a2405 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedOutboundSpamFilterPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoHostedOutboundSpamFilterPolicy.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheExoHostedOutboundSpamFilterPolicy { $HostedOutboundSpamFilterPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-HostedOutboundSpamFilterPolicy' if ($HostedOutboundSpamFilterPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoHostedOutboundSpamFilterPolicy' -Data $HostedOutboundSpamFilterPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoHostedOutboundSpamFilterPolicy' -Data $HostedOutboundSpamFilterPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoHostedOutboundSpamFilterPolicy' -Data $HostedOutboundSpamFilterPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($HostedOutboundSpamFilterPolicies.Count) Hosted Outbound Spam Filter policies" -sev Debug } $HostedOutboundSpamFilterPolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoMalwareFilterPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoMalwareFilterPolicies.ps1 index 23f6b1f775ef..7d71ab954e5e 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoMalwareFilterPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoMalwareFilterPolicies.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoMalwareFilterPolicies { # Get Malware Filter policies $MalwarePolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-MalwareFilterPolicy' if ($MalwarePolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoMalwareFilterPolicies' -Data $MalwarePolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoMalwareFilterPolicies' -Data $MalwarePolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoMalwareFilterPolicies' -Data $MalwarePolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($MalwarePolicies.Count) Malware Filter policies" -sev Debug } $MalwarePolicies = $null @@ -31,8 +30,7 @@ function Set-CIPPDBCacheExoMalwareFilterPolicies { # Get Malware Filter rules $MalwareRules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-MalwareFilterRule' if ($MalwareRules) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoMalwareFilterRules' -Data $MalwareRules - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoMalwareFilterRules' -Data $MalwareRules -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoMalwareFilterRules' -Data $MalwareRules -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($MalwareRules.Count) Malware Filter rules" -sev Debug } $MalwareRules = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoOrganizationConfig.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoOrganizationConfig.ps1 index 27a8c481a7da..c07c203edaef 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoOrganizationConfig.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoOrganizationConfig.ps1 @@ -24,8 +24,7 @@ function Set-CIPPDBCacheExoOrganizationConfig { if ($OrgConfig) { # OrganizationConfig returns a single object, wrap in array for consistency $OrgConfigArray = @($OrgConfig) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoOrganizationConfig' -Data $OrgConfigArray - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoOrganizationConfig' -Data $OrgConfigArray -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoOrganizationConfig' -Data $OrgConfigArray -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Exchange Organization configuration' -sev Debug } $OrgConfig = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 index 0810cf69ade7..7930ec943f18 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 @@ -38,8 +38,7 @@ function Set-CIPPDBCacheExoPresetSecurityPolicy { } if ($AllRules.Count -gt 0) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoPresetSecurityPolicy' -Data $AllRules - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoPresetSecurityPolicy' -Data $AllRules -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoPresetSecurityPolicy' -Data $AllRules -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AllRules.Count) Preset Security Policy rules" -sev Debug } $EOPRules = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoQuarantinePolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoQuarantinePolicy.ps1 index b4bcdd7ac011..cd7fb1afbbd1 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoQuarantinePolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoQuarantinePolicy.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheExoQuarantinePolicy { $QuarantinePolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-QuarantinePolicy' if ($QuarantinePolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoQuarantinePolicy' -Data $QuarantinePolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoQuarantinePolicy' -Data $QuarantinePolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoQuarantinePolicy' -Data $QuarantinePolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($QuarantinePolicies.Count) Quarantine policies" -sev Debug } $QuarantinePolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoRemoteDomain.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoRemoteDomain.ps1 index e9b32337a9ee..dd9fb277b99a 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoRemoteDomain.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoRemoteDomain.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheExoRemoteDomain { $RemoteDomains = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-RemoteDomain' if ($RemoteDomains) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoRemoteDomain' -Data $RemoteDomains - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoRemoteDomain' -Data $RemoteDomains -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoRemoteDomain' -Data $RemoteDomains -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($RemoteDomains.Count) Remote Domains" -sev Debug } $RemoteDomains = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeAttachmentPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeAttachmentPolicies.ps1 index 28d8f587afc3..07867a4caff3 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeAttachmentPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeAttachmentPolicies.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoSafeAttachmentPolicies { # Get Safe Attachment policies $SafeAttachmentPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeAttachmentPolicy' if ($SafeAttachmentPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeAttachmentPolicies' -Data $SafeAttachmentPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeAttachmentPolicies' -Data $SafeAttachmentPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeAttachmentPolicies' -Data $SafeAttachmentPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($SafeAttachmentPolicies.Count) Safe Attachment policies" -sev Debug } $SafeAttachmentPolicies = $null @@ -31,8 +30,7 @@ function Set-CIPPDBCacheExoSafeAttachmentPolicies { # Get Safe Attachment rules $SafeAttachmentRules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeAttachmentRule' if ($SafeAttachmentRules) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeAttachmentRules' -Data $SafeAttachmentRules - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeAttachmentRules' -Data $SafeAttachmentRules -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeAttachmentRules' -Data $SafeAttachmentRules -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($SafeAttachmentRules.Count) Safe Attachment rules" -sev Debug } $SafeAttachmentRules = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeLinksPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeLinksPolicies.ps1 index f78468844344..65403b187268 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeLinksPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSafeLinksPolicies.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoSafeLinksPolicies { # Get Safe Links policies $SafeLinksPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksPolicy' if ($SafeLinksPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeLinksPolicies' -Data $SafeLinksPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeLinksPolicies' -Data $SafeLinksPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeLinksPolicies' -Data $SafeLinksPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($SafeLinksPolicies.Count) Safe Links policies" -sev Debug } $SafeLinksPolicies = $null @@ -31,8 +30,7 @@ function Set-CIPPDBCacheExoSafeLinksPolicies { # Get Safe Links rules $SafeLinksRules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SafeLinksRule' if ($SafeLinksRules) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeLinksRules' -Data $SafeLinksRules - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeLinksRules' -Data $SafeLinksRules -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSafeLinksRules' -Data $SafeLinksRules -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($SafeLinksRules.Count) Safe Links rules" -sev Debug } $SafeLinksRules = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSharingPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSharingPolicy.ps1 index ffaaed92ed0a..368c1dca953c 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSharingPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoSharingPolicy.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoSharingPolicy { $SharingPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-SharingPolicy' if ($SharingPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSharingPolicy' -Data $SharingPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSharingPolicy' -Data $SharingPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoSharingPolicy' -Data $SharingPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($SharingPolicies.Count) Sharing Policies" -sev Debug } $SharingPolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTenantAllowBlockList.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTenantAllowBlockList.ps1 index 3906bf3d55f8..a73dfafa7e2e 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTenantAllowBlockList.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTenantAllowBlockList.ps1 @@ -36,13 +36,11 @@ function Set-CIPPDBCacheExoTenantAllowBlockList { } if ($AllItems.Count -gt 0) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTenantAllowBlockList' -Data $AllItems - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTenantAllowBlockList' -Data $AllItems -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTenantAllowBlockList' -Data $AllItems -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($AllItems.Count) Tenant Allow/Block List items" -sev Debug } else { # Even if empty, store an empty array so test knows cache was populated - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTenantAllowBlockList' -Data @() - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTenantAllowBlockList' -Data @() -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTenantAllowBlockList' -Data @() -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached empty Tenant Allow/Block List' -sev Debug } $SenderItems = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportRules.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportRules.ps1 index f08860ac00c6..a557947d4ce0 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportRules.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportRules.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheExoTransportRules { $TransportRules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-TransportRule' if ($TransportRules) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTransportRules' -Data $TransportRules - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTransportRules' -Data $TransportRules -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTransportRules' -Data $TransportRules -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($TransportRules.Count) Transport Rules" -sev Debug } $TransportRules = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGroups.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGroups.ps1 index b0857fd24d21..03c843a41ce0 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGroups.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGroups.ps1 @@ -59,8 +59,7 @@ function Set-CIPPDBCacheGroups { $Group } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Groups' -Data $GroupsWithMembers - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Groups' -Data $GroupsWithMembers -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Groups' -Data $GroupsWithMembers -AddCount $Groups = $null $GroupsWithMembers = $null } else { @@ -82,8 +81,7 @@ function Set-CIPPDBCacheGroups { $Group | Add-Member -NotePropertyName 'calculatedGroupType' -NotePropertyValue $calculatedGroupType -Force $Group } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Groups' -Data $Groups - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Groups' -Data $Groups -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Groups' -Data $Groups -AddCount $Groups = $null } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGuests.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGuests.ps1 index 867d45b791cd..7a25235e6946 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGuests.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheGuests.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheGuests { $Guests = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$filter=userType eq 'Guest'&`$expand=sponsors&`$top=999" -tenantid $TenantFilter if (!$Guests) { $Guests = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Guests' -Data $Guests - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Guests' -Data $Guests -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Guests' -Data $Guests -AddCount $Guests = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached guest users successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 index d3dd14ad33fa..418d5a277852 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 @@ -74,12 +74,9 @@ function Set-CIPPDBCacheIntuneAppProtectionPolicies { if (-not $Groups) { $Groups = @() } if (-not $MobileAppConfigs) { $MobileAppConfigs = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionPolicyGroups' -Data @($Groups) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionPolicyGroups' -Data @($Groups) -Count - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionManagedAppPolicies' -Data @($ManagedAppPoliciesWithAssignments) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionManagedAppPolicies' -Data @($ManagedAppPoliciesWithAssignments) -Count - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionMobileAppConfigurations' -Data @($MobileAppConfigs) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionMobileAppConfigurations' -Data @($MobileAppConfigs) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionPolicyGroups' -Data @($Groups) -AddCount + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionManagedAppPolicies' -Data @($ManagedAppPoliciesWithAssignments) -AddCount + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAppProtectionMobileAppConfigurations' -Data @($MobileAppConfigs) -AddCount $TotalCount = (($ManagedAppPoliciesWithAssignments | Measure-Object).Count + ($MobileAppConfigs | Measure-Object).Count) Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $TotalCount app protection/configuration policies" -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 index 811f2af821d3..8594bc640137 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 @@ -34,10 +34,8 @@ function Set-CIPPDBCacheIntuneApplications { if (-not $Groups) { $Groups = @() } if (-not $Apps) { $Apps = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneApplicationGroups' -Data @($Groups) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneApplicationGroups' -Data @($Groups) -Count - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneApplications' -Data @($Apps) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneApplications' -Data @($Apps) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneApplicationGroups' -Data @($Groups) -AddCount + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneApplications' -Data @($Apps) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $(($Apps | Measure-Object).Count) Intune applications" -sev Debug } catch { diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 index 882021cfb4ea..e6037b1f1061 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 @@ -17,8 +17,7 @@ function Set-CIPPDBCacheIntuneAssignmentFilters { $AssignmentFilters = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/assignmentFilters' -tenantid $TenantFilter if (-not $AssignmentFilters) { $AssignmentFilters = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAssignmentFilters' -Data @($AssignmentFilters) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAssignmentFilters' -Data @($AssignmentFilters) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneAssignmentFilters' -Data @($AssignmentFilters) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $(($AssignmentFilters | Measure-Object).Count) assignment filters" -sev Debug } catch { diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 index 80256d0ce92c..04ea2689982f 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 @@ -34,10 +34,8 @@ function Set-CIPPDBCacheIntuneCompliancePolicies { if (-not $Groups) { $Groups = @() } if (-not $Policies) { $Policies = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneCompliancePolicyGroups' -Data @($Groups) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneCompliancePolicyGroups' -Data @($Groups) -Count - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneDeviceCompliancePolicies' -Data @($Policies) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneDeviceCompliancePolicies' -Data @($Policies) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneCompliancePolicyGroups' -Data @($Groups) -AddCount + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneDeviceCompliancePolicies' -Data @($Policies) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $(($Policies | Measure-Object).Count) compliance policies" -sev Debug } catch { diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 index 427965a7b3a0..4acc51cee07a 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 @@ -104,8 +104,7 @@ function Set-CIPPDBCacheIntunePolicies { } } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type "Intune$($PolicyType.Type)" -Data $Policies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type "Intune$($PolicyType.Type)" -Data $Policies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type "Intune$($PolicyType.Type)" -Data $Policies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Policies.Count) $($PolicyType.Type)" -sev Debug # Fetch device statuses for compliance policies using bulk requests diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 index 77a964147403..437bcd6607db 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 @@ -29,8 +29,7 @@ function Set-CIPPDBCacheIntuneReusableSettings { $Settings = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/reusablePolicySettings$SelectQuery" -tenantid $TenantFilter if (-not $Settings) { $Settings = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneReusableSettings' -Data @($Settings) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneReusableSettings' -Data @($Settings) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneReusableSettings' -Data @($Settings) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $(($Settings | Measure-Object).Count) reusable settings" -sev Debug } catch { diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 index 8fb780b7d4ff..1052c9505b9d 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 @@ -46,8 +46,7 @@ function Set-CIPPDBCacheIntuneScripts { $Groups = ($BulkResults | Where-Object { $_.id -eq 'Groups' }).body.value if (-not $Groups) { $Groups = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneScriptGroups' -Data @($Groups) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneScriptGroups' -Data @($Groups) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'IntuneScriptGroups' -Data @($Groups) -AddCount $TypeMap = @{ Windows = 'IntuneWindowsScripts' @@ -75,8 +74,7 @@ function Set-CIPPDBCacheIntuneScripts { }) } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type $TypeMap[$scriptId] -Data @($Scripts) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type $TypeMap[$scriptId] -Data @($Scripts) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type $TypeMap[$scriptId] -Data @($Scripts) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $(($Scripts | Measure-Object).Count) $scriptId scripts" -sev Debug } } catch { diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheLicenseOverview.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheLicenseOverview.ps1 index cb28201d7746..57b31b3702c1 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheLicenseOverview.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheLicenseOverview.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheLicenseOverview { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching license overview' -sev Debug $LicenseOverview = Get-CIPPLicenseOverview -TenantFilter $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'LicenseOverview' -Data @($LicenseOverview) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'LicenseOverview' -Data @($LicenseOverview) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'LicenseOverview' -Data @($LicenseOverview) -AddCount $LicenseOverview = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached license overview successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMDEOnboarding.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMDEOnboarding.ps1 index 6a0db7384460..ea251b0c8ff7 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMDEOnboarding.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMDEOnboarding.ps1 @@ -37,8 +37,7 @@ function Set-CIPPDBCacheMDEOnboarding { ) } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MDEOnboarding' -Data @($Result) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MDEOnboarding' -Data @($Result) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MDEOnboarding' -Data @($Result) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached MDE onboarding status successfully' -sev Debug } catch { diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMFAState.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMFAState.ps1 index ccd489fe14ce..6b3bfa8cf340 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMFAState.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMFAState.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheMFAState { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching MFA state' -sev Debug $MFAState = Get-CIPPMFAState -TenantFilter $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MFAState' -Data @($MFAState) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MFAState' -Data @($MFAState) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MFAState' -Data @($MFAState) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($MFAState.Count) MFA state records successfully" -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxUsage.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxUsage.ps1 index bd13f0b2da02..1488af0eda4c 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxUsage.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxUsage.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheMailboxUsage { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching mailbox usage' -sev Debug $MailboxUsage = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMailboxUsageDetail(period='D7')?`$format=application%2fjson" -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MailboxUsage' -Data $MailboxUsage - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MailboxUsage' -Data $MailboxUsage -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'MailboxUsage' -Data $MailboxUsage -AddCount $MailboxUsage = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached mailbox usage successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDeviceEncryptionStates.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDeviceEncryptionStates.ps1 index 7dd8764854ff..23f28f1f673e 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDeviceEncryptionStates.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDeviceEncryptionStates.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheManagedDeviceEncryptionStates { $ManagedDeviceEncryptionStates = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/managedDeviceEncryptionStates?$top=999' -tenantid $TenantFilter if ($ManagedDeviceEncryptionStates) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ManagedDeviceEncryptionStates' -Data $ManagedDeviceEncryptionStates - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ManagedDeviceEncryptionStates' -Data $ManagedDeviceEncryptionStates -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ManagedDeviceEncryptionStates' -Data $ManagedDeviceEncryptionStates -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($ManagedDeviceEncryptionStates.Count) managed device encryption states" -sev Debug } $ManagedDeviceEncryptionStates = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDevices.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDevices.ps1 index 5190fd72c24f..5c462e1d91e3 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDevices.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheManagedDevices.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheManagedDevices { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching managed devices' -sev Debug $ManagedDevices = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/managedDevices?$top=999' -tenantid $TenantFilter if (!$ManagedDevices) { $ManagedDevices = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ManagedDevices' -Data $ManagedDevices - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ManagedDevices' -Data $ManagedDevices -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ManagedDevices' -Data $ManagedDevices -AddCount $ManagedDevices = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached managed devices successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOAuth2PermissionGrants.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOAuth2PermissionGrants.ps1 index 6fe33e93d378..404d7c7189ad 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOAuth2PermissionGrants.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOAuth2PermissionGrants.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheOAuth2PermissionGrants { $OAuth2PermissionGrants = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/oauth2PermissionGrants?$top=999' -tenantid $TenantFilter if ($OAuth2PermissionGrants) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OAuth2PermissionGrants' -Data $OAuth2PermissionGrants - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OAuth2PermissionGrants' -Data $OAuth2PermissionGrants -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OAuth2PermissionGrants' -Data $OAuth2PermissionGrants -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($OAuth2PermissionGrants.Count) OAuth2 permission grants" -sev Debug } $OAuth2PermissionGrants = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOfficeActivations.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOfficeActivations.ps1 index 208b4ce0261b..272f1fc317d6 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOfficeActivations.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOfficeActivations.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheOfficeActivations { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Office activations' -sev Debug $Activations = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getOffice365ActivationsUserDetail?`$format=application%2fjson" -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OfficeActivations' -Data $Activations - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OfficeActivations' -Data $Activations -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OfficeActivations' -Data $Activations -AddCount $Activations = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Office activations successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 index 4dedf324a0c2..2000025ebd5d 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 @@ -56,11 +56,9 @@ function Set-CIPPDBCacheOneDriveUsage { }) } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveSiteListing' -Data @($OneDriveListing) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveSiteListing' -Data @($OneDriveListing) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveSiteListing' -Data @($OneDriveListing) -AddCount - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveUsage' -Data @($OneDriveUsage) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveUsage' -Data @($OneDriveUsage) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveUsage' -Data @($OneDriveUsage) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached OneDrive site listing and usage successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOrganization.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOrganization.ps1 index a5e8607ab14f..80b4dea03bf4 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOrganization.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOrganization.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheOrganization { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching organization data' -sev Debug $Organization = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Organization' -Data $Organization - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Organization' -Data $Organization -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Organization' -Data $Organization -AddCount $Organization = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached organization data successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOwaMailboxPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOwaMailboxPolicy.ps1 index 6bb55b05780f..b29ad6bfa0e9 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOwaMailboxPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOwaMailboxPolicy.ps1 @@ -28,8 +28,7 @@ function Set-CIPPDBCacheOwaMailboxPolicy { $OwaMailboxPolicies = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-OwaMailboxPolicy' if ($OwaMailboxPolicies) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OwaMailboxPolicy' -Data $OwaMailboxPolicies - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OwaMailboxPolicy' -Data $OwaMailboxPolicies -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OwaMailboxPolicy' -Data $OwaMailboxPolicies -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($OwaMailboxPolicies.Count) OWA Mailbox Policies" -sev Debug } $OwaMailboxPolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 index d5da952cec5c..01057595b165 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 @@ -30,8 +30,7 @@ function Set-CIPPDBCachePIMSettings { $PIMRoleSettings = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/roleManagementPolicyAssignments?$top=999' -tenantid $TenantFilter if ($PIMRoleSettings) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'PIMRoleSettings' -Data $PIMRoleSettings - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'PIMRoleSettings' -Data $PIMRoleSettings -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'PIMRoleSettings' -Data $PIMRoleSettings -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($PIMRoleSettings.Count) PIM role settings" -sev Debug } $PIMRoleSettings = $null @@ -43,8 +42,7 @@ function Set-CIPPDBCachePIMSettings { $PIMAssignments = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/roleManagement/directory/roleEligibilityScheduleInstances?$top=999' -tenantid $TenantFilter if ($PIMAssignments) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'PIMAssignments' -Data $PIMAssignments - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'PIMAssignments' -Data $PIMAssignments -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'PIMAssignments' -Data $PIMAssignments -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($PIMAssignments.Count) PIM assignments" -sev Debug } $PIMAssignments = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheReportSubmissionPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheReportSubmissionPolicy.ps1 index b3de00a9591b..fb0e24beae0c 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheReportSubmissionPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheReportSubmissionPolicy.ps1 @@ -28,8 +28,7 @@ function Set-CIPPDBCacheReportSubmissionPolicy { if ($ReportSubmissionPolicies) { $Data = @($ReportSubmissionPolicies) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ReportSubmissionPolicy' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ReportSubmissionPolicy' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ReportSubmissionPolicy' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Data.Count) Report Submission Policies" -sev Debug } $ReportSubmissionPolicies = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskDetections.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskDetections.ps1 index f7878e2f9edb..6abdfd46ff95 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskDetections.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskDetections.ps1 @@ -23,8 +23,7 @@ function Set-CIPPDBCacheRiskDetections { $RiskDetections = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identityProtection/riskDetections' -tenantid $TenantFilter if ($RiskDetections) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskDetections' -Data $RiskDetections - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskDetections' -Data $RiskDetections -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskDetections' -Data $RiskDetections -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($RiskDetections.Count) risk detections successfully" -sev Debug } else { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No risk detections found or Identity Protection not available' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyServicePrincipals.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyServicePrincipals.ps1 index 4efcfce693a1..921208f6fff3 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyServicePrincipals.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyServicePrincipals.ps1 @@ -23,8 +23,7 @@ function Set-CIPPDBCacheRiskyServicePrincipals { $RiskyServicePrincipals = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identityProtection/riskyServicePrincipals' -tenantid $TenantFilter if ($RiskyServicePrincipals) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskyServicePrincipals' -Data $RiskyServicePrincipals - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskyServicePrincipals' -Data $RiskyServicePrincipals -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskyServicePrincipals' -Data $RiskyServicePrincipals -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($RiskyServicePrincipals.Count) risky service principals successfully" -sev Debug } else { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No risky service principals found or Workload Identity Protection not available' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyUsers.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyUsers.ps1 index 6e29032f0b03..7e47e3b66ba5 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyUsers.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRiskyUsers.ps1 @@ -23,8 +23,7 @@ function Set-CIPPDBCacheRiskyUsers { $RiskyUsers = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identityProtection/riskyUsers' -tenantid $TenantFilter if ($RiskyUsers) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskyUsers' -Data $RiskyUsers - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskyUsers' -Data $RiskyUsers -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RiskyUsers' -Data $RiskyUsers -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($RiskyUsers.Count) risky users successfully" -sev Debug } else { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No risky users found or Identity Protection not available' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleAssignmentScheduleInstances.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleAssignmentScheduleInstances.ps1 index aa2b914bf794..04b802332c70 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleAssignmentScheduleInstances.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleAssignmentScheduleInstances.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheRoleAssignmentScheduleInstances { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching role assignment schedule instances' -sev Debug $RoleAssignmentScheduleInstances = New-GraphGetRequest -Uri 'https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleInstances' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleAssignmentScheduleInstances' -Data @($RoleAssignmentScheduleInstances) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleAssignmentScheduleInstances' -Data @($RoleAssignmentScheduleInstances) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleAssignmentScheduleInstances' -Data @($RoleAssignmentScheduleInstances) -AddCount $RoleAssignmentScheduleInstances = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached role assignment schedule instances successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleEligibilitySchedules.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleEligibilitySchedules.ps1 index 1bcffb691b27..3a3b4208ced5 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleEligibilitySchedules.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleEligibilitySchedules.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheRoleEligibilitySchedules { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching role eligibility schedules' -sev Debug $RoleEligibilitySchedules = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/roleManagement/directory/roleEligibilitySchedules' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleEligibilitySchedules' -Data @($RoleEligibilitySchedules) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleEligibilitySchedules' -Data @($RoleEligibilitySchedules) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleEligibilitySchedules' -Data @($RoleEligibilitySchedules) -AddCount $RoleEligibilitySchedules = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached role eligibility schedules successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleManagementPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleManagementPolicies.ps1 index 23c7b5fe5ff8..6d88d55f0259 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleManagementPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoleManagementPolicies.ps1 @@ -19,8 +19,7 @@ function Set-CIPPDBCacheRoleManagementPolicies { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching role management policies' -sev Debug $RoleManagementPolicies = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/roleManagementPolicies' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleManagementPolicies' -Data @($RoleManagementPolicies) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleManagementPolicies' -Data @($RoleManagementPolicies) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'RoleManagementPolicies' -Data @($RoleManagementPolicies) -AddCount $RoleManagementPolicies = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached role management policies successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoles.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoles.ps1 index bcc10c993b20..3668cdaf18f6 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoles.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheRoles.ps1 @@ -49,13 +49,11 @@ function Set-CIPPDBCacheRoles { } } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Roles' -Data $RolesWithMembers - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Roles' -Data $RolesWithMembers -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Roles' -Data $RolesWithMembers -AddCount $Roles = $null $RolesWithMembers = $null } else { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Roles' -Data $Roles - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Roles' -Data $Roles -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Roles' -Data $Roles -AddCount $Roles = $null } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenant.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenant.ps1 index 3181f7399c60..0fcd2cd9f808 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenant.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenant.ps1 @@ -30,8 +30,7 @@ function Set-CIPPDBCacheSPOTenant { if ($SPOTenant) { $SPOTenantArray = @($SPOTenant) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SPOTenant' -Data $SPOTenantArray - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SPOTenant' -Data $SPOTenantArray -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SPOTenant' -Data $SPOTenantArray -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached SharePoint Online tenant configuration' -sev Debug } $SPOTenant = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenantSyncClientRestriction.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenantSyncClientRestriction.ps1 index 1c74c5c3fa3f..65b6b242fa9b 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenantSyncClientRestriction.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSPOTenantSyncClientRestriction.ps1 @@ -39,8 +39,7 @@ function Set-CIPPDBCacheSPOTenantSyncClientRestriction { TenantFilter = $TenantFilter } $Data = @($SyncRestriction) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SPOTenantSyncClientRestriction' -Data $Data - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SPOTenantSyncClientRestriction' -Data $Data -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SPOTenantSyncClientRestriction' -Data $Data -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached SharePoint sync client restriction' -sev Debug } $SPOTenant = $null diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 index b9d89242742a..307bc5e87913 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 @@ -29,8 +29,7 @@ function Set-CIPPDBCacheSensitivityLabels { $Labels = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/security/informationProtection/sensitivityLabels' -tenantid $TenantFilter -AsApp $true if ($Labels) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SensitivityLabels' -Data $Labels - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SensitivityLabels' -Data $Labels -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SensitivityLabels' -Data $Labels -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Labels.Count) sensitivity labels" -sev Debug } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipalRiskDetections.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipalRiskDetections.ps1 index bcd4cfba838e..892e19511701 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipalRiskDetections.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipalRiskDetections.ps1 @@ -23,8 +23,7 @@ function Set-CIPPDBCacheServicePrincipalRiskDetections { $ServicePrincipalRiskDetections = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identityProtection/servicePrincipalRiskDetections' -tenantid $TenantFilter if ($ServicePrincipalRiskDetections) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ServicePrincipalRiskDetections' -Data $ServicePrincipalRiskDetections - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ServicePrincipalRiskDetections' -Data $ServicePrincipalRiskDetections -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ServicePrincipalRiskDetections' -Data $ServicePrincipalRiskDetections -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($ServicePrincipalRiskDetections.Count) service principal risk detections successfully" -sev Debug } else { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No service principal risk detections found or Workload Identity Protection not available' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipals.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipals.ps1 index af887bf31342..166b6cac7fd6 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipals.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheServicePrincipals.ps1 @@ -20,8 +20,7 @@ function Set-CIPPDBCacheServicePrincipals { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching service principals' -sev Debug $ServicePrincipals = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $TenantFilter - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ServicePrincipals' -Data $ServicePrincipals - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ServicePrincipals' -Data $ServicePrincipals -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ServicePrincipals' -Data $ServicePrincipals -AddCount $ServicePrincipals = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached service principals successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSettings.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSettings.ps1 index d1951058c3d2..ce2c02bfa7de 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSettings.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSettings.ps1 @@ -21,8 +21,7 @@ function Set-CIPPDBCacheSettings { $Settings = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/settings?$top=999' -tenantid $TenantFilter if (!$Settings) { $Settings = @() } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Settings' -Data $Settings - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Settings' -Data $Settings -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Settings' -Data $Settings -AddCount $Settings = $null Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached directory settings successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 index 5722f962b7a0..561a01721236 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 @@ -90,11 +90,9 @@ function Set-CIPPDBCacheSharePointSiteUsage { $Site.AutoMapUrl = "tenantId=$($TenantId)&webId={$($Site.sharepointIds.webId)}&siteid={$($Site.sharepointIds.siteId)}&webUrl=$($Site.webUrl)&listId={$($ListId)}" } - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SharePointSiteListing' -Data @($SiteListing) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SharePointSiteListing' -Data @($SiteListing) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SharePointSiteListing' -Data @($SiteListing) -AddCount - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SharePointSiteUsage' -Data @($UsageRows) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SharePointSiteUsage' -Data @($UsageRows) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SharePointSiteUsage' -Data @($UsageRows) -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached SharePoint site listing and usage successfully' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeams.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeams.ps1 index ac91707e2cbc..3e0a1e5d69ac 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeams.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeams.ps1 @@ -12,8 +12,7 @@ function Set-CIPPDBCacheTeams { $Teams = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$select=id,displayName,description,visibility,mailNickname" -tenantid $TenantFilter | Sort-Object -Property displayName - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Teams' -Data @($Teams) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Teams' -Data @($Teams) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Teams' -Data @($Teams) -AddCount } catch { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache Teams list: $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_) } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsActivity.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsActivity.ps1 index a8482de6c30f..1ecb5d42b00b 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsActivity.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsActivity.ps1 @@ -18,8 +18,7 @@ function Set-CIPPDBCacheTeamsActivity { @{ Name = 'MeetingCount'; Expression = { $_.'Meeting Count' } } $DbType = "TeamsActivity$Type" - Add-CIPPDbItem -TenantFilter $TenantFilter -Type $DbType -Data @($TeamsActivity) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type $DbType -Data @($TeamsActivity) -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type $DbType -Data @($TeamsActivity) -AddCount } catch { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache Teams activity: $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_) } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsVoice.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsVoice.ps1 index edc41533d438..37b9be41b2a8 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsVoice.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheTeamsVoice.ps1 @@ -37,8 +37,7 @@ function Set-CIPPDBCacheTeamsVoice { } while ($Data.Count -eq 999) $PhoneNumbers = @($AllNumbers | Where-Object { $_.TelephoneNumber }) - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'TeamsVoice' -Data $PhoneNumbers - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'TeamsVoice' -Data $PhoneNumbers -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'TeamsVoice' -Data $PhoneNumbers -AddCount } catch { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache Teams Voice phone numbers: $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_) } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUserRegistrationDetails.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUserRegistrationDetails.ps1 index 64e596f669c9..121f6d62a17b 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUserRegistrationDetails.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUserRegistrationDetails.ps1 @@ -22,8 +22,7 @@ function Set-CIPPDBCacheUserRegistrationDetails { $UserRegistrationDetails = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails' -tenantid $TenantFilter if ($UserRegistrationDetails) { - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'UserRegistrationDetails' -Data $UserRegistrationDetails - Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'UserRegistrationDetails' -Data $UserRegistrationDetails -Count + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'UserRegistrationDetails' -Data $UserRegistrationDetails -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($UserRegistrationDetails.Count) user registration details" -sev Debug } $UserRegistrationDetails = $null From e3e82cd95b9597706f1edc3ccde5776695abe820 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 19 May 2026 07:51:16 -0400 Subject: [PATCH 13/90] Cache Security Defaults --- Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 37 +++++++++++++------ ...t-CIPPDBCacheConditionalAccessPolicies.ps1 | 10 +++++ ...-CIPPStandardConditionalAccessTemplate.ps1 | 24 ++++++------ 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index bed989dc48dc..478bd5e79bf9 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -12,7 +12,8 @@ function New-CIPPCAPolicy { $APIName = 'Create CA Policy', $Headers, $PreloadedCAPolicies = $null, - $PreloadedLocations = $null + $PreloadedLocations = $null, + $PreloadedSecurityDefaults = $null ) # Helper function to replace group display names with GUIDs @@ -490,16 +491,30 @@ function New-CIPPCAPolicy { } } if ($DisableSD -eq $true) { - #Send request to disable security defaults. - $body = '{ "isEnabled": false }' - try { - $null = New-GraphPostRequest -tenantid $TenantFilter -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -Type patch -Body $body -asApp $true - Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Disabled Security Defaults for tenant $($TenantFilter)" -Sev 'Info' - Start-Sleep 3 - } catch { - $ErrorMessage = Get-CippException -Exception $_ - Write-Information "Error disabling security defaults: $($ErrorMessage | ConvertTo-Json -Depth 10 -Compress)" - Write-Information "Failed to disable security defaults for tenant $($TenantFilter): $($ErrorMessage.NormalizedError)" + # Check if Security Defaults is already disabled using preloaded or live data + $SDPolicy = $PreloadedSecurityDefaults + if ($null -eq $SDPolicy) { + try { + $SDPolicy = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -tenantid $TenantFilter -AsApp $true + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Error fetching Security Defaults status: $($ErrorMessage | ConvertTo-Json -Depth 10 -Compress)" + } + } + + if ($SDPolicy.isEnabled -eq $false) { + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message 'Security Defaults already disabled, skipping.' -Sev 'Info' + } else { + $body = '{ "isEnabled": false }' + try { + $null = New-GraphPostRequest -tenantid $TenantFilter -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -Type patch -Body $body -asApp $true + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message "Disabled Security Defaults for tenant $($TenantFilter)" -Sev 'Info' + Start-Sleep 3 + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Error disabling security defaults: $($ErrorMessage | ConvertTo-Json -Depth 10 -Compress)" + Write-Information "Failed to disable security defaults for tenant $($TenantFilter): $($ErrorMessage.NormalizedError)" + } } } $RawJSON = ConvertTo-Json -InputObject $JSONobj -Depth 10 -Compress diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 index a4407e0b82d3..7e11accd8824 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 @@ -61,6 +61,16 @@ function Set-CIPPDBCacheConditionalAccessPolicies { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache authentication strengths: $($_.Exception.Message)" -sev Warning } + try { + $SecurityDefaults = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -tenantid $TenantFilter -AsApp $true + if ($SecurityDefaults) { + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'SecurityDefaults' -Data @($SecurityDefaults) + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached Security Defaults policy (isEnabled=$($SecurityDefaults.isEnabled))" -sev Debug + } + } catch { + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache Security Defaults: $($_.Exception.Message)" -sev Warning + } + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached CA data successfully' -sev Debug } catch { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 index 4d5c8447da02..549396edce9b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 @@ -60,6 +60,7 @@ function Invoke-CIPPStandardConditionalAccessTemplate { #Get from DB, as we just downloaded the latest before the standard runs. $AllCAPolicies = New-CIPPDbRequest -TenantFilter $tenant -Type 'ConditionalAccessPolicies' $PreloadedLocations = New-CIPPDbRequest -TenantFilter $tenant -Type 'NamedLocations' + $PreloadedSecurityDefaults = New-CIPPDbRequest -TenantFilter $tenant -Type 'SecurityDefaults' } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the ConditionalAccessTemplate state for $Tenant. Error: $ErrorMessage" -Sev Error @@ -80,17 +81,18 @@ function Invoke-CIPPStandardConditionalAccessTemplate { } } $NewCAPolicy = @{ - replacePattern = 'displayName' - TenantFilter = $Tenant - state = $Settings.state - RawJSON = $JSONObj - Overwrite = $true - APIName = 'Standards' - Headers = $Request.Headers - DisableSD = $Settings.DisableSD - CreateGroups = $Settings.CreateGroups ?? $false - PreloadedCAPolicies = $AllCAPolicies - PreloadedLocations = $PreloadedLocations + replacePattern = 'displayName' + TenantFilter = $Tenant + state = $Settings.state + RawJSON = $JSONObj + Overwrite = $true + APIName = 'Standards' + Headers = $Request.Headers + DisableSD = $Settings.DisableSD + CreateGroups = $Settings.CreateGroups ?? $false + PreloadedCAPolicies = $AllCAPolicies + PreloadedLocations = $PreloadedLocations + PreloadedSecurityDefaults = $PreloadedSecurityDefaults } $null = New-CIPPCAPolicy @NewCAPolicy From 9ba48711c614be8e2452ffff2e539b5b1a11b534 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 19 May 2026 10:01:24 -0400 Subject: [PATCH 14/90] correct incorrect default value --- Config/standards.json | 2 +- .../Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/standards.json b/Config/standards.json index 6230b383dab5..ce9752a56d19 100644 --- a/Config/standards.json +++ b/Config/standards.json @@ -3746,7 +3746,7 @@ "type": "number", "name": "standards.IntuneComplianceSettings.deviceComplianceCheckinThresholdDays", "label": "Compliance status validity period (days)", - "defaultValue": 130, + "defaultValue": 120, "validators": { "min": { "value": 1, "message": "Minimum value is 1" }, "max": { "value": 120, "message": "Maximum value is 120" } diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 index 8baa105848d6..ebd612ffac70 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 @@ -17,7 +17,7 @@ function Invoke-CIPPStandardIntuneComplianceSettings { Configures how the system treats devices that don't have specific compliance policies and sets how often devices must check in to maintain their compliance status. This ensures proper security oversight of all corporate devices and maintains current compliance information. ADDEDCOMPONENT {"type":"autoComplete","required":true,"multiple":false,"creatable":false,"name":"standards.IntuneComplianceSettings.secureByDefault","label":"Mark devices with no compliance policy as","options":[{"label":"Compliant","value":"false"},{"label":"Non-Compliant","value":"true"}]} - {"type":"number","name":"standards.IntuneComplianceSettings.deviceComplianceCheckinThresholdDays","label":"Compliance status validity period (days)","defaultValue":130,"validators":{"min":{"value":1,"message":"Minimum value is 1"},"max":{"value":120,"message":"Maximum value is 120"}}} + {"type":"number","name":"standards.IntuneComplianceSettings.deviceComplianceCheckinThresholdDays","label":"Compliance status validity period (days)","defaultValue":120,"validators":{"min":{"value":1,"message":"Minimum value is 1"},"max":{"value":120,"message":"Maximum value is 120"}}} IMPACT Low Impact ADDEDDATE From 73f83719d8d6123db9e450248f55dc97acfdc0ea Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 19 May 2026 10:01:41 -0400 Subject: [PATCH 15/90] add logging to geoip lookip --- Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 b/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 index 754454529e3d..b6eaabf73f71 100644 --- a/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPGeoIPLocation.ps1 @@ -12,7 +12,10 @@ function Get-CIPPGeoIPLocation { return ($GeoIP.Data | ConvertFrom-Json) } $location = Invoke-CIPPRestMethod -Uri "https://geoipdb.azurewebsites.net/api/GetIPInfo?IP=$IP" - if ($location.status -eq 'FAIL') { throw "Could not get location for $IP" } + if ($location.status -eq 'FAIL') { + Write-logMessage -API GeoIPLocation -message "Failed to get location for $IP. API returned status 'FAIL' with message: $($location.message)" -sev Warning + throw "Could not get location for $IP" + } $CacheGeo = @{ PartitionKey = 'IP' RowKey = $IP From 9fce7e77b7d7cf29692fa3aa869a373e076f7658 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Wed, 20 May 2026 22:10:20 -0400 Subject: [PATCH 16/90] feat: add in missing options for Windows Hello standard --- ...ntWindowsHelloForBusinessConfiguration.ps1 | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 index b3328394d715..806ef7276061 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 @@ -28,6 +28,8 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration { {"type":"switch","name":"standards.EnrollmentWindowsHelloForBusinessConfiguration.unlockWithBiometricsEnabled","label":"Allow biometric authentication","default":true} {"type":"autoComplete","name":"standards.EnrollmentWindowsHelloForBusinessConfiguration.enhancedBiometricsState","label":"Use enhanced anti-spoofing when available","multiple":false,"options":[{"label":"Not configured","value":"notConfigured"},{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} {"type":"switch","name":"standards.EnrollmentWindowsHelloForBusinessConfiguration.remotePassportEnabled","label":"Allow phone sign-in","default":true} + {"type":"autoComplete","name":"standards.EnrollmentWindowsHelloForBusinessConfiguration.enhancedSignInSecurity","label":"Enable enhanced sign-in security","multiple":false,"options":[{"label":"Not configured","value":"0"},{"label":"Enabled on capable hardware","value":"1"},{"label":"Disabled on all systems","value":"2"}]} + {"type":"autoComplete","name":"standards.EnrollmentWindowsHelloForBusinessConfiguration.securityKeyForSignIn","label":"Use security keys for sign-in","multiple":false,"options":[{"label":"Not configured","value":"notConfigured"},{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]} IMPACT Low Impact ADDEDDATE @@ -56,13 +58,15 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration { try { $CurrentState = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations?`$expand=assignments&orderBy=priority&`$filter=deviceEnrollmentConfigurationType eq 'WindowsHelloForBusiness'" -tenantID $Tenant -AsApp $true | - Select-Object -Property id, pinMinimumLength, pinMaximumLength, pinUppercaseCharactersUsage, pinLowercaseCharactersUsage, pinSpecialCharactersUsage, state, securityDeviceRequired, unlockWithBiometricsEnabled, remotePassportEnabled, pinPreviousBlockCount, pinExpirationInDays, enhancedBiometricsState + Select-Object -Property id, pinMinimumLength, pinMaximumLength, pinUppercaseCharactersUsage, pinLowercaseCharactersUsage, pinSpecialCharactersUsage, state, securityDeviceRequired, unlockWithBiometricsEnabled, remotePassportEnabled, pinPreviousBlockCount, pinExpirationInDays, enhancedBiometricsState, enhancedSignInSecurity, securityKeyForSignIn } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the EnrollmentWindowsHelloForBusinessConfiguration state for $Tenant. Error: $ErrorMessage" -Sev Error + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the EnrollmentWindowsHelloForBusinessConfiguration state for $Tenant. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage return } + $EnhancedSignInSecurity = if ($null -ne $Settings.enhancedSignInSecurity) { [int]$Settings.enhancedSignInSecurity.value } else { $null } + $StateIsCorrect = ($CurrentState.pinMinimumLength -eq $Settings.pinMinimumLength) -and ($CurrentState.pinMaximumLength -eq $Settings.pinMaximumLength) -and ($CurrentState.pinUppercaseCharactersUsage -eq $Settings.pinUppercaseCharactersUsage.value) -and @@ -74,7 +78,10 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration { ($CurrentState.remotePassportEnabled -eq $Settings.remotePassportEnabled) -and ($CurrentState.pinPreviousBlockCount -eq $Settings.pinPreviousBlockCount) -and ($CurrentState.pinExpirationInDays -eq $Settings.pinExpirationInDays) -and - ($CurrentState.enhancedBiometricsState -eq $Settings.enhancedBiometricsState.value) + ($CurrentState.enhancedBiometricsState -eq $Settings.enhancedBiometricsState.value) -and + (($null -eq $Settings.enhancedSignInSecurity) -or ($CurrentState.enhancedSignInSecurity -eq $EnhancedSignInSecurity)) -and + (($null -eq $Settings.securityKeyForSignIn) -or ($CurrentState.securityKeyForSignIn -eq $Settings.securityKeyForSignIn.value)) + # Backwards compatibility for when newer settings were not yet added $CompareField = [PSCustomObject]@{ pinMinimumLength = $CurrentState.pinMinimumLength @@ -91,6 +98,14 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration { enhancedBiometricsState = $CurrentState.enhancedBiometricsState } + if ($null -ne $Settings.enhancedSignInSecurity) { + $CompareField | Add-Member -NotePropertyName enhancedSignInSecurity -NotePropertyValue $CurrentState.enhancedSignInSecurity + } + + if ($null -ne $Settings.securityKeyForSignIn) { + $CompareField | Add-Member -NotePropertyName securityKeyForSignIn -NotePropertyValue $CurrentState.securityKeyForSignIn + } + $ExpectedValue = [PSCustomObject]@{ pinMinimumLength = $Settings.pinMinimumLength pinMaximumLength = $Settings.pinMaximumLength @@ -106,6 +121,14 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration { enhancedBiometricsState = $Settings.enhancedBiometricsState.value } + if ($null -ne $Settings.enhancedSignInSecurity) { + $ExpectedValue | Add-Member -NotePropertyName enhancedSignInSecurity -NotePropertyValue $EnhancedSignInSecurity + } + + if ($null -ne $Settings.securityKeyForSignIn) { + $ExpectedValue | Add-Member -NotePropertyName securityKeyForSignIn -NotePropertyValue $Settings.securityKeyForSignIn.value + } + if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'EnrollmentWindowsHelloForBusinessConfiguration is already applied correctly.' -Sev Info @@ -130,6 +153,8 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration { pinPreviousBlockCount = $Settings.pinPreviousBlockCount pinExpirationInDays = $Settings.pinExpirationInDays enhancedBiometricsState = $Settings.enhancedBiometricsState.value + enhancedSignInSecurity = ($EnhancedSignInSecurity ?? $CurrentState.enhancedSignInSecurity) + securityKeyForSignIn = ($Settings.securityKeyForSignIn.value ?? $CurrentState.securityKeyForSignIn) } | ConvertTo-Json -Compress -Depth 10 } try { From 1e02bfcb198406e9caa408acad12d79c7646ce6f Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Wed, 20 May 2026 23:42:04 -0400 Subject: [PATCH 17/90] feat(standards): add DLP via DCS standard --- .../Invoke-CIPPStandardDlpViaDcsEnabled.ps1 | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDlpViaDcsEnabled.ps1 diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDlpViaDcsEnabled.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDlpViaDcsEnabled.ps1 new file mode 100644 index 000000000000..4419a28bf8a0 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDlpViaDcsEnabled.ps1 @@ -0,0 +1,91 @@ +function Invoke-CIPPStandardDlpViaDcsEnabled { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) DlpViaDcsEnabled + .SYNOPSIS + (Label) Set OWA DLP evaluation via DCS + .DESCRIPTION + (Helptext) Sets whether Outlook on the web uses Data Classification Services for DLP evaluation. See [Microsoft's policy tip reference](https://learn.microsoft.com/en-us/purview/dlp-ol365-win32-policy-tips#sensitive-information-types-that-support-policy-tips-for-outlook-perpetual-users). + (DocsDescription) Configures whether Outlook on the web uses Data Classification Services (DCS)-based Data Loss Prevention (DLP) policy evaluation instead of Exchange-based evaluation. Review DLP policies before enabling this setting, as some legacy Exchange-based predicates are not supported with DCS-based evaluation. See [Microsoft's policy tip reference](https://learn.microsoft.com/en-us/purview/dlp-ol365-win32-policy-tips#sensitive-information-types-that-support-policy-tips-for-outlook-perpetual-users). + .NOTES + CAT + Exchange Standards + TAG + EXECUTIVETEXT + Improves how Outlook on the web applies Data Loss Prevention policies, giving users clearer guidance when sensitive information may be shared and helping reduce accidental data exposure. + ADDEDCOMPONENT + {"type":"autoComplete","multiple":false,"creatable":false,"label":"Select value","name":"standards.DlpViaDcsEnabled.state","options":[{"label":"Enabled","value":"true"},{"label":"Disabled","value":"false"}]} + IMPACT + Medium Impact + ADDEDDATE + 2026-05-20 + POWERSHELLEQUIVALENT + Set-OrganizationConfig -DlpViaDcsEnabled + RECOMMENDEDBY + REQUIREDCAPABILITIES + "EXCHANGE_S_STANDARD" + "EXCHANGE_S_ENTERPRISE" + "EXCHANGE_S_STANDARD_GOV" + "EXCHANGE_S_ENTERPRISE_GOV" + "EXCHANGE_LITE" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param($Tenant, $Settings) + $TestResult = Test-CIPPStandardLicense -StandardName 'DlpViaDcsEnabled' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access + + if ($TestResult -eq $false) { + return $true + } #we're done. + + $state = $Settings.state.value ?? $Settings.state + if ([string]::IsNullOrWhiteSpace($state) -or $state -eq 'Select a value') { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'DLP via Data Classification Service state not selected, skipping.' -sev Error + return + } + $WantedState = [System.Convert]::ToBoolean($state) + + try { + $CurrentInfo = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').DlpViaDcsEnabled + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the DLP via Data Classification Service state for $Tenant. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage + return + } + + if ($Settings.remediate -eq $true) { + if ($CurrentInfo -ne $WantedState) { + try { + $null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OrganizationConfig' -cmdParams @{ DlpViaDcsEnabled = $WantedState } + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully set DLP via Data Classification Service to $state." -sev Info + $CurrentInfo = $WantedState + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set DLP via Data Classification Service to $state. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DLP via Data Classification Service is already set to $state." -sev Info + } + } + + if ($Settings.alert -eq $true) { + if ($CurrentInfo -eq $WantedState) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DLP via Data Classification Service is set to $state." -sev Info + } else { + Write-StandardsAlert -message "DLP via Data Classification Service is not set to $state" -object $CurrentInfo -tenant $Tenant -standardName 'DlpViaDcsEnabled' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DLP via Data Classification Service is not set to $state." -sev Info + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'DlpViaDcsEnabled' -FieldValue $CurrentInfo -StoreAs bool -Tenant $Tenant + $CurrentValue = @{ DlpViaDcsEnabled = $CurrentInfo } + $ExpectedValue = @{ DlpViaDcsEnabled = $WantedState } + Set-CIPPStandardsCompareField -FieldName 'standards.DlpViaDcsEnabled' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -Tenant $Tenant + } +} From cfa144d6b00b6641720c18bde34537227965a7fd Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Fri, 22 May 2026 12:51:10 -0400 Subject: [PATCH 18/90] Update Invoke-ListWorkerHealth.ps1 --- .../CIPP/Settings/Invoke-ListWorkerHealth.ps1 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 index 66862547597b..f1753f41d93b 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 @@ -26,6 +26,24 @@ function Invoke-ListWorkerHealth { $Pool = [Craft.Services.WorkerMetricsBridge]::GetPoolMetrics($PoolType) $Body = @{ Results = $Pool } } + 'History' { + $Minutes = if ($Request.Query.Minutes) { [int]$Request.Query.Minutes } else { 60 } + $MaxPoints = if ($Request.Query.MaxPoints) { [int]$Request.Query.MaxPoints } else { $null } + $History = [Craft.Services.StatsHistoryBridge]::GetHistory($Minutes, $MaxPoints) + $Count = [Craft.Services.StatsHistoryBridge]::GetCount() + $Body = @{ + Results = @{ + TotalPoints = $Count + ReturnedPoints = $History.Count + RangeMinutes = $Minutes + Data = $History + } + } + } + 'Startup' { + $StartupInfo = [Craft.Services.StartupInfoBridge]::GetInfo() + $Body = @{ Results = $StartupInfo } + } 'Jobs' { $RunName = $Request.Query.RunName $Status = $Request.Query.Status From 77a4be6499e8e93d41a932983f03799275071893 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 23 May 2026 18:10:15 +0200 Subject: [PATCH 19/90] fixes #6027 --- .../HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 index a5e20fc7125e..a0d869ade67f 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 @@ -112,7 +112,7 @@ function Invoke-ExecTenantGroup { $Adds.Add('Added member {0}' -f $member.label) } - if ($CurrentMembers -and $members) { + if ($CurrentMembers -and $null -ne $members) { foreach ($CurrentMember in $CurrentMembers) { if ($members.value -notcontains $CurrentMember.customerId) { Remove-AzDataTableEntity @MembersTable -Entity $CurrentMember -Force From 4ab85c751fe6aad0c93d41ce43eb1e7755ae12c4 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 24 May 2026 09:13:56 +1000 Subject: [PATCH 20/90] CIPP Hosted Notices --- .../CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 | 8 ++++++++ .../Entrypoints/HTTP Functions/New-CippCoreRequest.ps1 | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index 9622bf5f0c9e..af0d8bb74bf6 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -209,6 +209,14 @@ function Test-CIPPAccess { 'permissions' = $Permissions } + # Hosted payment status checks — shown to all users (no permission gating) + if ($env:cipp_hosted_subscription_ended) { + $MeResponse['hostedSubscriptionEnded'] = $true + } + if ($env:cipp_hosted_failed_payments) { + $MeResponse['hostedFailedPayments'] = $true + } + # Forced SSO migration: non-dismissible prompt when migration env var is set if ($env:CIPP_SSO_MIGRATION_APPID -and $Permissions -contains 'CIPP.AppSettings.ReadWrite') { $MeResponse['forceSsoMigration'] = @{ diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/New-CippCoreRequest.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/New-CippCoreRequest.ps1 index 87fc325b6c1e..a457080639d2 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/New-CippCoreRequest.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/New-CippCoreRequest.ps1 @@ -58,6 +58,15 @@ function New-CippCoreRequest { }) } + # Block all API calls except /api/me when subscription has ended + if ($env:cipp_hosted_subscription_ended -and $Request.Params.CIPPEndpoint -ne 'me') { + $HttpTotalStopwatch.Stop() + return ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::Forbidden + Body = 'Your CIPP subscription has ended. Access to this instance is no longer available.' + }) + } + if ($Request.Headers.'X-CIPP-Version') { $Table = Get-CippTable -tablename 'Version' $FrontendVer = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'Version' and RowKey eq 'frontend'" From dcf382aaa03539256b17454a49371ac7fda6485e Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 24 May 2026 09:33:25 +1000 Subject: [PATCH 21/90] Update Build-DevApiModules.ps1 --- Tools/Build-DevApiModules.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tools/Build-DevApiModules.ps1 b/Tools/Build-DevApiModules.ps1 index c11524f4724b..5510a3a36e83 100644 --- a/Tools/Build-DevApiModules.ps1 +++ b/Tools/Build-DevApiModules.ps1 @@ -5,9 +5,7 @@ $repoRoot = Split-Path -Parent $toolsRoot $modulesRoot = Join-Path $repoRoot 'Modules' $outputRoot = Join-Path $repoRoot 'Output' -if (-not (Get-Module -ListAvailable -Name ModuleBuilder)) { - Install-Module -Name ModuleBuilder -Scope CurrentUser -Force -} +Install-Module -Name ModuleBuilder -Scope CurrentUser -Force -AllowClobber Import-Module -Name ModuleBuilder -Force Write-Host "Repo root: $repoRoot" From 9bb2f6bc962c1fbb89801b6e50295407672ca829 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 24 May 2026 09:38:26 +1000 Subject: [PATCH 22/90] Update Build-DevApiModules.ps1 --- Tools/Build-DevApiModules.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/Build-DevApiModules.ps1 b/Tools/Build-DevApiModules.ps1 index 5510a3a36e83..04b94fbe195c 100644 --- a/Tools/Build-DevApiModules.ps1 +++ b/Tools/Build-DevApiModules.ps1 @@ -5,7 +5,7 @@ $repoRoot = Split-Path -Parent $toolsRoot $modulesRoot = Join-Path $repoRoot 'Modules' $outputRoot = Join-Path $repoRoot 'Output' -Install-Module -Name ModuleBuilder -Scope CurrentUser -Force -AllowClobber +Install-Module -Name ModuleBuilder -MaximumVersion 3.1.9 -Scope CurrentUser -Force -AllowClobber Import-Module -Name ModuleBuilder -Force Write-Host "Repo root: $repoRoot" From fa5f4de6f54e7efc2473687e705d5b6caf20b1aa Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 24 May 2026 14:46:53 -0400 Subject: [PATCH 23/90] remove sso setup from featureflag --- Config/FeatureFlags.json | 1 - 1 file changed, 1 deletion(-) diff --git a/Config/FeatureFlags.json b/Config/FeatureFlags.json index 35c51a44e865..1216ac071689 100644 --- a/Config/FeatureFlags.json +++ b/Config/FeatureFlags.json @@ -32,7 +32,6 @@ "Endpoints": [ "ExecCIPPUsers", "ListCIPPUsers", - "ExecSSOSetup", "ExecContainerManagement", "ListContainerLogs", "ListWorkerHealth" From df8477792bb4970416689b732c43a95e3537be7b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 24 May 2026 22:41:08 +0200 Subject: [PATCH 24/90] implement standards template deployment for intune apps --- .../Applications/Push-UploadApplication.ps1 | 139 +----------- .../Public/New-CIPPIntuneAppDeployment.ps1 | 204 ++++++++++++++++++ ...ke-CIPPStandardIntuneAppTemplateDeploy.ps1 | 142 ++++++++++++ 3 files changed, 347 insertions(+), 138 deletions(-) create mode 100644 Modules/CIPPCore/Public/New-CIPPIntuneAppDeployment.ps1 create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneAppTemplateDeploy.ps1 diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 index ba5d57d4a1a7..914801bcb29d 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Applications/Push-UploadApplication.ps1 @@ -11,15 +11,11 @@ function Push-UploadApplication { $Filter = "PartitionKey eq 'apps' and RowKey eq '$($Item.Name)'" $AppConfig = (Get-CIPPAzDataTableEntity @Table -filter $Filter).JSON | ConvertFrom-Json - $intuneBody = $AppConfig.IntuneBody $tenants = if ($AppConfig.tenant -eq 'AllTenants') { (Get-Tenants -IncludeErrors).defaultDomainName } else { $AppConfig.tenant } - $assignTo = $AppConfig.assignTo - $AssignToIntent = $AppConfig.InstallationIntent - $ExcludeGroup = $AppConfig.excludeGroup $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter if ($AppConfig.tenant -ne 'AllTenants') { $null = Remove-AzDataTableEntity -Force @Table -Entity $clearRow @@ -33,142 +29,9 @@ function Push-UploadApplication { } } - # Determine app type (default to 'Choco' if not specified) - $AppType = if ($AppConfig.type) { $AppConfig.type } else { 'Choco' } - - # Load files based on app type (only for types that need them) - $Intunexml = $null - $Infile = $null - if ($AppType -eq 'MSPApp') { - [xml]$Intunexml = Get-Content (Join-Path $env:CIPPRootPath "AddMSPApp\$($AppConfig.MSPAppName).app.xml") - $Infile = Join-Path $env:CIPPRootPath "AddMSPApp\$($AppConfig.MSPAppName).intunewin" - } elseif ($AppType -in @('Choco', 'Win32ScriptApp')) { - [xml]$Intunexml = Get-Content (Join-Path $env:CIPPRootPath 'AddChocoApp\Choco.App.xml') - $Infile = Join-Path $env:CIPPRootPath "AddChocoApp\$($Intunexml.ApplicationInfo.FileName)" - } - - - $baseuri = 'https://graph.microsoft.com/beta/deviceAppManagement/mobileApps' foreach ($tenant in $tenants) { try { - # Check if app already exists - $ApplicationList = New-GraphGetRequest -Uri $baseuri -tenantid $tenant | Where-Object { $_.DisplayName -eq $AppConfig.Applicationname -and ($_.'@odata.type' -eq '#microsoft.graph.win32LobApp' -or $_.'@odata.type' -eq '#microsoft.graph.winGetApp') } - if ($ApplicationList.displayname.count -ge 1) { - Write-LogMessage -api 'AppUpload' -tenant $tenant -message "$($AppConfig.Applicationname) exists. Skipping this application" -Sev 'Info' - continue - } - - # Route to appropriate handler based on app type - $NewApp = $null - switch ($AppType) { - 'WinGet' { - $NewApp = Add-CIPPWinGetApp -AppBody $intuneBody -TenantFilter $tenant - } - 'Choco' { - # Prepare encryption info from XML - $EncryptionInfo = @{ - EncryptionKey = $Intunexml.ApplicationInfo.EncryptionInfo.EncryptionKey - MacKey = $Intunexml.ApplicationInfo.EncryptionInfo.MacKey - InitializationVector = $Intunexml.ApplicationInfo.EncryptionInfo.InitializationVector - Mac = $Intunexml.ApplicationInfo.EncryptionInfo.Mac - ProfileIdentifier = $Intunexml.ApplicationInfo.EncryptionInfo.ProfileIdentifier - FileDigest = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigest - FileDigestAlgorithm = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigestAlgorithm - } - - # Build parameters dynamically - $Params = @{ - AppBody = $intuneBody - TenantFilter = $tenant - FilePath = $Infile - FileName = $Intunexml.ApplicationInfo.FileName - UnencryptedSize = [int64]$Intunexml.ApplicationInfo.UnencryptedContentSize - EncryptionInfo = $EncryptionInfo - } - if ($AppConfig.Applicationname) { $Params.DisplayName = $AppConfig.Applicationname } - - $NewApp = Add-CIPPPackagedApplication @Params - } - 'MSPApp' { - # Prepare encryption info from XML - $EncryptionInfo = @{ - EncryptionKey = $Intunexml.ApplicationInfo.EncryptionInfo.EncryptionKey - MacKey = $Intunexml.ApplicationInfo.EncryptionInfo.MacKey - InitializationVector = $Intunexml.ApplicationInfo.EncryptionInfo.InitializationVector - Mac = $Intunexml.ApplicationInfo.EncryptionInfo.Mac - ProfileIdentifier = $Intunexml.ApplicationInfo.EncryptionInfo.ProfileIdentifier - FileDigest = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigest - FileDigestAlgorithm = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigestAlgorithm - } - - # Build parameters dynamically - $Params = @{ - AppBody = $intuneBody - TenantFilter = $tenant - FilePath = $Infile - FileName = $Intunexml.ApplicationInfo.FileName - UnencryptedSize = [int64]$Intunexml.ApplicationInfo.UnencryptedContentSize - EncryptionInfo = $EncryptionInfo - } - if ($AppConfig.Applicationname) { $Params.DisplayName = $AppConfig.Applicationname } - - $NewApp = Add-CIPPPackagedApplication @Params - } - 'Win32ScriptApp' { - # Prepare encryption info from XML - $EncryptionInfo = @{ - EncryptionKey = $Intunexml.ApplicationInfo.EncryptionInfo.EncryptionKey - MacKey = $Intunexml.ApplicationInfo.EncryptionInfo.MacKey - InitializationVector = $Intunexml.ApplicationInfo.EncryptionInfo.InitializationVector - Mac = $Intunexml.ApplicationInfo.EncryptionInfo.Mac - ProfileIdentifier = $Intunexml.ApplicationInfo.EncryptionInfo.ProfileIdentifier - FileDigest = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigest - FileDigestAlgorithm = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigestAlgorithm - } - - # Build properties dynamically - $Properties = @{ - displayName = $AppConfig.Applicationname - installScript = $AppConfig.installScript - } - - # A few of these are probably mandatory - if ($AppConfig.description) { $Properties['description'] = $AppConfig.description } - if ($AppConfig.publisher) { $Properties['publisher'] = $AppConfig.publisher } - if ($AppConfig.uninstallScript) { $Properties['uninstallScript'] = $AppConfig.uninstallScript } - if ($AppConfig.detectionScript) { $Properties['detectionScript'] = $AppConfig.detectionScript } - if ($AppConfig.detectionPath) { $Properties['detectionPath'] = $AppConfig.detectionPath } - if ($AppConfig.detectionFile) { $Properties['detectionFile'] = $AppConfig.detectionFile } - if ($AppConfig.runAsAccount) { $Properties['runAsAccount'] = $AppConfig.runAsAccount } - if ($AppConfig.deviceRestartBehavior) { $Properties['deviceRestartBehavior'] = $AppConfig.deviceRestartBehavior } - if ($null -ne $AppConfig.runAs32Bit) { $Properties['runAs32Bit'] = $AppConfig.runAs32Bit } - if ($null -ne $AppConfig.enforceSignatureCheck) { $Properties['enforceSignatureCheck'] = $AppConfig.enforceSignatureCheck } - - $NewApp = Add-CIPPW32ScriptApplication -TenantFilter $tenant -Properties ([PSCustomObject]$Properties) - } - 'WinGetNew' { - # I think we don't need a separate WinGetNew type, just use WinGet? - } - default { - throw "Unsupported app type: $($AppConfig.type)" - } - } - - # Log success and assign app if requested - if ($NewApp) { - Write-LogMessage -api 'AppUpload' -tenant $tenant -message "$($AppConfig.Applicationname) Successfully created" -Sev 'Info' - - if ($assignTo -and $assignTo -ne 'On') { - $intent = if ($AssignToIntent) { 'Uninstall' } else { 'Required' } - $AppTypeForAssignment = switch ($AppType) { - 'WinGet' { 'WinGet' } - 'WinGetNew' { 'WinGet' } - default { 'Win32Lob' } - } - Start-Sleep -Milliseconds 200 - Set-CIPPAssignedApplication -ApplicationId $NewApp.Id -TenantFilter $tenant -groupName $assignTo -ExcludeGroup $ExcludeGroup -Intent $intent -AppType $AppTypeForAssignment -APIName 'AppUpload' - } - } + $NewApp = New-CIPPIntuneAppDeployment -AppConfig $AppConfig -TenantFilter $tenant -APIName 'AppUpload' } catch { "Failed to add Application for $tenant : $($_.Exception.Message)" Write-LogMessage -api 'AppUpload' -tenant $tenant -message "Failed adding Application $($AppConfig.Applicationname). Error: $($_.Exception.Message)" -LogData (Get-CippException -Exception $_) -Sev 'Error' diff --git a/Modules/CIPPCore/Public/New-CIPPIntuneAppDeployment.ps1 b/Modules/CIPPCore/Public/New-CIPPIntuneAppDeployment.ps1 new file mode 100644 index 000000000000..be7ca21b9787 --- /dev/null +++ b/Modules/CIPPCore/Public/New-CIPPIntuneAppDeployment.ps1 @@ -0,0 +1,204 @@ +function New-CIPPIntuneAppDeployment { + <# + .SYNOPSIS + Deploys a single Intune application to a tenant. + .DESCRIPTION + Shared deployment function used by both Push-UploadApplication (queue processing) + and standards. Handles app existence check, type routing, and assignment. + Accepts either a pre-built AppConfig (with IntuneBody, from queue) or raw template + config (appType + raw fields) and builds the deployment config internally. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [PSCustomObject]$AppConfig, + + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + + [string]$APIName = 'AppUpload' + ) + + $IntuneBody = $AppConfig.IntuneBody + $AssignTo = $AppConfig.assignTo + $AssignToIntent = $AppConfig.InstallationIntent + $ExcludeGroup = $AppConfig.excludeGroup + $AppType = if ($AppConfig.type) { $AppConfig.type } else { 'Choco' } + + # Build IntuneBody from raw config if not pre-built (template/standard path) + if (-not $IntuneBody -and $AppType -eq 'WinGet') { + $PackageId = $AppConfig.packagename ?? $AppConfig.PackageName + $AppDisplayName = $AppConfig.applicationName ?? $AppConfig.ApplicationName + if (-not $PackageId) { + throw "PackageName/packagename is required for WinGet apps but was not found in the config for '$AppDisplayName'." + } + $IntuneBody = [ordered]@{ + '@odata.type' = '#microsoft.graph.winGetApp' + 'displayName' = "$AppDisplayName" + 'description' = "$($AppConfig.description)" + 'packageIdentifier' = "$PackageId" + 'installExperience' = @{ + '@odata.type' = 'microsoft.graph.winGetAppInstallExperience' + 'runAsAccount' = 'system' + } + } + } + + if (-not $IntuneBody -and $AppType -eq 'Choco') { + $IntuneBody = Get-Content (Join-Path $env:CIPPRootPath 'AddChocoApp\Choco.app.json') | ConvertFrom-Json + $IntuneBody.description = $AppConfig.description + $IntuneBody.displayName = $AppConfig.ApplicationName + $IntuneBody.installExperience.runAsAccount = if ($AppConfig.InstallAsSystem) { 'system' } else { 'user' } + $IntuneBody.installExperience.deviceRestartBehavior = if ($AppConfig.DisableRestart) { 'suppress' } else { 'allow' } + $IntuneBody.installCommandLine = "powershell.exe -ExecutionPolicy Bypass .\Install.ps1 -InstallChoco -Packagename $($AppConfig.PackageName)" + if ($AppConfig.customrepo) { + $IntuneBody.installCommandLine = $IntuneBody.installCommandLine + " -CustomRepo $($AppConfig.CustomRepo)" + } + if ($AppConfig.customArguments) { + $IntuneBody.installCommandLine = $IntuneBody.installCommandLine + " -CustomArguments '$($AppConfig.customArguments)'" + } + $IntuneBody.UninstallCommandLine = "powershell.exe -ExecutionPolicy Bypass .\Uninstall.ps1 -Packagename $($AppConfig.PackageName)" + $IntuneBody.detectionRules[0].path = "$($ENV:SystemDrive)\programdata\chocolatey\lib" + $IntuneBody.detectionRules[0].fileOrFolderName = "$($AppConfig.PackageName)" + + if ($IntuneBody.installCommandLine -match '%') { + $IntuneBody.installCommandLine = Get-CIPPTextReplacement -TenantFilter $TenantFilter -Text $IntuneBody.installCommandLine + } + } + + # Load files based on app type (only for types that need them) + $Intunexml = $null + $Infile = $null + if ($AppType -eq 'MSPApp') { + [xml]$Intunexml = Get-Content (Join-Path $env:CIPPRootPath "AddMSPApp\$($AppConfig.MSPAppName).app.xml") + $Infile = Join-Path $env:CIPPRootPath "AddMSPApp\$($AppConfig.MSPAppName).intunewin" + } elseif ($AppType -in @('Choco', 'Win32ScriptApp')) { + [xml]$Intunexml = Get-Content (Join-Path $env:CIPPRootPath 'AddChocoApp\Choco.App.xml') + $Infile = Join-Path $env:CIPPRootPath "AddChocoApp\$($Intunexml.ApplicationInfo.FileName)" + } + + $BaseUri = 'https://graph.microsoft.com/beta/deviceAppManagement/mobileApps' + + # Check if app already exists (any type with matching display name) + $ApplicationList = New-GraphGetRequest -Uri $BaseUri -tenantid $TenantFilter | Where-Object { $_.DisplayName -eq $AppConfig.Applicationname } + if ($ApplicationList.displayname.count -ge 1) { + Write-LogMessage -API $APIName -tenant $TenantFilter -message "$($AppConfig.Applicationname) exists. Skipping this application" -Sev 'Info' + return $null + } + + # Route to appropriate handler based on app type + $NewApp = $null + switch ($AppType) { + 'WinGet' { + $NewApp = Add-CIPPWinGetApp -AppBody $IntuneBody -TenantFilter $TenantFilter + } + 'Choco' { + $EncryptionInfo = @{ + EncryptionKey = $Intunexml.ApplicationInfo.EncryptionInfo.EncryptionKey + MacKey = $Intunexml.ApplicationInfo.EncryptionInfo.MacKey + InitializationVector = $Intunexml.ApplicationInfo.EncryptionInfo.InitializationVector + Mac = $Intunexml.ApplicationInfo.EncryptionInfo.Mac + ProfileIdentifier = $Intunexml.ApplicationInfo.EncryptionInfo.ProfileIdentifier + FileDigest = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigest + FileDigestAlgorithm = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigestAlgorithm + } + + $Params = @{ + AppBody = $IntuneBody + TenantFilter = $TenantFilter + FilePath = $Infile + FileName = $Intunexml.ApplicationInfo.FileName + UnencryptedSize = [int64]$Intunexml.ApplicationInfo.UnencryptedContentSize + EncryptionInfo = $EncryptionInfo + } + if ($AppConfig.Applicationname) { $Params.DisplayName = $AppConfig.Applicationname } + + $NewApp = Add-CIPPPackagedApplication @Params + } + 'MSPApp' { + $EncryptionInfo = @{ + EncryptionKey = $Intunexml.ApplicationInfo.EncryptionInfo.EncryptionKey + MacKey = $Intunexml.ApplicationInfo.EncryptionInfo.MacKey + InitializationVector = $Intunexml.ApplicationInfo.EncryptionInfo.InitializationVector + Mac = $Intunexml.ApplicationInfo.EncryptionInfo.Mac + ProfileIdentifier = $Intunexml.ApplicationInfo.EncryptionInfo.ProfileIdentifier + FileDigest = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigest + FileDigestAlgorithm = $Intunexml.ApplicationInfo.EncryptionInfo.FileDigestAlgorithm + } + + $Params = @{ + AppBody = $IntuneBody + TenantFilter = $TenantFilter + FilePath = $Infile + FileName = $Intunexml.ApplicationInfo.FileName + UnencryptedSize = [int64]$Intunexml.ApplicationInfo.UnencryptedContentSize + EncryptionInfo = $EncryptionInfo + } + if ($AppConfig.Applicationname) { $Params.DisplayName = $AppConfig.Applicationname } + + $NewApp = Add-CIPPPackagedApplication @Params + } + 'Win32ScriptApp' { + $Properties = @{ + displayName = $AppConfig.Applicationname + installScript = $AppConfig.installScript + } + + if ($AppConfig.description) { $Properties['description'] = $AppConfig.description } + if ($AppConfig.publisher) { $Properties['publisher'] = $AppConfig.publisher } + if ($AppConfig.uninstallScript) { $Properties['uninstallScript'] = $AppConfig.uninstallScript } + if ($AppConfig.detectionScript) { $Properties['detectionScript'] = $AppConfig.detectionScript } + if ($AppConfig.detectionPath) { $Properties['detectionPath'] = $AppConfig.detectionPath } + if ($AppConfig.detectionFile) { $Properties['detectionFile'] = $AppConfig.detectionFile } + if ($AppConfig.runAsAccount) { $Properties['runAsAccount'] = $AppConfig.runAsAccount } + if ($AppConfig.deviceRestartBehavior) { $Properties['deviceRestartBehavior'] = $AppConfig.deviceRestartBehavior } + if ($null -ne $AppConfig.runAs32Bit) { $Properties['runAs32Bit'] = $AppConfig.runAs32Bit } + if ($null -ne $AppConfig.enforceSignatureCheck) { $Properties['enforceSignatureCheck'] = $AppConfig.enforceSignatureCheck } + + $NewApp = Add-CIPPW32ScriptApplication -TenantFilter $TenantFilter -Properties ([PSCustomObject]$Properties) + } + 'OfficeApp' { + # Strip read-only properties that Graph API won't accept on create + $ObjBody = $IntuneBody + if ($ObjBody -is [string]) { $ObjBody = $ObjBody | ConvertFrom-Json -Depth 100 } + $ReadOnlyProps = @('id', 'createdDateTime', 'lastModifiedDateTime', 'uploadState', 'publishingState', 'isAssigned', 'roleScopeTagIds', 'dependentAppCount', 'supersedingAppCount', 'supersededAppCount', 'committedContentVersion', 'fileName', 'size', 'assignments@odata.context', 'assignments', 'AppAssignment', 'AppExclude') + foreach ($prop in $ReadOnlyProps) { + if ($ObjBody.PSObject.Properties[$prop]) { + $ObjBody.PSObject.Properties.Remove($prop) + } + } + $NewApp = New-GraphPostRequest -Uri $BaseUri -tenantid $TenantFilter -Body (ConvertTo-Json -InputObject $ObjBody -Depth 10) -Type POST + } + default { + throw "Unsupported app type: $AppType" + } + } + + # Log success and assign app if requested + if ($NewApp) { + Write-LogMessage -API $APIName -tenant $TenantFilter -message "$($AppConfig.Applicationname) Successfully created" -Sev 'Info' + + if ($AssignTo -and $AssignTo -ne 'On') { + $Intent = if ($AssignToIntent) { 'Uninstall' } else { 'Required' } + $AppTypeForAssignment = switch ($AppType) { + 'WinGet' { 'WinGet' } + 'WinGetNew' { 'WinGet' } + 'OfficeApp' { $null } + default { 'Win32Lob' } + } + Start-Sleep -Milliseconds 200 + $AssignParams = @{ + ApplicationId = $NewApp.Id + TenantFilter = $TenantFilter + GroupName = $AssignTo + ExcludeGroup = $ExcludeGroup + Intent = $Intent + APIName = $APIName + } + if ($AppTypeForAssignment) { $AssignParams.AppType = $AppTypeForAssignment } + Set-CIPPAssignedApplication @AssignParams + } + } + + return $NewApp +} diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneAppTemplateDeploy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneAppTemplateDeploy.ps1 new file mode 100644 index 000000000000..cc1b5013d9e5 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneAppTemplateDeploy.ps1 @@ -0,0 +1,142 @@ +function Invoke-CIPPStandardIntuneAppTemplateDeploy { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) IntuneAppTemplateDeploy + .SYNOPSIS + (Label) Deploy Intune Application Template + .DESCRIPTION + (Helptext) Deploys selected Intune application templates to the tenant. Supports WinGet/Store apps, Office apps, Chocolatey apps, Win32 script apps, and MSP apps. + (DocsDescription) Uses CIPP Intune Application Templates to deploy applications across tenants as a standard. Each template can contain multiple applications of different types which will be queued for deployment. + .NOTES + CAT + Intune Standards + TAG + EXECUTIVETEXT + Automatically deploys approved Intune applications across all managed tenants, ensuring consistent software availability and reducing manual deployment overhead. Supports WinGet, Office, Chocolatey, Win32, and MSP application types. + ADDEDCOMPONENT + {"type":"autoComplete","multiple":true,"creatable":false,"label":"Select Application Templates","name":"standards.IntuneAppTemplateDeploy.templateIds","api":{"url":"/api/ListAppTemplates","labelField":"Displayname","valueField":"GUID","queryKey":"StdIntuneAppTemplateList"}} + IMPACT + Medium Impact + ADDEDDATE + 2026-05-23 + POWERSHELLEQUIVALENT + Graph API - /deviceAppManagement/mobileApps + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param( + $Tenant, + $Settings + ) + + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneAppTemplateDeploy' -TenantFilter $Tenant -Preset Intune + if ($TestResult -eq $false) { return $true } + + $TemplateIds = @($Settings.templateIds.value ?? $Settings.templateIds) + if ($TemplateIds.Count -eq 0) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'IntuneAppTemplateDeploy: No template IDs provided, skipping.' -sev Error + return + } + + # Get current Intune apps via live Graph call (same as Push-UploadApplication) + $BaseUri = 'https://graph.microsoft.com/beta/deviceAppManagement/mobileApps' + $CurrentApps = @(New-GraphGetRequest -Uri $BaseUri -tenantid $Tenant) + + # Load all selected templates and build per-app objects + $Table = Get-CIPPTable -TableName 'templates' + $MissingApps = [System.Collections.Generic.List[PSCustomObject]]::new() + $CurrentAppNames = @($CurrentApps.displayName) + + foreach ($TemplateId in $TemplateIds) { + $Entity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'AppTemplate' and RowKey eq '$TemplateId'" + if (-not $Entity) { continue } + + $TemplateData = $Entity.JSON | ConvertFrom-Json -Depth 100 + $TemplateName = $TemplateData.Displayname + $AppsRaw = $TemplateData.Apps + + # Build individual app objects from the template apps collection + $AppTypes = @($AppsRaw.appType) + $AppNames = @($AppsRaw.appName) + $AppConfigs = @($AppsRaw.config) + + for ($i = 0; $i -lt $AppTypes.Count; $i++) { + $RawConfig = $AppConfigs[$i] + $Config = if ($RawConfig -is [string]) { $RawConfig | ConvertFrom-Json -Depth 100 } else { $RawConfig } + $DisplayName = [string]($Config.ApplicationName ?? $Config.displayName ?? $AppNames[$i]) + + if ($DisplayName -notin $CurrentAppNames) { + $MissingApps.Add([PSCustomObject]@{ + TemplateId = [string]$TemplateId + TemplateName = [string]$TemplateName + AppName = [string]$DisplayName + AppType = [string]$AppTypes[$i] + Config = $Config + }) + } + } + } + + $ExpectedValue = [PSCustomObject]@{ state = 'All template apps deployed' } + $CurrentValue = if ($MissingApps.Count -eq 0) { + [PSCustomObject]@{ state = 'All template apps deployed' } + } else { + [PSCustomObject]@{ MissingApps = ($MissingApps.AppName -join ', ') } + } + + if ($Settings.remediate -eq $true) { + if ($MissingApps.Count -eq 0) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'All Intune application template apps are already deployed.' -sev Info + } else { + foreach ($App in $MissingApps) { + try { + # Map template appType to queue type used by New-CIPPIntuneAppDeployment + $QueueType = switch ($App.AppType) { + 'StoreApp' { 'WinGet' } + 'chocolateyApp' { 'Choco' } + 'win32ScriptApp' { 'Win32ScriptApp' } + 'mspApp' { 'MSPApp' } + 'officeApp' { 'OfficeApp' } + default { $App.AppType } + } + + # Build AppConfig in the same format as the apps queue + # Assignment info comes from the template's per-app config + $DeployConfig = $App.Config | ConvertTo-Json -Depth 100 | ConvertFrom-Json -Depth 100 + $DeployConfig | Add-Member -NotePropertyName 'type' -NotePropertyValue $QueueType -Force + $DeployConfig | Add-Member -NotePropertyName 'Applicationname' -NotePropertyValue $App.AppName -Force + # Compute assignTo the same way the HTTP handlers do + $AppAssignTo = if ($DeployConfig.AssignTo -eq 'customGroup') { $DeployConfig.CustomGroup } else { $DeployConfig.AssignTo } + $DeployConfig | Add-Member -NotePropertyName 'assignTo' -NotePropertyValue $AppAssignTo -Force + + $null = New-CIPPIntuneAppDeployment -AppConfig $DeployConfig -TenantFilter $Tenant -APIName 'Standards' + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Deployed Intune app '$($App.AppName)' ($($App.AppType)) from template '$($App.TemplateName)'." -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to deploy Intune app '$($App.AppName)' from template '$($App.TemplateName)': $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + } + + if ($Settings.alert -eq $true) { + if ($MissingApps.Count -eq 0) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'All Intune application template apps are deployed.' -sev Info + } else { + $MissingList = $MissingApps.AppName -join ', ' + Write-StandardsAlert -message "The following Intune template apps are not deployed: $MissingList" -object (@{ 'Missing Apps' = $MissingList }) -tenant $Tenant -standardName 'IntuneAppTemplateDeploy' -standardId $Settings.standardId + } + } + + if ($Settings.report -eq $true) { + $StateIsCorrect = $MissingApps.Count -eq 0 + Set-CIPPStandardsCompareField -FieldName 'standards.IntuneAppTemplateDeploy' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant + Add-CIPPBPAField -FieldName 'IntuneAppTemplateDeploy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} From 08ab039ccdaa149d3c0698d1dba627acd668ee58 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 25 May 2026 00:22:59 +0200 Subject: [PATCH 25/90] add filtering --- .../Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 | 2 ++ .../CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 | 1 + 2 files changed, 3 insertions(+) diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 index 575930678050..299381674910 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 @@ -21,6 +21,7 @@ function Get-CIPPAlertAppCertificateExpiry { } $AppAlertData = foreach ($App in $appList) { + if ($App.displayName -match 'ConnectSyncProvisioning') { continue } if ($App.keyCredentials) { foreach ($Credential in $App.keyCredentials) { if ($Credential.endDateTime -lt $Now.AddDays(30) -and $Credential.endDateTime -gt $Now.AddDays(-7)) { @@ -42,6 +43,7 @@ function Get-CIPPAlertAppCertificateExpiry { } $SamlAlertData = foreach ($ServicePrincipal in $servicePrincipals) { + if ($ServicePrincipal.displayName -match 'ConnectSyncProvisioning') { continue } $ExpiryDate = $null if ($ServicePrincipal.preferredTokenSigningKeyEndDateTime) { $ExpiryDate = [datetime]$ServicePrincipal.preferredTokenSigningKeyEndDateTime diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 index f8ade377536c..2a53835cc946 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 @@ -21,6 +21,7 @@ function Get-CIPPAlertAppSecretExpiry { $AlertData = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($App in $applist) { + if ($App.displayName -match 'ConnectSyncProvisioning') { continue } Write-Host "checking $($App.displayName)" if ($App.passwordCredentials) { foreach ($Credential in $App.passwordCredentials) { From c81b6a5c57a49d9dfec2728706fb7cc2098d5dfa Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 25 May 2026 00:23:02 +0200 Subject: [PATCH 26/90] add filtering --- .../Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 | 2 +- .../CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 index 299381674910..e31714f632dd 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppCertificateExpiry.ps1 @@ -4,7 +4,7 @@ function Get-CIPPAlertAppCertificateExpiry { Entrypoint #> [CmdletBinding()] - Param ( + param ( [Parameter(Mandatory = $false)] [Alias('input')] $InputValue, diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 index 2a53835cc946..20041ca7845c 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAppSecretExpiry.ps1 @@ -4,7 +4,7 @@ function Get-CIPPAlertAppSecretExpiry { Entrypoint #> [CmdletBinding()] - Param ( + param ( [Parameter(Mandatory = $false)] [Alias('input')] $InputValue, From 33512c39360bd618749adf423d132b764e62db1f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 25 May 2026 01:34:04 +0200 Subject: [PATCH 27/90] FIDO2 profile standards --- ...nvoke-CIPPStandardFIDO2PasskeyProfiles.ps1 | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFIDO2PasskeyProfiles.ps1 diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFIDO2PasskeyProfiles.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFIDO2PasskeyProfiles.ps1 new file mode 100644 index 000000000000..37eb3e0402a3 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFIDO2PasskeyProfiles.ps1 @@ -0,0 +1,159 @@ +function Invoke-CIPPStandardFIDO2PasskeyProfiles { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) FIDO2PasskeyProfiles + .SYNOPSIS + (Label) Configure FIDO2 Passkey Profile + .DESCRIPTION + (Helptext) Configures the default FIDO2 passkey profile including AAGUID allowlists, attestation enforcement, and passkey types for the tenant. + (DocsDescription) Manages the default FIDO2 passkey profile on the tenant authentication methods policy. Controls which authenticators (hardware keys, password managers, Microsoft Authenticator) are permitted via AAGUID allowlists, whether attestation is enforced, and which passkey types (device-bound, synced, or both) are allowed. This enables MSPs to centrally deploy phishing-resistant MFA configurations across tenants. + .NOTES + CAT + Entra (AAD) Standards + TAG + EXECUTIVETEXT + Configures the default passkey (FIDO2) profile that controls which authenticators users can register for phishing-resistant MFA. Supports allowlisting specific hardware keys (e.g., YubiKey models), password managers (e.g., 1Password), and Microsoft Authenticator by AAGUID, with control over attestation enforcement and passkey types. + ADDEDCOMPONENT + [{"type":"select","multiple":false,"name":"standards.FIDO2PasskeyProfiles.PasskeyTypes","label":"Allowed Passkey Types","options":[{"label":"Device-bound only","value":"deviceBound"},{"label":"Synced only","value":"synced"},{"label":"Both device-bound and synced","value":"deviceBound,synced"}],"required":true},{"type":"select","multiple":false,"name":"standards.FIDO2PasskeyProfiles.AttestationEnforcement","label":"Attestation Enforcement","options":[{"label":"Disabled (required for synced passkeys)","value":"disabled"},{"label":"Registration only","value":"registrationOnly"}],"required":true},{"type":"switch","name":"standards.FIDO2PasskeyProfiles.EnforceKeyRestrictions","label":"Enforce AAGUID Key Restrictions"},{"type":"select","multiple":false,"name":"standards.FIDO2PasskeyProfiles.EnforcementType","label":"Key Restriction Type","options":[{"label":"Allow listed AAGUIDs only","value":"allow"},{"label":"Block listed AAGUIDs","value":"block"}]},{"type":"textField","name":"standards.FIDO2PasskeyProfiles.AAGUIDs","label":"AAGUIDs (comma-separated list of authenticator AAGUIDs)"}] + IMPACT + Medium Impact + ADDEDDATE + 2026-05-25 + POWERSHELLEQUIVALENT + Graph API PATCH /policies/authenticationMethodsPolicy/authenticationMethodConfigurations/fido2 + RECOMMENDEDBY + "CIPP" + DISABLEDFEATURES + {"report":false,"warn":false,"remediate":false} + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param($Tenant, $Settings) + + $PasskeyTypes = $Settings.PasskeyTypes.value ?? $Settings.PasskeyTypes + + $AttestationEnforcement = $Settings.AttestationEnforcement.value ?? $Settings.AttestationEnforcement + + + $EnforceKeyRestrictions = [bool]$Settings.EnforceKeyRestrictions + $EnforcementType = $Settings.EnforcementType.value ?? $Settings.EnforcementType ?? 'allow' + + # Parse AAGUIDs from comma-separated string + $AAGUIDs = @() + if (-not [string]::IsNullOrWhiteSpace($Settings.AAGUIDs)) { + $AAGUIDs = @($Settings.AAGUIDs -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) + } + + # Key restrictions require at least one AAGUID + if ($EnforceKeyRestrictions -and $AAGUIDs.Count -eq 0) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'FIDO2PasskeyProfiles: Key restrictions are enabled but no AAGUIDs specified. Provide at least one AAGUID or disable key restrictions.' -sev Error + return + } + + # Get current FIDO2 configuration + try { + $CurrentConfig = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Fido2' -tenantid $Tenant -AsApp $true + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "FIDO2PasskeyProfiles: Could not retrieve current FIDO2 configuration. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return $true + } + + # Find the default passkey profile + $DefaultProfileId = $CurrentConfig.defaultPasskeyProfile + $DefaultProfile = $CurrentConfig.passkeyProfiles | Where-Object { $_.id -eq $DefaultProfileId } + + # Determine compliance against the default profile + $StateIsCorrect = $false + if ($DefaultProfile) { + $ExistingAAGUIDs = @($DefaultProfile.keyRestrictions.aaGuids | Sort-Object) + $DesiredAAGUIDs = @($AAGUIDs | Sort-Object) + $AAGUIDsMatch = (-not (Compare-Object -ReferenceObject $DesiredAAGUIDs -DifferenceObject $ExistingAAGUIDs -ErrorAction SilentlyContinue)) + + $StateIsCorrect = ( + $DefaultProfile.passkeyTypes -eq $PasskeyTypes -and + $DefaultProfile.attestationEnforcement -eq $AttestationEnforcement -and + $DefaultProfile.keyRestrictions.isEnforced -eq $EnforceKeyRestrictions -and + $DefaultProfile.keyRestrictions.enforcementType -eq $EnforcementType -and + $AAGUIDsMatch + ) + } + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'FIDO2PasskeyProfiles: Default passkey profile is already configured correctly.' -sev Info + } else { + try { + # Update the default profile in the profiles array, preserve all others + $ExistingProfiles = @($CurrentConfig.passkeyProfiles) + $UpdatedProfiles = foreach ($Profile in $ExistingProfiles) { + if ($Profile.id -eq $DefaultProfileId) { + @{ + id = $Profile.id + name = $Profile.name + passkeyTypes = $PasskeyTypes + attestationEnforcement = $AttestationEnforcement + keyRestrictions = @{ + isEnforced = $EnforceKeyRestrictions + enforcementType = $EnforcementType + aaGuids = $AAGUIDs + } + } + } else { + $Profile + } + } + + $Body = @{ + '@odata.type' = '#microsoft.graph.fido2AuthenticationMethodConfiguration' + passkeyProfiles = @($UpdatedProfiles) + } | ConvertTo-Json -Compress -Depth 10 + + Write-Host "FIDO2PasskeyProfiles: Request body: $Body" + + $null = New-GraphPostRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/Fido2' -Type PATCH -Body $Body -ContentType 'application/json' -AsApp $true + Write-LogMessage -API 'Standards' -tenant $Tenant -message "FIDO2PasskeyProfiles: Successfully configured default passkey profile with $($AAGUIDs.Count) AAGUID(s), passkey types '$PasskeyTypes', attestation '$AttestationEnforcement'." -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "FIDO2PasskeyProfiles: Failed to configure default passkey profile. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'FIDO2PasskeyProfiles: Default passkey profile is compliant.' -sev Info + } else { + $AlertDetails = if ($DefaultProfile) { + 'Default passkey profile exists but is not configured as expected.' + } else { + 'No default passkey profile found.' + } + Write-StandardsAlert -message "FIDO2PasskeyProfiles: $AlertDetails" -object $DefaultProfile -tenant $Tenant -standardName 'FIDO2PasskeyProfiles' -standardId $Settings.standardId + } + } + + if ($Settings.report -eq $true) { + $CurrentValue = [PSCustomObject]@{ + PasskeyTypes = $DefaultProfile.passkeyTypes ?? 'N/A' + AttestationEnforcement = $DefaultProfile.attestationEnforcement ?? 'N/A' + EnforceKeyRestrictions = $DefaultProfile.keyRestrictions.isEnforced ?? $false + EnforcementType = $DefaultProfile.keyRestrictions.enforcementType ?? 'N/A' + AAGUIDs = ($DefaultProfile.keyRestrictions.aaGuids ?? @()) -join ', ' + } + $ExpectedValue = [PSCustomObject]@{ + PasskeyTypes = $PasskeyTypes + AttestationEnforcement = $AttestationEnforcement + EnforceKeyRestrictions = $EnforceKeyRestrictions + EnforcementType = $EnforcementType + AAGUIDs = $AAGUIDs -join ', ' + } + Set-CIPPStandardsCompareField -FieldName 'standards.FIDO2PasskeyProfiles' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant + Add-CIPPBPAField -FieldName 'FIDO2PasskeyProfiles' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} From 03abdad414c58c3a3502068465c1ff6441fcc140 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 25 May 2026 01:58:21 +0200 Subject: [PATCH 28/90] add global var showing --- .../CIPP/Settings/Invoke-ExecCippReplacemap.ps1 | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 index 54d11633e42f..17e84e919a89 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCippReplacemap.ps1 @@ -29,10 +29,23 @@ function Invoke-ExecCippReplacemap { switch ($Action) { 'List' { - $Variables = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$customerId'" + $Variables = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$customerId'" | ForEach-Object { + $_ | Add-Member -NotePropertyName 'Scope' -NotePropertyValue $(if ($customerId -eq 'AllTenants') { 'Global' } else { 'Tenant' }) -PassThru + } if (!$Variables) { $Variables = @() } + $IncludeGlobal = $Request.Query.includeGlobal ?? $Request.Body.includeGlobal + if ($IncludeGlobal -eq 'true' -and $customerId -ne 'AllTenants') { + $GlobalVariables = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'AllTenants'" | ForEach-Object { + $_ | Add-Member -NotePropertyName 'Scope' -NotePropertyValue 'Global' -PassThru + } + if ($GlobalVariables) { + $TenantVarNames = @($Variables | ForEach-Object { $_.RowKey }) + $GlobalVariables = @($GlobalVariables | Where-Object { $_.RowKey -notin $TenantVarNames }) + $Variables = @($Variables) + @($GlobalVariables) + } + } $Body = @{ Results = @($Variables) } } 'AddEdit' { From f09ce56acc6efba45ee1cb0b9dcbcf43c1224ac5 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 25 May 2026 11:58:16 +0800 Subject: [PATCH 29/90] Update New-TeamsRequest.ps1 --- Modules/CIPPCore/Public/GraphHelper/New-TeamsRequest.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsRequest.ps1 index 64b85d354f46..48470f8cbd35 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsRequest.ps1 @@ -16,7 +16,8 @@ function New-TeamsRequest { $GraphToken = (Get-GraphToken -tenantid $TenantFilter).Authorization -replace 'Bearer ' $null = Connect-MicrosoftTeams -AccessTokens @($TeamsToken, $GraphToken) - & $Cmdlet @CmdParams + $Result = & $Cmdlet @CmdParams -ErrorAction Stop + $Result } else { Write-Error "Cmdlet $Cmdlet not found in MicrosoftTeams module" } From a0dab59a9091949721e42bd2f4c8cf78acc927cc Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 25 May 2026 14:35:20 +0800 Subject: [PATCH 30/90] domain fixes --- .../Domain Analyser/Push-DomainAnalyserTenant.ps1 | 4 ++-- .../Domain Analyser/Push-GetTenantDomains.ps1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 index 7a5c8cfac91a..f06d3a1fae6b 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 @@ -99,8 +99,8 @@ function Push-DomainAnalyserTenant { } if ($OldDomain) { - $DomainObject.DkimSelectors = $OldDomain.DkimSelectors - $DomainObject.MailProviders = $OldDomain.MailProviders + $Domain.DkimSelectors = $OldDomain.DkimSelectors + $Domain.MailProviders = $OldDomain.MailProviders } } else { $Domain.TenantDetails = $TenantDetails diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 index 071bb5289139..44d0ce7a9710 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-GetTenantDomains.ps1 @@ -2,6 +2,6 @@ function Push-GetTenantDomains { Param($Item) $DomainTable = Get-CippTable -tablename 'Domains' $Filter = "PartitionKey eq 'TenantDomains' and TenantGUID eq '{0}'" -f $Item.TenantGUID - $Domains = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter -Property PartitionKey, RowKey | Select-Object RowKey, @{n = 'FunctionName'; exp = { 'DomainAnalyserDomain' } } + $Domains = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter -Property PartitionKey, RowKey, TenantId | Select-Object RowKey, @{n = 'FunctionName'; exp = { 'DomainAnalyserDomain' } }, @{n = 'TenantFilter'; exp = { $_.TenantId } } return @($Domains) } From 08b972ccac96794551db5c6dca9c2135914144e3 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 25 May 2026 15:33:49 +0800 Subject: [PATCH 31/90] timezone changes --- .../Initialize-CIPPTimezone.ps1 | 30 +++++++++++++++++++ .../CIPP/Settings/Invoke-ExecTimeSettings.ps1 | 3 +- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPCore/Public/Authentication/Initialize-CIPPTimezone.ps1 diff --git a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPTimezone.ps1 b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPTimezone.ps1 new file mode 100644 index 000000000000..01f6400fb5e5 --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPTimezone.ps1 @@ -0,0 +1,30 @@ +function Initialize-CIPPTimezone { + <# + .SYNOPSIS + Loads the configured timezone from storage, sets $env:CIPP_TIMEZONE, + and updates the scheduler timezone. + + .DESCRIPTION + Reads the TimeSettings row from the Config table. Sets the CIPP_TIMEZONE + environment variable to the configured timezone, or defaults to 'UTC' if not set or on error. + #> + [CmdletBinding()] + param() + + try { + $ConfigTable = Get-CIPPTable -tablename Config + $TimeSettings = Get-CIPPAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'TimeSettings' and RowKey eq 'TimeSettings'" -Property @('PartitionKey', 'RowKey', 'Timezone') -First 1 + if ($TimeSettings.Timezone) { + $null = [TimeZoneInfo]::FindSystemTimeZoneById($TimeSettings.Timezone) + [Craft.Services.PowerShellRunnerService]::SetProcessEnvVar('CIPP_TIMEZONE', $TimeSettings.Timezone) + [Craft.Services.PowerShellRunnerService]::SetProcessEnvVar('CraftTZ', $TimeSettings.Timezone) + Write-Information "[Timezone-Init] Timezone set to $($TimeSettings.Timezone)" + } else { + [Craft.Services.PowerShellRunnerService]::SetProcessEnvVar('CIPP_TIMEZONE', 'UTC') + Write-Information '[Timezone-Init] No timezone configured, defaulting to UTC' + } + } catch { + [Craft.Services.PowerShellRunnerService]::SetProcessEnvVar('CIPP_TIMEZONE', 'UTC') + Write-Warning "[Timezone-Init] Failed to load timezone, defaulting to UTC: $_" + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTimeSettings.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTimeSettings.ps1 index 1fcca265b612..c2769a2c9f02 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTimeSettings.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTimeSettings.ps1 @@ -31,7 +31,8 @@ function Invoke-ExecTimeSettings { $ConfigTable = Get-CIPPTable -tablename Config Add-CIPPAzDataTableEntity @ConfigTable -Entity $Config -Force | Out-Null $env:CIPP_TIMEZONE = $Timezone - + try { [Craft.Services.SchedulerBridge]::SetTimezone($Timezone) } catch { $null } + try { [Craft.Services.PowerShellRunnerService]::SetProcessEnvVar('CIPP_TIMEZONE', $Timezone) } catch { $null } Write-LogMessage -API 'ExecTimeSettings' -headers $Request.Headers -message "Updated time settings: Timezone=$Timezone" -Sev 'Info' return ([HttpResponseContext]@{ From d854e22d853449c8f45b44ed31c5e385508aba55 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Mon, 25 May 2026 11:49:28 +0200 Subject: [PATCH 32/90] feat: add function to remove users from admin roles --- .../Users/Invoke-ExecRemoveAdminRole.ps1 | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRemoveAdminRole.ps1 diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRemoveAdminRole.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRemoveAdminRole.ps1 new file mode 100644 index 000000000000..1d10053353ca --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRemoveAdminRole.ps1 @@ -0,0 +1,67 @@ +function Invoke-ExecRemoveAdminRole { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Identity.Role.ReadWrite + + .DESCRIPTION + Removes a user from an assigned Microsoft Entra admin role. + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $TenantFilter = $Request.Body.tenantFilter.value ?? $Request.Body.tenantFilter + $RoleId = $Request.Body.RoleId.value ?? $Request.Body.RoleId + $RoleName = $Request.Body.RoleName.label ?? $Request.Body.RoleName.value ?? $Request.Body.RoleName + $Users = if ($Request.Body.Users) { @($Request.Body.Users) } else { @() } + + # Input validation + if ([string]::IsNullOrWhiteSpace($TenantFilter) -or [string]::IsNullOrWhiteSpace($RoleId) -or $Users.Count -eq 0) { + $Result = 'TenantFilter, RoleId, and Users are required to remove an admin role assignment.' + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Error' + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ 'Results' = @($Result) } + } + } + + $Results = [System.Collections.Generic.List[string]]::new() + $Failures = 0 + + foreach ($User in $Users) { + # Handle both roles and view user pages where user info is in different properties + $UserId = $User.value ?? $User.id ?? $User.UserId ?? $User + $UserName = $User.addedFields.userPrincipalName ?? $User.addedFields.displayName ?? $User.userPrincipalName ?? $User.displayName ?? $User.label ?? $User.UserName ?? $UserId + + if ([string]::IsNullOrWhiteSpace($UserId)) { + $Failures++ + $Result = "Skipped a $RoleName role removal request because UserId was empty." + $Results.Add($Result) + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Error' + continue + } + + try { + $null = New-GraphPOSTRequest -type DELETE -uri "https://graph.microsoft.com/v1.0/directoryRoles/$RoleId/members/$UserId/`$ref" -tenantid $TenantFilter + $Result = "Successfully removed $UserName from admin role $RoleName." + $Results.Add($Result) + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Info' + } catch { + $Failures++ + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to remove $UserName from admin role $RoleName. $($ErrorMessage.NormalizedError)" + $Results.Add($Result) + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Result -Sev 'Error' -LogData $ErrorMessage + } + } + + $StatusCode = $Failures -gt 0 ? [HttpStatusCode]::InternalServerError : [HttpStatusCode]::OK + + return [HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{ 'Results' = @($Results) } + } +} From 46015ce7394fb954d357f8ebf26ef77a827626ad Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 25 May 2026 14:29:29 +0200 Subject: [PATCH 33/90] Add APv2 profile --- .../Invoke-CIPPStandardDevicePrepProfile.ps1 | 429 ++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDevicePrepProfile.ps1 diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDevicePrepProfile.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDevicePrepProfile.ps1 new file mode 100644 index 000000000000..cfd22352bd49 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDevicePrepProfile.ps1 @@ -0,0 +1,429 @@ +function Invoke-CIPPStandardDevicePrepProfile { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) DevicePrepProfile + .SYNOPSIS + (Label) Deploy Device Prep Profile + .DESCRIPTION + (Helptext) Creates and manages a Windows Autopilot Device Preparation profile for streamlined device enrollment. + (DocsDescription) Deploys a Windows Autopilot Device Preparation profile through Intune configuration policies. This standard manages deployment mode, join type, account type, timeout, error messages, and optional device security group assignment. Optionally creates a new security group with the Intune Provisioning Client as owner. + .NOTES + CAT + Device Management Standards + TAG + "autopilot" + "device_prep" + "enrollment" + ADDEDCOMPONENT + {"type":"textField","name":"standards.DevicePrepProfile.ProfileName","label":"Profile Display Name","required":true} + {"type":"textField","name":"standards.DevicePrepProfile.ProfileDescription","label":"Profile Description","required":false} + {"type":"select","multiple":false,"name":"standards.DevicePrepProfile.DeploymentType","label":"Deployment Type","options":[{"label":"Single user","value":"0"},{"label":"Shared","value":"1"}]} + {"type":"select","multiple":false,"name":"standards.DevicePrepProfile.JoinType","label":"Join Type","options":[{"label":"Microsoft Entra join","value":"0"},{"label":"Microsoft Entra hybrid join","value":"1"}]} + {"type":"select","multiple":false,"name":"standards.DevicePrepProfile.AccountType","label":"Account Type","options":[{"label":"Standard user","value":"0"},{"label":"Administrator","value":"1"}]} + {"type":"number","name":"standards.DevicePrepProfile.Timeout","label":"Timeout (minutes)","defaultValue":60} + {"type":"textField","name":"standards.DevicePrepProfile.CustomErrorMessage","label":"Custom Error Message","required":false} + {"type":"switch","name":"standards.DevicePrepProfile.AllowSkip","label":"Allow users to skip setup after failure","defaultValue":false} + {"type":"switch","name":"standards.DevicePrepProfile.AllowDiagnostics","label":"Allow users to collect diagnostics","defaultValue":false} + {"type":"textField","name":"standards.DevicePrepProfile.DeviceGroupName","label":"Device Security Group Name (wildcard match)","required":false} + {"type":"switch","name":"standards.DevicePrepProfile.CreateNewGroup","label":"Create new group if group is not found","defaultValue":false} + {"type":"radio","name":"standards.DevicePrepProfile.AssignTo","label":"Policy Assignment","options":[{"label":"Do not assign","value":"none"},{"label":"All devices","value":"AllDevices"},{"label":"All users and devices","value":"AllDevicesAndUsers"}]} + IMPACT + High Impact + ADDEDDATE + 2025-05-25 + POWERSHELLEQUIVALENT + Graph API - deviceManagement/configurationPolicies + RECOMMENDEDBY + DISABLEDFEATURES + {"report":false,"warn":false,"remediate":false} + REQUIREDCAPABILITIES + "INTUNE_A" + "MDM_Services" + "EMS" + "SCCM" + "MICROSOFTINTUNEPLAN1" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param($Tenant, $Settings) + + $TestResult = Test-CIPPStandardLicense -StandardName 'DevicePrepProfile' -TenantFilter $Tenant -Preset Intune + if ($TestResult -eq $false) { return $true } + + $ProfileName = $Settings.ProfileName + if ([string]::IsNullOrWhiteSpace($ProfileName)) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'DevicePrepProfile: ProfileName is empty, skipping.' -sev Error + return + } + + # Resolve setting values + $DeploymentMode = '0' # Device Prep only supports self-deploying mode + $DeploymentType = $Settings.DeploymentType.value ?? $Settings.DeploymentType ?? '0' + $JoinType = $Settings.JoinType.value ?? $Settings.JoinType ?? '0' + $AccountType = $Settings.AccountType.value ?? $Settings.AccountType ?? '0' + $Timeout = [int]($Settings.Timeout ?? 60) + $CustomErrorMessage = $Settings.CustomErrorMessage ?? "Contact your organization`u{2019}s support person for help." + $AllowSkip = if ($Settings.AllowSkip -eq $true) { '1' } else { '0' } + $AllowDiagnostics = if ($Settings.AllowDiagnostics -eq $true) { '1' } else { '0' } + $AssignTo = $Settings.AssignTo.value ?? $Settings.AssignTo ?? 'none' + + # Resolve device security group ID + $DeviceGroupId = '' + if (-not [string]::IsNullOrWhiteSpace($Settings.DeviceGroupName)) { + $GroupName = $Settings.DeviceGroupName + try { + $EscapedName = $GroupName -replace "'", "''" + $GroupFilter = [System.Uri]::EscapeDataString("startsWith(displayName,'$EscapedName') and mailEnabled eq false and securityEnabled eq true") + $MatchedGroups = @(New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$select=id,displayName&`$filter=$GroupFilter" -tenantid $Tenant) + + if ($MatchedGroups.Count -gt 1) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Multiple groups found matching '$GroupName', using first match '$($MatchedGroups[0].displayName)'" -sev Warning + $DeviceGroupId = $MatchedGroups[0].id + } elseif ($MatchedGroups.Count -eq 1) { + $DeviceGroupId = $MatchedGroups[0].id + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Found group '$($MatchedGroups[0].displayName)' (ID: $DeviceGroupId)" -sev Info + } elseif ($Settings.CreateNewGroup -eq $true -and $Settings.remediate -eq $true) { + # Group not found — create it with Intune Provisioning Client as owner + $IntuneProvisioningAppId = 'f1346770-5b25-470b-88bd-d5744ab7952c' + $ServicePrincipal = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$filter=appId eq '$IntuneProvisioningAppId'&`$select=id" -tenantid $Tenant + $SpId = $ServicePrincipal.id + if ([string]::IsNullOrWhiteSpace($SpId)) { + # Service principal not found — instantiate it in the tenant + try { + $SpBody = @{ appId = $IntuneProvisioningAppId } | ConvertTo-Json -Compress + $CreatedSp = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $Tenant -body $SpBody -type POST + $SpId = $CreatedSp.id + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Created Intune Provisioning Client service principal (ID: $SpId)" -sev Info + } catch { + $SpError = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Failed to create Intune Provisioning Client service principal: $($SpError.NormalizedError)" -sev Error -LogData $SpError + return + } + } + + $GroupBody = @{ + displayName = $GroupName + description = 'Device Preparation security group managed by CIPP' + securityEnabled = $true + mailEnabled = $false + mailNickname = ($GroupName -replace '[^a-zA-Z0-9]', '') + (Get-Random -Maximum 9999) + 'owners@odata.bind' = @( + "https://graph.microsoft.com/v1.0/servicePrincipals/$SpId" + ) + } | ConvertTo-Json -Compress -Depth 10 + + $NewGroup = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $Tenant -body $GroupBody -type POST + $DeviceGroupId = $NewGroup.id + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Created security group '$GroupName' (ID: $DeviceGroupId) with Intune Provisioning Client as owner" -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: No security group found matching '$GroupName'" -sev Warning + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Failed to resolve device group: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + + # Build the expected configuration policy body + $PolicyBody = @{ + name = $ProfileName + description = $Settings.ProfileDescription ?? '' + roleScopeTagIds = @('0') + platforms = 'windows10' + technologies = 'enrollment' + templateReference = @{ + templateId = '80d33118-b7b4-40d8-b15f-81be745e053f_1' + } + settings = @( + @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSetting' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_deploymentmode' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = '5180aeab-886e-4589-97d4-40855c646315' } + choiceSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingValue' + children = @() + settingValueTemplateReference = @{ settingValueTemplateId = '5874c2f6-bcf1-463b-a9eb-bee64e2f2d82' } + value = "enrollment_autopilot_dpp_deploymentmode_$DeploymentMode" + } + } + } + @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSetting' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_deploymenttype' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'f4184296-fa9f-4b67-8b12-1723b3f8456b' } + choiceSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingValue' + children = @() + settingValueTemplateReference = @{ settingValueTemplateId = 'e0af022f-37f3-4a40-916d-1ab7281c88d9' } + value = "enrollment_autopilot_dpp_deploymenttype_$DeploymentType" + } + } + } + @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSetting' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_jointype' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = '6310e95d-6cfa-4d2f-aae0-1e7af12e2182' } + choiceSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingValue' + children = @() + settingValueTemplateReference = @{ settingValueTemplateId = '1fa84eb3-fcfa-4ed6-9687-0f3d486402c4' } + value = "enrollment_autopilot_dpp_jointype_$JoinType" + } + } + } + @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSetting' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_accountype' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'd4f2a840-86d5-4162-9a08-fa8cc608b94e' } + choiceSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingValue' + children = @() + settingValueTemplateReference = @{ settingValueTemplateId = 'bf13bb47-69ef-4e06-97c1-50c2859a49c2' } + value = "enrollment_autopilot_dpp_accountype_$AccountType" + } + } + } + @{ + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_timeout' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = '6dec0657-dfb8-4906-a7ee-3ac6ee1edecb' } + simpleSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationIntegerSettingValue' + value = $Timeout + settingValueTemplateReference = @{ settingValueTemplateId = '0bbcce5b-a55a-4e05-821a-94bf576d6cc8' } + } + } + } + @{ + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_customerrormessage' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = '2ddf0619-2b7a-46de-b29b-c6191e9dda6e' } + simpleSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue' + value = $CustomErrorMessage + settingValueTemplateReference = @{ settingValueTemplateId = 'fe5002d5-fbe9-4920-9e2d-26bfc4b4cc97' } + } + } + } + @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSetting' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_allowskip' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = '2a71dc89-0f17-4ba9-bb27-af2521d34710' } + choiceSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingValue' + children = @() + settingValueTemplateReference = @{ settingValueTemplateId = 'a2323e5e-ac56-4517-8847-b0a6fdb467e7' } + value = "enrollment_autopilot_dpp_allowskip_$AllowSkip" + } + } + } + @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSetting' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_allowdiagnostics' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'e2b7a81b-f243-4abd-bce3-c1856345f405' } + choiceSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingValue' + children = @() + settingValueTemplateReference = @{ settingValueTemplateId = 'c59d26fd-3460-4b26-b47a-f7e202e7d5a3' } + value = "enrollment_autopilot_dpp_allowdiagnostics_$AllowDiagnostics" + } + } + } + @{ + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance' + settingDefinitionId = 'enrollment_autopilot_dpp_devicesecuritygroupids' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'a46a50ab-3076-4968-9366-75a40dde950e' } + simpleSettingValue = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue' + value = $DeviceGroupId + settingValueTemplateReference = @{ settingValueTemplateId = '5f7d09e1-1a90-44ad-9c9f-ad90ba509e60' } + } + } + } + ) + } + + # Setting definition ID map for parsing current state + $ChoiceSettingMap = @{ + 'enrollment_autopilot_dpp_deploymentmode' = 'DeploymentMode' + 'enrollment_autopilot_dpp_deploymenttype' = 'DeploymentType' + 'enrollment_autopilot_dpp_jointype' = 'JoinType' + 'enrollment_autopilot_dpp_accountype' = 'AccountType' + 'enrollment_autopilot_dpp_allowskip' = 'AllowSkip' + 'enrollment_autopilot_dpp_allowdiagnostics' = 'AllowDiagnostics' + } + $SimpleSettingMap = @{ + 'enrollment_autopilot_dpp_timeout' = 'Timeout' + 'enrollment_autopilot_dpp_customerrormessage' = 'CustomErrorMessage' + 'enrollment_autopilot_dpp_devicesecuritygroupids' = 'DeviceGroupId' + } + + # Check existing policies + try { + $ExistingPolicies = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $Tenant + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Failed to retrieve configuration policies: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return + } + + $ExistingPolicy = $ExistingPolicies | Where-Object { $_.Name -eq $ProfileName } | Select-Object -First 1 + $PolicyExists = $null -ne $ExistingPolicy + + # Parse current settings + $CurrentParsed = @{} + if ($PolicyExists) { + try { + $PolicyDetail = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($ExistingPolicy.id)')?`$expand=settings" -tenantid $Tenant + foreach ($Setting in $PolicyDetail.settings) { + $Instance = $Setting.settingInstance + $DefId = $Instance.settingDefinitionId + + if ($ChoiceSettingMap.ContainsKey($DefId)) { + $CurrentParsed[$ChoiceSettingMap[$DefId]] = ($Instance.choiceSettingValue.value -split '_')[-1] + } elseif ($SimpleSettingMap.ContainsKey($DefId)) { + $CurrentParsed[$SimpleSettingMap[$DefId]] = $Instance.simpleSettingValue.value + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Failed to read policy settings: $($ErrorMessage.NormalizedError)" -sev Warning -LogData $ErrorMessage + } + } + + $CurrentValue = [PSCustomObject]@{ + PolicyExists = $PolicyExists + DeploymentMode = [string]($CurrentParsed.DeploymentMode ?? '') + DeploymentType = [string]($CurrentParsed.DeploymentType ?? '') + JoinType = [string]($CurrentParsed.JoinType ?? '') + AccountType = [string]($CurrentParsed.AccountType ?? '') + Timeout = [int]($CurrentParsed.Timeout ?? 0) + CustomErrorMessage = [string]($CurrentParsed.CustomErrorMessage ?? '') + AllowSkip = [string]($CurrentParsed.AllowSkip ?? '') + AllowDiagnostics = [string]($CurrentParsed.AllowDiagnostics ?? '') + DeviceGroupId = [string]($CurrentParsed.DeviceGroupId ?? '') + } + + $ExpectedValue = [PSCustomObject]@{ + PolicyExists = $true + DeploymentMode = $DeploymentMode + DeploymentType = $DeploymentType + JoinType = $JoinType + AccountType = $AccountType + Timeout = $Timeout + CustomErrorMessage = $CustomErrorMessage + AllowSkip = $AllowSkip + AllowDiagnostics = $AllowDiagnostics + DeviceGroupId = $DeviceGroupId + } + + # Determine compliance + $StateIsCorrect = $PolicyExists + if ($PolicyExists) { + $PropertiesToCompare = @('DeploymentMode', 'DeploymentType', 'JoinType', 'AccountType', 'AllowSkip', 'AllowDiagnostics') + foreach ($Prop in $PropertiesToCompare) { + if ([string]$CurrentValue.$Prop -ne [string]$ExpectedValue.$Prop) { + $StateIsCorrect = $false + break + } + } + if ($StateIsCorrect -and [int]$CurrentValue.Timeout -ne $ExpectedValue.Timeout) { $StateIsCorrect = $false } + if ($StateIsCorrect -and $CurrentValue.CustomErrorMessage -ne $ExpectedValue.CustomErrorMessage) { $StateIsCorrect = $false } + if ($StateIsCorrect -and $CurrentValue.DeviceGroupId -ne $ExpectedValue.DeviceGroupId) { $StateIsCorrect = $false } + } + + # Remediate + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Profile '$ProfileName' already correctly configured" -sev Info + } else { + try { + # Delete drifted policy before recreating + if ($PolicyExists) { + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($ExistingPolicy.id)')" -tenantid $Tenant -type DELETE + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Deleted existing profile '$ProfileName' for recreation" -sev Info + } + + $Body = $PolicyBody | ConvertTo-Json -Compress -Depth 20 + $NewPolicy = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $Tenant -body $Body -type POST + + # Assign the policy if requested + if ($AssignTo -ne 'none' -and $NewPolicy.id) { + $AssignBody = switch ($AssignTo) { + 'AllDevices' { + @{ + assignments = @( + @{ + target = @{ + '@odata.type' = '#microsoft.graph.allDevicesAssignmentTarget' + } + } + ) + } + } + 'AllDevicesAndUsers' { + @{ + assignments = @( + @{ + target = @{ + '@odata.type' = '#microsoft.graph.allDevicesAssignmentTarget' + } + } + @{ + target = @{ + '@odata.type' = '#microsoft.graph.allLicensedUsersAssignmentTarget' + } + } + ) + } + } + } + if ($AssignBody) { + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($NewPolicy.id)')/assign" -tenantid $Tenant -body ($AssignBody | ConvertTo-Json -Compress -Depth 10) -type POST + } + } + + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Successfully deployed profile '$ProfileName'" -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Failed to deploy profile: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + # Alert + if ($Settings.alert -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Profile '$ProfileName' is correctly configured" -sev Info + } else { + Write-StandardsAlert -message "Device Prep Profile '$ProfileName' is not correctly configured" -object $CurrentValue -tenant $Tenant -standardName 'DevicePrepProfile' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message "DevicePrepProfile: Profile '$ProfileName' is not correctly configured" -sev Info + } + } + + # Report + if ($Settings.report -eq $true) { + Set-CIPPStandardsCompareField -FieldName 'standards.DevicePrepProfile' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant + Add-CIPPBPAField -FieldName 'DevicePrepProfile' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} From 71afcdd2be483a31473fcadaf239888671548787 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 26 May 2026 11:54:49 +0800 Subject: [PATCH 34/90] ExoTransportConfig cache type - fix for missing data used in test suites --- Config/CIPPDBCacheTypes.json | 5 +++ .../Public/Invoke-CIPPDBCacheCollection.ps1 | 1 + .../Set-CIPPDBCacheExoTransportConfig.ps1 | 35 +++++++++++++++++++ ...nvoke-CIPPStandardDisableBasicAuthSMTP.ps1 | 2 +- .../Invoke-CIPPStandardMessageExpiration.ps1 | 3 +- .../CIS/Identity/Invoke-CippTestCIS_6_5_4.ps1 | 4 +-- 6 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportConfig.ps1 diff --git a/Config/CIPPDBCacheTypes.json b/Config/CIPPDBCacheTypes.json index 071eff75a431..eb833092befd 100644 --- a/Config/CIPPDBCacheTypes.json +++ b/Config/CIPPDBCacheTypes.json @@ -363,5 +363,10 @@ "type": "CopilotUserCountTrend", "friendlyName": "Copilot User Count Trend", "description": "Daily Copilot active user count trend (7-day period)" + }, + { + "type": "ExoTransportConfig", + "friendlyName": "Exchange Transport Config", + "description": "Exchange Online transport configuration including SMTP authentication settings" } ] diff --git a/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 index 9e666f8af279..c4af3e75d077 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 @@ -89,6 +89,7 @@ function Invoke-CIPPDBCacheCollection { 'ExoTenantAllowBlockList' 'OwaMailboxPolicy' 'ReportSubmissionPolicy' + 'ExoTransportConfig' ) ExchangeData = @( 'CASMailboxes' diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportConfig.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportConfig.ps1 new file mode 100644 index 000000000000..f606c288f020 --- /dev/null +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoTransportConfig.ps1 @@ -0,0 +1,35 @@ +function Set-CIPPDBCacheExoTransportConfig { + <# + .SYNOPSIS + Caches Exchange Online Transport Configuration + + .PARAMETER TenantFilter + The tenant to cache transport configuration for + + .PARAMETER QueueId + The queue ID to update with total tasks (optional) + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + [string]$QueueId + ) + + try { + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Exchange Transport configuration' -sev Debug + + $TransportConfig = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-TransportConfig' + + if ($TransportConfig) { + # TransportConfig returns a single object, wrap in array for consistency + $TransportConfigArray = @($TransportConfig) + Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'ExoTransportConfig' -Data $TransportConfigArray -AddCount + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Cached Exchange Transport configuration' -sev Debug + } + $TransportConfig = $null + + } catch { + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache Transport configuration: $($_.Exception.Message)" -sev Error + } +} diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 index 64c39c224c69..70637854ca19 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 @@ -50,7 +50,7 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableBasicAuthSMTP' try { - $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig' + $CurrentInfo = New-CIPPDbRequest -TenantFilter $Tenant -Type 'ExoTransportConfig' | Select-Object -First 1 $SMTPusers = New-CippDbRequest -TenantFilter $Tenant -Type 'CASMailbox' | Where-Object { ($_.SmtpClientAuthenticationDisabled -eq $false) } } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 index 1bc199ba4537..4390c14ef392 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 @@ -41,7 +41,8 @@ function Invoke-CIPPStandardMessageExpiration { } #we're done. try { - $MessageExpiration = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig').messageExpiration + $TransportConfig = New-CIPPDbRequest -TenantFilter $Tenant -Type 'ExoTransportConfig' | Select-Object -First 1 + $MessageExpiration = $TransportConfig.messageExpiration } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the MessageExpiration state for $Tenant. Error: $ErrorMessage" -Sev Error diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_6_5_4.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_6_5_4.ps1 index 69384d24eacc..ad4eac06c812 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_6_5_4.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_6_5_4.ps1 @@ -6,10 +6,10 @@ function Invoke-CippTestCIS_6_5_4 { param($Tenant) try { - $Org = Get-CIPPTestData -TenantFilter $Tenant -Type 'ExoOrganizationConfig' + $Org = Get-CIPPTestData -TenantFilter $Tenant -Type 'ExoTransportConfig' if (-not $Org) { - Add-CippTestResult -TenantFilter $Tenant -TestId 'CIS_6_5_4' -TestType 'Identity' -Status 'Skipped' -ResultMarkdown 'ExoOrganizationConfig cache not found.' -Risk 'High' -Name 'SMTP AUTH is disabled' -UserImpact 'Medium' -ImplementationEffort 'Low' -Category 'Authentication' + Add-CippTestResult -TenantFilter $Tenant -TestId 'CIS_6_5_4' -TestType 'Identity' -Status 'Skipped' -ResultMarkdown 'ExoTransportConfig cache not found.' -Risk 'High' -Name 'SMTP AUTH is disabled' -UserImpact 'Medium' -ImplementationEffort 'Low' -Category 'Authentication' return } From 1e63ebf0fc6b61a4873d4eb2f4f1b389f034676a Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 26 May 2026 15:09:22 +0800 Subject: [PATCH 35/90] Update Invoke-CIPPStandardsharingDomainRestriction.ps1 --- .../Invoke-CIPPStandardsharingDomainRestriction.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 index 6bec4a3e9c6c..1517af22f389 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 @@ -121,13 +121,13 @@ function Invoke-CIPPStandardsharingDomainRestriction { $CurrentValue = @{ sharingDomainRestrictionMode = $CurrentState.sharingDomainRestrictionMode - sharingAllowedDomainList = $CurrentState.sharingAllowedDomainList - sharingBlockedDomainList = $CurrentState.sharingBlockedDomainList + sharingAllowedDomainList = @($CurrentState.sharingAllowedDomainList ?? @()) + sharingBlockedDomainList = @($CurrentState.sharingBlockedDomainList ?? @()) } $ExpectedValue = @{ sharingDomainRestrictionMode = $mode - sharingAllowedDomainList = if ($mode -eq 'allowList') { $SelectedDomains } else { @() } - sharingBlockedDomainList = if ($mode -eq 'blockList') { $SelectedDomains } else { @() } + sharingAllowedDomainList = @(if ($mode -eq 'allowList') { $SelectedDomains } else { @() }) + sharingBlockedDomainList = @(if ($mode -eq 'blockList') { $SelectedDomains } else { @() }) } Set-CIPPStandardsCompareField -FieldName 'standards.sharingDomainRestriction' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -Tenant $Tenant } From 59a0e15d1c11b1c8c33e0d04f3f6cd9dd96e23b0 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 26 May 2026 15:12:33 +0800 Subject: [PATCH 36/90] update application content type handling --- Shared/CIPPSharp/CIPPRestClient.cs | 12 +++++------- Shared/CIPPSharp/bin/CIPPSharp.dll | Bin 33280 -> 33280 bytes 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Shared/CIPPSharp/CIPPRestClient.cs b/Shared/CIPPSharp/CIPPRestClient.cs index 2f0cf94fde7d..70a610110c12 100644 --- a/Shared/CIPPSharp/CIPPRestClient.cs +++ b/Shared/CIPPSharp/CIPPRestClient.cs @@ -564,13 +564,11 @@ public static async Task SendAsync( request.Content = new StringContent(body, encoding, mediaTypePart); - // Re-apply the full Content-Type (including charset) because - // StringContent's constructor strips parameters on some runtimes - if (ctParts.Length > 1) - { - request.Content.Headers.Remove("Content-Type"); - request.Content.Headers.TryAddWithoutValidation("Content-Type", effectiveCt); - } + // Re-apply the exact Content-Type the caller specified. + // StringContent's constructor auto-appends "; charset=utf-8" + // which breaks APIs that require a bare media type (e.g. "text/css"). + request.Content.Headers.Remove("Content-Type"); + request.Content.Headers.TryAddWithoutValidation("Content-Type", effectiveCt); } // Apply any deferred content headers (e.g. Content-Encoding) diff --git a/Shared/CIPPSharp/bin/CIPPSharp.dll b/Shared/CIPPSharp/bin/CIPPSharp.dll index d5d33d239f5533aba48891854bbb850111fab111..c35be9216c3f8a1a482f6ddd2d4344dbcd13979e 100644 GIT binary patch delta 5245 zcma)AX>?O%8h*c<<=)&Z%}tu6OK0mP!MUYH7zZc zO|f$k7!jutILfGJj;G>^+DJh~R73`+6`9sT71X0JIGj=O%=^8$gTnlooRjB!p6A`Z z-0z;Gw=L~LOS|ys8hzul;o;5=DXgG#_f2d-Wh`ju-2#xJ287XSB@41LwUtHGZyF`j zOaKtT0Izf_!tzwMOkF*qlKIqsjM&C*QJeg$S%S&}qyCWPe%xsd?E{dVHc!Y7kR9I7 zMM8G9k`O+?sa>%zYZYOsFZ>S5(Z5&hPBR&ycX`;Mm=%17xlkNxB#=5yQIyaj9!Y(8N(x^vA+!e|k#b!Z(Inr;Ws3r&@XicPUD{{Y?ahY0GIfy#2HB0Ce~ zIZl`23V+1gBq^>3ry`+UFF%4T%ju??cqV0q52LR*)z);6&Z#7-+tN2yB{`BpeP}zq z4sWO*Nlv-o$&#Dr@hvFc<^_Cx;Ce)dR|qM1Ek1unhgY$Q2b^Bv0lxm?8VBGLTv{D= z`i!^q2#QBtmr*x)gkwZ#fIDf9w9sIDi1*TvwB|d09g-%59DK+Z0w2Ny&NMYQ(;Z0* zeZf1(*&=;Vr;L{&IfM!F@*y7Z2~9Pa9X^R7e2P2%>B>3YUDF$Z#t%9$Mkr|^#7-$>du;-lHtf$_B`G- zp>Oz3GK8i&+;J!ox8lf9AI!RTOmg_27;&a7OOW`zbsj~ITS*zw6#juzGtyg$W;2g7 zRsAt5Q%Meo0E}5mYWONzeQNH=>shAy?#Ky(lBvc<-W{>vwKU_c@4*$%4r~AnDZI=8 z?!h$+1>WWK;{Ha2HMn{(84u&qYX$0dRR6{muivFU1A2fw)ON$=eWsyCzXtBTB9Mdy zYjMa^MgXtYo)4abjhEp2wJUr)Cpe8gj{L^wWg@?}vJ$KF!<{G$jw}Cs1YQF^T1gX|aRZr?}K-DSzQ?R*?D-_*1(MLH6Di%K5_3xKf}lQyJn6KBhNWw>ysF4 z7C0`^y@2gb>Rv}I)%PIo)bB^M7!D%xF$|s-K0@@7^RVF0CZk8{!J5)f({rB^95Rn_n`{FZ)0o6`$kuWEfH)K~n)wP)-4_riPO` zAQk+YI+rv?{d2xA5|qxH zH>&jozS>$j6BhID8yuVIT_leLLw;PXbS{-gK@q9V@Q!JPd=*q_s?^va7r=m~K5}lC z$HNOnylyj`a7IymPpZ@VtTU>9SKwno)m-SS9V1VGma#nVA?caqn0_Lx(bQ|nyX1+` z8CP_vCc#cpn<3BloIDBgisSvw${u+N=u6`2sClnE4Hjss!nRahTj-1Ildpj_rSS|y z!cuuQ9MTlu>l`Sf|C{q#rZ%6L{-3C}I8bJiseM>S^egZveZd=40$8 zaBHer7?3L=MNRX8hG!6Z%Hpc{_J)i6Vohjhbo z4b0V)Uw>9!05@xDgML_E2-TW8kE#yt($qM^S@}*_uBjDs*qn%E z2ET5D>@ITwtbk-v4@vFz4!94}HT5U^82LWP))c?-Rzjhs_>H#`CTgl5V+}A(Q(t4O z0p`RN-IR?`MJj%-jj(veWx;z%wc)RHj=2$<<1s1KR%pH-R%@!>Sc2-|OLdb_?daZE zJWGuH-MESEa<0;EVq2W2jZd(>Nf(Vf*_$3ktY(#NpSV^ykq{6!v3nB6>7QWj$r*+| zrt=2H3O3S^CngEQ_Eg<-SX(Z>h_!QsKK7~Y6V}J}*{;LP7j4&xQNirHS?psQ5^At& zwe3#vRbiJijQ!6gHAHY3PKt<&aM)@bR$*ID;YY<7V~Oin6gSt&cC!xiRx!$+N_bLy zPbf9+6!)_8p1oo(D|a3i53#R}8HPjbM(-SCT@G}lxBAv#aE|+4$E9N%otgzYK0evhtnd4#? z|CFEF)=J+%g}uo54ODrTAm{hQd#oVg5y=M6!(Sx_yZ|pD?jd{?QGhq#bxDF4yn(nM z-p0%iDDz`@DT3Bf_zM>NjSBjx;BzV%q=HjeAc2*=gBV~hA!e~dh&k+IDFAmv%#sC7 zaKRLWTVb4a4F2Kxn0XBTx%VWZ58gn`fCGrxptp>HLU1BZfHcIZkZp;KfmsxohgeSS zra~>;ZkbL^t6(|uYVvEzZy~3ZoHlaWF*9iGB0oz04)S}*iIKApzk`C-ebjT1oFQ_C zB4jbfKZlO-Bpu_QCO}RGIT_@XlQWf^spM3XQ_J{vTZy+3Z^tNZi1DpQ$=^YK4>>V% z_K`EdB0Oo3tRZR&0(Y1|8xnYHe6quK0dg{gHaKX(2Laq-IXP97Urm`U#9N7X5swn@ zAs!<>NXT?e*e9TCir`XCVj5!5T1~u_FiIFB9MI85iG!YVy

d>h;?o+3F{!jIc`2 zH{C+mMc6|)KsZPU2A;zZBez@pBm#tGgjIwsgzW~t)18KGkYnv7-fkqv$ekYJdRSoT zC1()vf{BUS;IJh?SWZ|?*edd9m$)9bSfb>_xDzoAqWH|hBp%iy9<=x+zPkW9ndFp_ zQ!eq$D&o}?Z6V%Dyo-30a(alz$R8wRCSDsb@hRnm)r65&TB3w8O!~^wi~OKvkQ}|t zc`M=tQ;cwskeRo^c}u`d?FeILejG9MldZkz1g(SQgQX2?nT2QiiI)+UlQS)XOVCK=w1H=a@2dvcE%CDB6cz}2r@p9tRtkj>JYUCG8EyP=``~!4Z z7x5NRO6{RQjQ9X?u<@v$Fw;hjZ2Z>iBJ8p8!2^VlK${>eBdj6>JNX>d`f2kc)6D?Q z@->LBxj64lqXs@4wOQ*XLvERf4+wDB?W^xhFi)Gv%b7)|1MahCX^_>R~wGD(zd(s*gMv_L9VbFU6Ywn{Ux06^lP zy|zmGU7TL-{X~!QmwPq%$bV(tyGj4+eT)B#xH8YJpk=#kNK_rV`dimw<62gXzq~{E zc1e9uEPlqSx=+pAl3};M{95+*&n8ZJbNloSj|6qi-5VB9H0o3_OMQP?X7|};M+~9- zg~f#nYwGgqYKv=%^Q((WO9~ehEvzZ1E2_({DJ&gVmseO+TvA-Ee%)|Z?Qe8Aq^*2! z@mD(M{?33K|E*_H{r6`Y(jf@`Zl&d8)@^&>h%h%F7UI)zAz}^G;eR3O@HeCwc`@We vH55T9lwfoLGM-U@@gjWZBQM0*II1Z`51**XXczab79U5b7Mdlhsz);1A-1}D|jilG9$GjpIShn)QW`63)+C7 zMt3@9#57}003W=sgH+45rbS2G9?WSex*6M!$H)q|n;W_Vl-}k%y=RfC} z8K$9GXlNE5e?TlVy>+Pd;biuQ)|YQ$r!>a0jGwLsNY;YFV6BX0vFo))7E{xn0(wnE z6u=1G9jZ{5!tU3WrDBry&(t-nTB}c6#sV4(W>0h3RGa4+mp!}ZsYlsj8~AjS}1wsCggd;6JLW%R)wYCl9++i;71L7phBJ6im6ssXD_wJeF|C znt8~KjapC*S#`)^^5j}n$6yJq2hr*i>i+~F3iV}(YJzI7`v~2dj}cVcqqDquQSCsm zY*3=AxZLqnmq+!~e8Ri=RnPt7kkIn$b|cGhd9~XHdRU_Neb6JSiK<(BY~W+%NzSBj zFNR%yr$4-(mzD_L3}wj_K3VlInaR(Kry@H2LRiHuIQWtQcd(uZTz+9OKc9CO0R#lM zUWc6y@RmM7^=bEqs$*%+wD4i>q&ic>{qZ5bi$hYE-2VR|sY2MvhkPOMAw1wp6&CaJ zdH3i`hNOnS;4PILiLTV8;64Pxn5onq;elkKz7or7j)Dhw?MrS2b3$sc<`|~~)nx5p zx<4@(-~GM%?gw%AgW8Mf*`t1w8&Z>Gn{nOHK+PG9xKe5pWd8X4h+>FGO-Ze<`H52t(&c*gq#b^wMH zHZy>`@jM6xKFIXq{u+b_@EpQq-1v*HM%2xyj3NMD?ZrN)P)ATF8!zp1J!P;qe`MXFFK9#2DKr2jNEZ0mQlH z1uO_D#0$-bWEJ{+Um<$@9RE$cm2d(%Zxf$Id?jKYs0k+#*So(%+?vR7E#Y|&=bOl{ zO8gmTy)N^KzsL(>IPyXB1q?ruD46rW<+hkrxWjt^NA@OiEGE9f&G}@)ThYmby-FhH z&q`RwR2Z1FPcChkn6OVC#&#G}%){Y!W6Sh}>B!a_0nLu8}))jND;V`xS5}i)w9D^=HFP=$tmpK^!YCLfj#? zm`yOqx=%L2Cg)RT2KTY&%%<2y>jex496Wr?zJQtFB;F1a?D1?wKNR46r-h@_#B)yA z56BEw2^{Y-bYQ!qhIbL8;(Lhe#P<=c#vVjIhQS8mGsFNnj~Mr1)e^%2a}cud9_Y6q z2ruJ};9GYD*LyR3h2f*}4~S37KWbIEh3ta1CU-)Ps2E{skUvVxjnyssOZab=4U5#?%8xJhf)nu;^)lyv< z+$pFYjw|1K*9;>*siby5f%}5l4?8pYgdLEg++a+Cm05AM(Y`_(lNVqEwdr|*Y1v9L z__E_GIp$vhDUhbC)BcE(0@=FS>IfMJK%TBXaAYe3V5F|9JR_7;7^|y=o)YcDyg=+a zB@Oz>YX!;A)wTG(0?-Oy`?%Vyt9nxDIq}$F%3P_dwG|R|pk~*rZsirB| zGi^vb^IreWF_g`@aXB{Wc`*bce`mq*XA`QFbhuMj>ApEiCK&VLvH7lAB^wGzt%CQ> z^OZqRuB)i2MahT5y86WRyfPAAFW_~n;E-!As_#j)`Zl5pjD*_?d2qShV-6WdLba~` zVb0brxnLlay9>qxDFNZ>W)3WyAk53s0zRmOriBChsYwkYFa zrmjluwc3(lf!KECYIqu zhjrp8KClT!us&r9D7xB!v1#DY)vXx22|T)5B7C8gL9(tsz|7cm7^ur)IIi3ZIl6MQ zyxH1Uiw6K>Yk!{TwJ3MzE!sOd#%GyBoIMe1fHuH8}(J823T zd)SS>Ny1!~t(=zT>OIGr{w3|D%s$FIN|{F~^A+HiZJCRM2g)tNYFAkM{13VQ>2fPu z;9LQ};@}$jYh39(xl4GK`)ZK$Ctw%LPk3Cm!>jmB!wIj$ zTZo$o-$oSRU3gcPp$oPmZijBn{D?9?!>9~<;T6pPJLUIM{z1y`r~EH5Uj`f7g&1US z#qgBDK1LkE_Q*k)1KX?_P!H$KSuh<&*@og5$JLgh_;s%hF#uZ;L-<)f6mr019SXz1 zjW`O@5XZp~#B0c(f>=V`#=#7zuuh}0z8@}me+qO~DesYeGbA%km_+=Ov&oMB5F+p-dd-bmO^*hP5Qz?ao;sE=V8 zh+K%G27*l#*Fdr@jhte_a*?mPfv}CRlkhNMKOq=-4kJt>3=$R-mJ>D*HXHdu*BRG9 zuB{t+tjF4HBFn_BPSZnBZS5wfAMu=-No!!YHAq-OSV7n*@o1a$5Ik*dC#Q>?e&h$O zOy*G$vB#Pw^Th?pNhhb6oDvx`W7cvK6%=kD-blQScsozBb`tL*zn_qqxgRvsjU%ie zY$R+a?82NA)^6lI)_!tCh4V(lbLK9>SU;VZWeuFQ1})T%u*<@4M;H3Zwr+H?ZT;kf zwTbVFm1m|AFD5J@XFOuIt(=?&!bU6Kzc%9Sl+#JPn=%i_Nc2+@*r>IQZ&w=eAn{`2 zCB(a0+zc_yHRpP(=m*?&D|K9rse~Y+0&!eJc zzhuZ-?enX?aql*@uqb}ceu;-y?uDpyfK_(%PFx*w+?Dg|qwC-N_Ud&FmUHAY^IZFZ6X>zi1=turJ;% zOs>M89cJS9mndQd{{9ff50D5H&{2S@8V~Lk;$IYoK^}}iuMmnbBZ9gTtA;}rvLdXC TYPqpqp*Y&1HhjlgBhr5X`G01f From e6b800b1c93bd1236bef7c6aacd194f4de8289f7 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 26 May 2026 15:34:21 +0800 Subject: [PATCH 37/90] remove rerun from alert --- .../Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 index cc6795457cf1..e53db602ca35 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 @@ -11,11 +11,6 @@ $TenantFilter ) - #Add rerun protection: This Monitor can only run once every hour. - $Rerun = Test-CIPPRerun -TenantFilter $TenantFilter -Type 'ExchangeMonitor' -API 'Get-CIPPAlertQuarantineReleaseRequests' - if ($Rerun) { - return - } $HasLicense = Test-CIPPStandardLicense -StandardName 'QuarantineReleaseRequests' -TenantFilter $TenantFilter -Preset Exchange if (-not $HasLicense) { From 97dc672c748a5152c5a7438588f6ab6847b4ac26 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 26 May 2026 23:33:37 +0800 Subject: [PATCH 38/90] user sync --- Config/CIPPTimers.json | 9 + .../Timer Functions/Start-UserSyncTimer.ps1 | 194 ++++++++++++++++++ .../CIPP/Settings/Invoke-ExecCIPPUsers.ps1 | 30 ++- .../CIPP/Settings/Invoke-ListCIPPUsers.ps1 | 26 ++- 4 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 diff --git a/Config/CIPPTimers.json b/Config/CIPPTimers.json index 077f7d53fb0c..0802c9863096 100644 --- a/Config/CIPPTimers.json +++ b/Config/CIPPTimers.json @@ -273,5 +273,14 @@ "Priority": 30, "RunOnProcessor": false, "IsSystem": true + }, + { + "Id": "7e2a9b4c-1d5f-4a8e-b3c6-0f9d2e7a4b1c", + "Command": "Start-UserSyncTimer", + "Description": "Sync partner tenant users and group-based roles into allowedUsers table", + "Cron": "0 */15 * * * *", + "Priority": 11, + "RunOnProcessor": false, + "IsSystem": true } ] diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 new file mode 100644 index 000000000000..f4578bccecdf --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-UserSyncTimer.ps1 @@ -0,0 +1,194 @@ +function Start-UserSyncTimer { + <# + .SYNOPSIS + Sync partner tenant users into the allowedUsers table + .DESCRIPTION + Pulls users from the partner tenant via Graph, resolves their Entra group memberships + against AccessRoleGroups, and upserts into allowedUsers with auto-derived roles. + Manual role assignments are preserved in a separate column and merged at compute time. + .FUNCTIONALITY + Entrypoint + #> + + [CmdletBinding(SupportsShouldProcess = $true)] + param() + + if (-not $PSCmdlet.ShouldProcess('Start-UserSyncTimer', 'Sync partner tenant users to allowedUsers table')) { + return + } + + $ApiName = 'UserSync' + + try { + Write-LogMessage -API $ApiName -tenant 'none' -message 'Starting user sync from partner tenant.' -sev Info + + # Load the role-to-group mappings + $AccessGroupsTable = Get-CippTable -TableName AccessRoleGroups + $AccessGroups = @(Get-CIPPAzDataTableEntity @AccessGroupsTable -Filter "PartitionKey eq 'AccessRoleGroups'") + + # Get the group IDs we care about + $RoleGroupIds = @($AccessGroups | ForEach-Object { $_.GroupId } | Where-Object { $_ }) + + # Build a lookup: GroupId -> Role names (a group can map to multiple roles) + $GroupToRoles = @{} + foreach ($Mapping in $AccessGroups) { + if ($Mapping.GroupId) { + if (-not $GroupToRoles.ContainsKey($Mapping.GroupId)) { + $GroupToRoles[$Mapping.GroupId] = [System.Collections.Generic.List[string]]::new() + } + $GroupToRoles[$Mapping.GroupId].Add($Mapping.RowKey) + } + } + + # Fetch members of each role group from the partner tenant + # Use transitiveMembers to catch nested group memberships + $UserRoleMap = @{} # UPN -> HashSet of auto roles + + foreach ($GroupId in $RoleGroupIds) { + try { + $Members = @(New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/groups/$GroupId/transitiveMembers?`$select=id,userPrincipalName,mail,accountEnabled&`$top=999" -NoAuthCheck $true -AsApp $true) + $UserMembers = @($Members | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.user' -and $_.accountEnabled -eq $true }) + + $RolesForGroup = $GroupToRoles[$GroupId] + + foreach ($Member in $UserMembers) { + $Upn = $Member.userPrincipalName + if ([string]::IsNullOrWhiteSpace($Upn)) { continue } + $Upn = $Upn.Trim().ToLower() + + if (-not $UserRoleMap.ContainsKey($Upn)) { + $UserRoleMap[$Upn] = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + } + foreach ($Role in $RolesForGroup) { + [void]$UserRoleMap[$Upn].Add($Role) + } + } + } catch { + $ErrorData = Get-CippException -Exception $_ + Write-LogMessage -API $ApiName -tenant 'none' -message "Failed to fetch members of group $GroupId : $($ErrorData.NormalizedError)" -sev Warning -LogData $ErrorData + } + } + + if ($UserRoleMap.Count -eq 0 -and $RoleGroupIds.Count -gt 0) { + Write-LogMessage -API $ApiName -tenant 'none' -message 'No users found in any role groups.' -sev Info + } elseif ($RoleGroupIds.Count -eq 0) { + Write-LogMessage -API $ApiName -tenant 'none' -message 'No Entra groups mapped to roles — will clean up any stale auto-provisioned users.' -sev Info + } + + # Load existing allowedUsers table + $UsersTable = Get-CippTable -tablename 'allowedUsers' + $ExistingUsers = @(Get-CIPPAzDataTableEntity @UsersTable | Where-Object { -not $_.RowKey.StartsWith('_') }) + + # Build lookup of existing users + $ExistingLookup = @{} + foreach ($Existing in $ExistingUsers) { + $ExistingLookup[$Existing.RowKey.ToLower()] = $Existing + } + + $Now = (Get-Date).ToUniversalTime().ToString('o') + $UpsertCount = 0 + $RemoveCount = 0 + $EntitiesToUpsert = [System.Collections.Generic.List[object]]::new() + + # Upsert users from Graph + foreach ($Upn in $UserRoleMap.Keys) { + $AutoRoles = @($UserRoleMap[$Upn] | Sort-Object) + + $ManualRoles = @() + $Source = 'Auto' + + if ($ExistingLookup.ContainsKey($Upn)) { + $Existing = $ExistingLookup[$Upn] + + # Preserve manual roles if they exist + if ($Existing.ManualRoles) { + try { + $ManualRoles = @($Existing.ManualRoles | ConvertFrom-Json -ErrorAction Stop) + } catch { + $ManualRoles = @() + } + } + + # If user was previously manual-only and now also auto, mark as Both + if ($ManualRoles.Count -gt 0) { + $Source = 'Both' + } + } + + # Compute effective roles = union of auto + manual + $EffectiveRoles = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($Role in $AutoRoles) { [void]$EffectiveRoles.Add($Role) } + foreach ($Role in $ManualRoles) { [void]$EffectiveRoles.Add($Role) } + $EffectiveRolesArray = @($EffectiveRoles | Sort-Object) + + $Entity = @{ + PartitionKey = 'User' + RowKey = $Upn + Roles = [string]($EffectiveRolesArray | ConvertTo-Json -Compress -AsArray) + AutoRoles = [string]($AutoRoles | ConvertTo-Json -Compress -AsArray) + ManualRoles = [string](($ManualRoles.Count -gt 0 ? $ManualRoles : @()) | ConvertTo-Json -Compress -AsArray) + Source = $Source + LastSync = $Now + } + + $EntitiesToUpsert.Add($Entity) + $UpsertCount++ + } + + # Handle users that were auto-provisioned but are no longer in any role group + foreach ($Existing in $ExistingUsers) { + $ExistingUpn = $Existing.RowKey.ToLower() + if ($UserRoleMap.ContainsKey($ExistingUpn)) { continue } # Still in a group, already handled + + if ($Existing.Source -eq 'Auto') { + # Purely auto-provisioned user no longer in any group — remove + Remove-AzDataTableEntity -Force @UsersTable -Entity $Existing + $RemoveCount++ + } elseif ($Existing.Source -eq 'Both') { + # Was both auto + manual — clear auto roles, keep manual only + $ManualRoles = @() + if ($Existing.ManualRoles) { + try { + $ManualRoles = @($Existing.ManualRoles | ConvertFrom-Json -ErrorAction Stop) + } catch { + $ManualRoles = @() + } + } + + if ($ManualRoles.Count -gt 0) { + $Entity = @{ + PartitionKey = 'User' + RowKey = $Existing.RowKey + Roles = [string]($ManualRoles | ConvertTo-Json -Compress -AsArray) + AutoRoles = '[]' + ManualRoles = [string]($ManualRoles | ConvertTo-Json -Compress -AsArray) + Source = 'Manual' + LastSync = $Now + } + $EntitiesToUpsert.Add($Entity) + } else { + # No manual roles either — remove + Remove-AzDataTableEntity -Force @UsersTable -Entity $Existing + $RemoveCount++ + } + } + # Source = 'Manual' (or unset) — leave untouched, these are purely manual entries + } + + # Batch upsert + if ($EntitiesToUpsert.Count -gt 0) { + foreach ($Entity in $EntitiesToUpsert) { + Add-CIPPAzDataTableEntity @UsersTable -Entity $Entity -Force + } + } + + # Invalidate CRAFT's in-memory user cache so changes apply + try { [Craft.Services.AuthBridge]::InvalidateUsers() } catch {} + + Write-LogMessage -API $ApiName -tenant 'none' -message "User sync completed: $UpsertCount users synced, $RemoveCount auto-only users removed." -sev Info + + } catch { + $ErrorData = Get-CippException -Exception $_ + Write-LogMessage -API $ApiName -tenant 'none' -message "User sync failed: $($ErrorData.NormalizedError)" -sev Error -LogData $ErrorData + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 index 1220df4bad43..dfc78cd1a542 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 @@ -45,13 +45,41 @@ function Invoke-ExecCIPPUsers { } } + # Check if user already exists to preserve auto-synced roles + $ExistingEntity = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$UPN'" + $AutoRoles = @() + $Source = 'Manual' + + if ($ExistingEntity -and $ExistingEntity.AutoRoles) { + try { + $AutoRoles = @($ExistingEntity.AutoRoles | ConvertFrom-Json -ErrorAction Stop) + } catch { + $AutoRoles = @() + } + if ($AutoRoles.Count -gt 0) { + $Source = 'Both' + } + } + + # Compute effective roles = union of manual + auto + $EffectiveRoles = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($R in $Roles) { [void]$EffectiveRoles.Add($R) } + foreach ($R in $AutoRoles) { [void]$EffectiveRoles.Add($R) } + $EffectiveRolesArray = @($EffectiveRoles | Sort-Object) + $Entity = @{ PartitionKey = 'User' RowKey = $UPN - Roles = [string](@($Roles) | ConvertTo-Json -Compress -AsArray) + Roles = [string]($EffectiveRolesArray | ConvertTo-Json -Compress -AsArray) + ManualRoles = [string](@($Roles) | ConvertTo-Json -Compress -AsArray) + AutoRoles = [string]($AutoRoles | ConvertTo-Json -Compress -AsArray) + Source = $Source } Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force | Out-Null + # Trigger a user sync to reconcile auto + manual roles + try { Start-UserSyncTimer } catch {} + # Invalidate the in-memory user cache so changes apply immediately try { [Craft.Services.AuthBridge]::InvalidateUsers() } catch {} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCIPPUsers.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCIPPUsers.ps1 index 5ed1dff766e2..0e60dcb07c25 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCIPPUsers.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCIPPUsers.ps1 @@ -39,9 +39,31 @@ function Invoke-ListCIPPUsers { } } + $ParsedManualRoles = @() + if ($User.ManualRoles) { + try { + $ParsedManualRoles = @($User.ManualRoles | ConvertFrom-Json -ErrorAction Stop) + } catch { + $ParsedManualRoles = @() + } + } + + $ParsedAutoRoles = @() + if ($User.AutoRoles) { + try { + $ParsedAutoRoles = @($User.AutoRoles | ConvertFrom-Json -ErrorAction Stop) + } catch { + $ParsedAutoRoles = @() + } + } + $UserList.Add([pscustomobject]@{ - UPN = $User.RowKey - Roles = $ParsedRoles + UPN = $User.RowKey + Roles = $ParsedRoles + ManualRoles = $ParsedManualRoles + AutoRoles = $ParsedAutoRoles + Source = $User.Source + LastSync = $User.LastSync }) } From 359633a42f5c208fd3b7e85f5903833e96e9c1c4 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Tue, 26 May 2026 17:58:54 +0200 Subject: [PATCH 39/90] fix: ensure tenant groups skips cache so they dont alternate anymore when refreshed --- .../Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 index 98bb7a0641e6..37c6ca5eb34f 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 @@ -24,7 +24,7 @@ Function Invoke-ListTenantDetails { $customProperties = Get-TenantProperties -customerId $TenantFilter $org | Add-Member -MemberType NoteProperty -Name 'customProperties' -Value $customProperties - $Groups = (Get-TenantGroups -TenantFilter $TenantFilter) ?? @() + $Groups = (Get-TenantGroups -TenantFilter $TenantFilter -SkipCache) ?? @() $org | Add-Member -MemberType NoteProperty -Name 'Groups' -Value @($Groups) $StatusCode = [HttpStatusCode]::OK From 49d629e51778c237f1e5fb4580493e32eb93937c Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 27 May 2026 00:31:43 +0800 Subject: [PATCH 40/90] Update Get-CippApiAuth.ps1 --- Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 index 6d822746d6cf..faea057c4fa1 100644 --- a/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 @@ -41,9 +41,12 @@ function Get-CippApiAuth { } } + $ExtractedTenantId = $Issuer -replace 'https://sts.windows.net/', '' -replace 'https://login.microsoftonline.com/', '' -replace '/v2.0', '' + $TenantId = if ($ExtractedTenantId -eq 'common') { $env:TenantID } else { $ExtractedTenantId } + [PSCustomObject]@{ ApiUrl = "https://$($env:WEBSITE_HOSTNAME)" - TenantID = $Issuer -replace 'https://sts.windows.net/', '' -replace 'https://login.microsoftonline.com/', '' -replace '/v2.0', '' + TenantID = $TenantId ClientIDs = $AllowedApps Enabled = $AAD.enabled } From 22902b0b81b33f8785715fd37ad176e60afade3b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 27 May 2026 01:51:03 +0800 Subject: [PATCH 41/90] api fixes --- .../Authentication/New-CIPPAPIConfig.ps1 | 83 ++++++++++---- .../Repair-CippApiIdentifierUri.ps1 | 101 ++++++++++++++++++ .../CIPP/Settings/Invoke-ExecApiClient.ps1 | 34 ++++++ 3 files changed, 198 insertions(+), 20 deletions(-) create mode 100644 Modules/CIPPCore/Public/Authentication/Repair-CippApiIdentifierUri.ps1 diff --git a/Modules/CIPPCore/Public/Authentication/New-CIPPAPIConfig.ps1 b/Modules/CIPPCore/Public/Authentication/New-CIPPAPIConfig.ps1 index 80480d9a23ab..ebf9d8120e0e 100644 --- a/Modules/CIPPCore/Public/Authentication/New-CIPPAPIConfig.ps1 +++ b/Modules/CIPPCore/Public/Authentication/New-CIPPAPIConfig.ps1 @@ -22,6 +22,20 @@ function New-CIPPAPIConfig { if ($AppId) { $APIApp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appid='$($AppId)')" -NoAuthCheck $true -AsApp $true Write-Information "Found existing app with AppId $AppId" + + # Validate and repair identifier URI for existing apps + try { + $RepairResult = Repair-CippApiIdentifierUri -AppId $AppId -ApplicationObjectId $APIApp.id + if ($RepairResult.Fixed) { + Write-Information "Repaired identifier URI: $($RepairResult.Message)" + Write-LogMessage -headers $Headers -API $APINAME -tenant 'None' -message "Repaired identifier URI for app $AppId $($RepairResult.Message)" -Sev 'Info' + } else { + Write-Information "Identifier URI validation: $($RepairResult.Message)" + } + } catch { + Write-Warning "Failed to validate/repair identifier URI for existing app: $($_.Exception.Message)" + # Don't fail the whole operation if URI repair fails + } } else { $CreateBody = @{ api = @{ @@ -122,29 +136,58 @@ function New-CIPPAPIConfig { if (-not $APIPassword) { throw 'Failed to create application password after retries. The app may not have replicated yet; wait a moment and retry.' } - Write-Information 'Adding App URL' + + Write-Information 'Adding Application Identifier URI' $Step = 'Adding Application Identifier URI' - $IdentifierUriBody = "{`"identifierUris`":[`"api://$($APIApp.appId)`"]}" - $IdentifierUriUpdated = $false - for ($Attempt = 1; $Attempt -le 6; $Attempt++) { - try { - New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)" -AsApp $true -NoAuthCheck $true -type PATCH -body $IdentifierUriBody -maxRetries 3 | Out-Null - $IdentifierUriUpdated = $true - break - } catch { - $IsNotReplicatedYet = $_.Exception.Message -match "Resource '.*' does not exist or one of its queried reference-property objects are not present" - if ($IsNotReplicatedYet -and $Attempt -lt 6) { - $DelaySeconds = 3 - Write-Information "Application object not yet replicated for identifier URI update (attempt $Attempt of 6). Retrying in $DelaySeconds second(s)." - Start-Sleep -Seconds $DelaySeconds - try { - $APIApp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appid='$($APIApp.appId)')" -NoAuthCheck $true -AsApp $true - } catch { - Write-Information "Application lookup retry failed before identifier URI retry: $($_.Exception.Message)" + $DesiredIdentifierUri = "api://$($APIApp.appId)" + + # Check if identifier URI already exists + if ($APIApp.identifierUris -contains $DesiredIdentifierUri) { + Write-Information "Application Identifier URI '$DesiredIdentifierUri' already set, skipping." + $IdentifierUriUpdated = $true + } else { + Write-Information "Setting Application Identifier URI to '$DesiredIdentifierUri'" + $IdentifierUriBody = @{ + identifierUris = @($DesiredIdentifierUri) + } + $IdentifierUriUpdated = $false + for ($Attempt = 1; $Attempt -le 6; $Attempt++) { + try { + New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($APIApp.id)" -AsApp $true -NoAuthCheck $true -type PATCH -body $IdentifierUriBody -maxRetries 3 | Out-Null + Write-Information "Successfully set identifier URI on attempt $Attempt" + $IdentifierUriUpdated = $true + break + } catch { + $ErrorMsg = $_.Exception.Message + Write-Information "Identifier URI update attempt $Attempt failed: $ErrorMsg" + + $IsNotReplicatedYet = $ErrorMsg -match "Resource '.*' does not exist or one of its queried reference-property objects are not present" + $IsConflict = $ErrorMsg -match "Another object with the same value for property identifierUris already exists" -or $ErrorMsg -match "Property identifierUris is invalid" + + if ($IsConflict) { + Write-Warning "Identifier URI conflict detected: $ErrorMsg" + Write-Information "This may indicate the URI is already in use by another application or the app registration needs manual cleanup." + throw } - continue + + if ($IsNotReplicatedYet -and $Attempt -lt 6) { + $DelaySeconds = 3 + Write-Information "Application object not yet replicated for identifier URI update (attempt $Attempt of 6). Retrying in $DelaySeconds second(s)." + Start-Sleep -Seconds $DelaySeconds + try { + $APIApp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appid='$($APIApp.appId)')" -NoAuthCheck $true -AsApp $true + Write-Information "Re-fetched app: identifierUris=$($APIApp.identifierUris -join ', ')" + } catch { + Write-Information "Application lookup retry failed before identifier URI retry: $($_.Exception.Message)" + } + continue + } + + if ($Attempt -ge 6) { + Write-Warning "Failed to set identifier URI after $Attempt attempts. Last error: $ErrorMsg" + } + throw } - throw } } diff --git a/Modules/CIPPCore/Public/Authentication/Repair-CippApiIdentifierUri.ps1 b/Modules/CIPPCore/Public/Authentication/Repair-CippApiIdentifierUri.ps1 new file mode 100644 index 000000000000..1f557db756ee --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/Repair-CippApiIdentifierUri.ps1 @@ -0,0 +1,101 @@ +function Repair-CippApiIdentifierUri { + <# + .SYNOPSIS + Validates and repairs the Application ID URI (api://{appId}) for a CIPP API client + .DESCRIPTION + Checks if an application has the correct identifier URI set (api://{appId}) and fixes it if missing or incorrect. + This is required for client_credentials (app-only) authentication to work properly with EasyAuth. + .PARAMETER AppId + The Application (Client) ID of the app to check/repair + .PARAMETER ApplicationObjectId + Optional. The object ID of the application. If not provided, will be looked up. + .EXAMPLE + Repair-CippApiIdentifierUri -AppId '12345678-1234-1234-1234-123456789012' + .OUTPUTS + PSCustomObject with properties: + - Fixed: boolean indicating if a fix was applied + - PreviousUri: the previous identifier URI (if any) + - CurrentUri: the current/fixed identifier URI + - Message: description of what happened + #> + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory = $true)] + [string]$AppId, + + [Parameter(Mandatory = $false)] + [string]$ApplicationObjectId + ) + + try { + # Get the application details + $App = if ($ApplicationObjectId) { + New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications/$ApplicationObjectId" -NoAuthCheck $true -AsApp $true -ErrorAction Stop + } else { + $Apps = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications?`$filter=appId eq '$AppId'&`$select=id,appId,identifierUris" -NoAuthCheck $true -AsApp $true -ErrorAction Stop + if ($Apps -is [array] -and $Apps.Count -gt 0) { + $Apps[0] + } elseif ($Apps.id) { + $Apps + } else { + throw "Application with AppId '$AppId' not found" + } + } + + $DesiredUri = "api://$($App.appId)" + $CurrentUris = @($App.identifierUris) + + Write-Information "Application '$($App.appId)': Current identifier URIs: $($CurrentUris -join ', ')" + + # Check if the desired URI is already present + if ($CurrentUris -contains $DesiredUri) { + return [PSCustomObject]@{ + Fixed = $false + PreviousUri = $CurrentUris -join ', ' + CurrentUri = $DesiredUri + Message = "Identifier URI '$DesiredUri' already correctly configured" + } + } + + # Need to add/fix the URI + Write-Information "Identifier URI missing or incorrect. Setting to '$DesiredUri'" + + if ($PSCmdlet.ShouldProcess($App.appId, "Set identifier URI to '$DesiredUri'")) { + $PatchBody = @{ + identifierUris = @($DesiredUri) + } + + $Retries = 0 + $MaxRetries = 3 + $Success = $false + + while (-not $Success -and $Retries -lt $MaxRetries) { + try { + $Retries++ + New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($App.id)" -AsApp $true -NoAuthCheck $true -type PATCH -body $PatchBody -maxRetries 1 | Out-Null + $Success = $true + Write-Information "Successfully set identifier URI on attempt $Retries" + } catch { + $ErrorMsg = $_.Exception.Message + Write-Warning "Attempt $Retries to set identifier URI failed: $ErrorMsg" + + if ($Retries -lt $MaxRetries) { + Start-Sleep -Seconds 2 + } else { + throw "Failed to set identifier URI after $MaxRetries attempts: $ErrorMsg" + } + } + } + + return [PSCustomObject]@{ + Fixed = $true + PreviousUri = $CurrentUris -join ', ' + CurrentUri = $DesiredUri + Message = "Identifier URI successfully set to '$DesiredUri'" + } + } + } catch { + Write-Warning "Failed to repair identifier URI for AppId '$AppId': $($_.Exception.Message)" + throw + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 index 8315d00c1ca6..24eeb46c9b27 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecApiClient.ps1 @@ -228,6 +228,40 @@ function Invoke-ExecApiClient { } $Body = @($Results) } + 'RepairUri' { + $Client = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$($Request.Body.ClientId)'" + if (!$Client) { + $Results = @{ + resultText = 'API client not found' + state = 'error' + } + } else { + try { + $RepairResult = Repair-CippApiIdentifierUri -AppId $Request.Body.ClientId + + if ($RepairResult.Fixed) { + Write-LogMessage -headers $Request.Headers -API 'ExecApiClient' -message "Repaired identifier URI for $($Client.AppName) $($RepairResult.Message)" -Sev 'Info' + $Results = @{ + resultText = "Identifier URI fixed for $($Client.AppName). $($RepairResult.Message)" + state = 'success' + } + } else { + $Results = @{ + resultText = "Identifier URI already correct for $($Client.AppName). $($RepairResult.Message)" + state = 'info' + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -headers $Request.Headers -API 'ExecApiClient' -message "Failed to repair identifier URI for $($Client.AppName) $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + $Results = @{ + resultText = "Failed to repair identifier URI for $($Client.AppName) $($ErrorMessage.NormalizedError)" + state = 'error' + } + } + } + $Body = @($Results) + } 'Delete' { try { if ($Request.Body.ClientId) { From 95d48d1fe90a2e57c7459afa1330290ef659ff02 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 27 May 2026 13:58:17 +0800 Subject: [PATCH 42/90] Fix for desktop activations copilot ready test --- .../Invoke-CippTestCopilotReady003.ps1 | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 index 898098726583..aa2fc5f723fa 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 @@ -46,19 +46,30 @@ function Invoke-CippTestCopilotReady003 { # For each licensed user, check if they have a desktop activation in the activation report. # Users absent from the report entirely are counted as unactivated. + # The Graph API returns activation counts nested inside userActivationCounts[] per product type, + # so we sum windows/mac across all product types to get the total desktop activation count. $NoDesktopUsers = [System.Collections.Generic.List[object]]::new() $DesktopCount = 0 foreach ($User in $LicensedUsers) { $Activation = $ActivationLookup[$User.userPrincipalName.ToLower()] - if ($Activation -and (([int]($Activation.windows ?? 0) + [int]($Activation.mac ?? 0)) -gt 0)) { + $TotalWindows = 0 + $TotalMac = 0 + $TotalAndroid = 0 + $TotalIos = 0 + if ($Activation.userActivationCounts) { + $TotalWindows = ($Activation.userActivationCounts | Measure-Object -Property windows -Sum).Sum + $TotalMac = ($Activation.userActivationCounts | Measure-Object -Property mac -Sum).Sum + $TotalAndroid = ($Activation.userActivationCounts | Measure-Object -Property android -Sum).Sum + $TotalIos = ($Activation.userActivationCounts | Measure-Object -Property ios -Sum).Sum + } + if ($Activation -and (($TotalWindows + $TotalMac) -gt 0)) { $DesktopCount++ } else { $NoDesktopUsers.Add([pscustomobject]@{ displayName = $User.displayName userPrincipalName = $User.userPrincipalName - web = if ($Activation) { $Activation.web } else { 0 } - android = if ($Activation) { $Activation.android } else { 0 } - ios = if ($Activation) { $Activation.ios } else { 0 } + android = if ($Activation) { $TotalAndroid } else { 0 } + ios = if ($Activation) { $TotalIos } else { 0 } neverActivated = ($null -eq $Activation) }) } @@ -83,7 +94,6 @@ function Invoke-CippTestCopilotReady003 { $PlatformStr = ' (never activated)' } else { $Platforms = @() - if ([int]($User.web ?? 0) -gt 0) { $Platforms += 'Web' } if ([int]($User.android ?? 0) -gt 0 -or [int]($User.ios ?? 0) -gt 0) { $Platforms += 'Mobile' } $PlatformStr = if ($Platforms) { " ($(($Platforms -join ', ')) only)" } else { ' (no activations)' } } @@ -93,7 +103,7 @@ function Invoke-CippTestCopilotReady003 { $NeverActivated = @($NoDesktopUsers | Where-Object { $_.neverActivated }).Count $Result += "**$($NoDesktopUsers.Count) users** have no desktop M365 Apps activation" if ($NeverActivated -gt 0) { $Result += " ($NeverActivated have never activated on any platform)" } - $Result += '.`n' + $Result += ".`n" } } From a6fdfe23eb387354bb0c1bd461e8d807e06dcee5 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 27 May 2026 13:59:27 +0800 Subject: [PATCH 43/90] Make all tenants list for SPO sites fast --- .../Get-CIPPSharePointSiteUsageReport.ps1 | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPSharePointSiteUsageReport.ps1 b/Modules/CIPPCore/Public/Get-CIPPSharePointSiteUsageReport.ps1 index c4f76eaaa272..89e9eb3c196d 100644 --- a/Modules/CIPPCore/Public/Get-CIPPSharePointSiteUsageReport.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPSharePointSiteUsageReport.ps1 @@ -18,23 +18,56 @@ function Get-CIPPSharePointSiteUsageReport { try { if ($TenantFilter -eq 'AllTenants') { - $AllSiteItems = Get-CIPPDbItem -TenantFilter 'allTenants' -Type 'SharePointSiteListing' - $Tenants = @($AllSiteItems | Where-Object { $_.RowKey -ne 'SharePointSiteListing-Count' } | Select-Object -ExpandProperty PartitionKey -Unique) + # Bulk-fetch all site listings and usage data in 2 queries instead of per-tenant + $AllSiteItems = @(Get-CIPPDbItem -TenantFilter 'allTenants' -Type 'SharePointSiteListing' | Where-Object { $_.RowKey -ne 'SharePointSiteListing-Count' }) + $AllUsageItems = @(Get-CIPPDbItem -TenantFilter 'allTenants' -Type 'SharePointSiteUsage' | Where-Object { $_.RowKey -ne 'SharePointSiteUsage-Count' }) $TenantList = Get-Tenants -IncludeErrors - $Tenants = $Tenants | Where-Object { $TenantList.defaultDomainName -contains $_ } + $ValidTenants = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($T in $TenantList) { [void]$ValidTenants.Add($T.defaultDomainName) } + + # Build usage lookup keyed by siteId across all tenants + $UsageBySiteId = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($UsageItem in $AllUsageItems) { + $UsageRow = $UsageItem.Data | ConvertFrom-Json -Depth 10 + if (-not [string]::IsNullOrWhiteSpace($UsageRow.siteId)) { + $UsageBySiteId[[string]$UsageRow.siteId] = $UsageRow + } + } $AllResults = [System.Collections.Generic.List[PSCustomObject]]::new() - foreach ($Tenant in $Tenants) { - try { - $TenantResults = Get-CIPPSharePointSiteUsageReport -TenantFilter $Tenant - foreach ($Result in $TenantResults) { - $Result | Add-Member -NotePropertyName 'Tenant' -NotePropertyValue $Tenant -Force - $AllResults.Add($Result) - } - } catch { - Write-LogMessage -API 'SharePointSiteUsageReport' -tenant $Tenant -message "Failed to get report for tenant: $($_.Exception.Message)" -sev Warning - } + foreach ($SiteItem in $AllSiteItems) { + $Tenant = $SiteItem.PartitionKey + if (-not $ValidTenants.Contains($Tenant)) { continue } + + $Site = $SiteItem.Data | ConvertFrom-Json -Depth 10 + if ($Site.isPersonalSite -eq $true) { continue } + + $SiteUsage = $null + [void]$UsageBySiteId.TryGetValue([string]$Site.sharepointIds.siteId, [ref]$SiteUsage) + + $StorageUsedInBytes = [double]($SiteUsage.storageUsedInBytes ?? 0) + $StorageAllocatedInBytes = [double]($SiteUsage.storageAllocatedInBytes ?? 0) + + $AllResults.Add([PSCustomObject]@{ + Tenant = $Tenant + siteId = $Site.sharepointIds.siteId + webId = $Site.sharepointIds.webId + createdDateTime = $Site.createdDateTime + displayName = $Site.displayName + webUrl = $Site.webUrl + ownerDisplayName = $SiteUsage.ownerDisplayName + ownerPrincipalName = $SiteUsage.ownerPrincipalName + lastActivityDate = $SiteUsage.lastActivityDate + fileCount = $SiteUsage.fileCount + storageUsedInGigabytes = [math]::round($StorageUsedInBytes / 1GB, 2) + storageAllocatedInGigabytes = [math]::round($StorageAllocatedInBytes / 1GB, 2) + storageUsedInBytes = $SiteUsage.storageUsedInBytes + storageAllocatedInBytes = $SiteUsage.storageAllocatedInBytes + rootWebTemplate = $SiteUsage.rootWebTemplate + reportRefreshDate = $SiteUsage.reportRefreshDate + AutoMapUrl = $Site.AutoMapUrl + }) } return $AllResults } From 122aec8f757ac9e96e515b19cd5965c7d8f3e5d3 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 27 May 2026 15:00:16 +0800 Subject: [PATCH 44/90] fix for template id casing --- .../HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 index 0cd410faaa5a..70f8593e74f1 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 @@ -13,7 +13,7 @@ function Invoke-ExecStandardsRun { $TenantFilter = $Request.Query.tenantFilter ?? 'allTenants' - $TemplateId = $Request.Query.templateId ?? '*' + $TemplateId = $Request.Query.templateId ?? $Request.Query.TemplateId ?? '*' $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'StandardsTemplateV2'" $Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter | Sort-Object TimeStamp).JSON | ForEach-Object { From 7d3b480edb21d46cea9ae0d21196eab2c670b358 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 27 May 2026 16:29:11 +0800 Subject: [PATCH 45/90] Update Invoke-CIPPStandardDefenderCompliancePolicy.ps1 --- .../Invoke-CIPPStandardDefenderCompliancePolicy.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefenderCompliancePolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefenderCompliancePolicy.ps1 index f46791387453..86c475fa28b5 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefenderCompliancePolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefenderCompliancePolicy.ps1 @@ -29,9 +29,9 @@ function Invoke-CIPPStandardDefenderCompliancePolicy { {"type":"switch","name":"standards.DefenderCompliancePolicy.allowPartnerToCollectIosPersonalCertificateMetadata","label":"Collect personal certificate metadata from iOS","defaultValue":false} {"type":"switch","name":"standards.DefenderCompliancePolicy.ConnectMac","label":"Connect macOS devices to MDE","defaultValue":false} {"type":"switch","name":"standards.DefenderCompliancePolicy.macDeviceBlockedOnMissingPartnerData","label":"Block macOS if partner data unavailable","defaultValue":false} - {"type":"switch","name":"standards.DefenderCompliancePolicy.ConnectWindows","label":"Connect Windows 10.0.15063+ to MDE","defaultValue":false} + {"type":"switch","name":"standards.DefenderCompliancePolicy.ConnectWindows","label":"Connect Windows 10.0.15063+ to MDE (Note: enabling this forces 'Block Windows if partner data unavailable' to on)","defaultValue":false} {"type":"switch","name":"standards.DefenderCompliancePolicy.windowsMobileApplicationManagementEnabled","label":"Connect Windows (MAM)","defaultValue":false} - {"type":"switch","name":"standards.DefenderCompliancePolicy.windowsDeviceBlockedOnMissingPartnerData","label":"Block Windows if partner data unavailable","defaultValue":false} + {"type":"switch","name":"standards.DefenderCompliancePolicy.windowsDeviceBlockedOnMissingPartnerData","label":"Block Windows if partner data unavailable (Note: Microsoft enforces this to on when Connect Windows 10.0.15063+ to MDE is on)","defaultValue":false} {"type":"switch","name":"standards.DefenderCompliancePolicy.BlockunsupportedOS","label":"Block unsupported OS versions","defaultValue":false} {"type":"switch","name":"standards.DefenderCompliancePolicy.AllowMEMEnforceCompliance","label":"Allow MEM enforcement of compliance","defaultValue":false} IMPACT @@ -62,7 +62,7 @@ function Invoke-CIPPStandardDefenderCompliancePolicy { allowPartnerToCollectIOSPersonalApplicationMetadata = [bool]$Settings.ConnectIosCompliance androidDeviceBlockedOnMissingPartnerData = [bool]$Settings.androidDeviceBlockedOnMissingPartnerData iosDeviceBlockedOnMissingPartnerData = [bool]$Settings.iosDeviceBlockedOnMissingPartnerData - windowsDeviceBlockedOnMissingPartnerData = [bool]$Settings.windowsDeviceBlockedOnMissingPartnerData + windowsDeviceBlockedOnMissingPartnerData = if ([bool]$Settings.ConnectWindows) { $true } else { [bool]$Settings.windowsDeviceBlockedOnMissingPartnerData } macDeviceBlockedOnMissingPartnerData = [bool]$Settings.macDeviceBlockedOnMissingPartnerData androidMobileApplicationManagementEnabled = [bool]$Settings.ConnectAndroidCompliance iosMobileApplicationManagementEnabled = [bool]$Settings.appSync From 491530194c73d6fea5256666d3b46a93ce83a74c Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 27 May 2026 16:41:48 +0800 Subject: [PATCH 46/90] use top 500 to minimise requests --- Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertNewRiskyUsers.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertNewRiskyUsers.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertNewRiskyUsers.ps1 index 108bcb9f416f..46a8033f3c88 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertNewRiskyUsers.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertNewRiskyUsers.ps1 @@ -22,7 +22,7 @@ function Get-CIPPAlertNewRiskyUsers { $RiskyUsersDelta = (Get-CIPPAzDataTableEntity @Deltatable -Filter $Filter).delta | ConvertFrom-Json -ErrorAction SilentlyContinue # Get current risky users with more detailed information - $NewDelta = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identityProtection/riskyUsers' -tenantid $TenantFilter) | Select-Object userPrincipalName, riskLevel, riskState, riskDetail, riskLastUpdatedDateTime, isProcessing, history + $NewDelta = (New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/identityProtection/riskyUsers?`$top=500' -tenantid $TenantFilter) | Select-Object userPrincipalName, riskLevel, riskState, riskDetail, riskLastUpdatedDateTime, isProcessing, history $NewDeltatoSave = $NewDelta | ConvertTo-Json -Depth 10 -Compress -ErrorAction SilentlyContinue | Out-String $DeltaEntity = @{ From c5b0e59221366cb68a8233590fbc38ee3e5321c6 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 27 May 2026 14:23:59 +0200 Subject: [PATCH 47/90] smart lockout standard --- .../Invoke-CIPPStandardSmartLockout.ps1 | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 new file mode 100644 index 000000000000..91ffd8babc82 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 @@ -0,0 +1,170 @@ +function Invoke-CIPPStandardSmartLockout { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SmartLockout + .SYNOPSIS + (Label) Configure Entra ID Smart Lockout + .DESCRIPTION + (Helptext) **Requires Entra ID P1.** Configures the Entra ID Smart Lockout settings including lockout duration, lockout threshold, and on-premises integration mode. + (DocsDescription) Configures the Entra ID Smart Lockout policy which protects against brute-force password attacks. Smart Lockout locks out bad actors who try to guess user passwords or use brute-force methods. It recognizes sign-ins from valid users and treats them differently from attackers. Settings include lockout duration (seconds), lockout threshold (failed attempts before lockout), and on-premises password protection mode (Audit or Enforced). + .NOTES + CAT + Entra (AAD) Standards + TAG + "EIDSCAPR05" + "EIDSCAPR06" + ADDEDCOMPONENT + {"type":"number","name":"standards.SmartLockout.LockoutDurationInSeconds","label":"Lockout Duration (seconds)","default":60,"required":true} + {"type":"number","name":"standards.SmartLockout.LockoutThreshold","label":"Lockout Threshold (failed attempts)","default":10,"required":true} + {"type":"switch","name":"standards.SmartLockout.EnableBannedPasswordCheckOnPremises","label":"Enable On-Premises Password Protection"} + {"type":"radio","name":"standards.SmartLockout.BannedPasswordCheckOnPremisesMode","label":"On-Premises Mode","options":[{"label":"Audit","value":"Audit"},{"label":"Enforced","value":"Enforced"}]} + IMPACT + Medium Impact + ADDEDDATE + 2025-05-27 + POWERSHELLEQUIVALENT + Get-MgBetaDirectorySetting, New-MgBetaDirectorySetting, Update-MgBetaDirectorySetting + RECOMMENDEDBY + "CIS" + REQUIREDCAPABILITIES + "AAD_PREMIUM" + "AAD_PREMIUM_P2" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param($Tenant, $Settings) + + $TestResult = Test-CIPPStandardLicense -StandardName 'SmartLockout' -TenantFilter $Tenant -Preset Entra + + if ($TestResult -eq $false) { + return $true + } + + $PasswordRuleTemplateId = '5cf42378-d67d-4f36-ba46-e8b86229381d' + + # Extract desired values from settings + $DesiredLockoutDuration = [string]($Settings.LockoutDurationInSeconds.value ?? $Settings.LockoutDurationInSeconds ?? '60') + $DesiredLockoutThreshold = [string]($Settings.LockoutThreshold.value ?? $Settings.LockoutThreshold ?? '10') + $DesiredEnableOnPrem = [string]($Settings.EnableBannedPasswordCheckOnPremises.value ?? $Settings.EnableBannedPasswordCheckOnPremises ?? 'False') + $DesiredOnPremMode = $Settings.BannedPasswordCheckOnPremisesMode.value ?? $Settings.BannedPasswordCheckOnPremisesMode ?? 'Audit' + + # Normalize boolean switch to string + if ($DesiredEnableOnPrem -eq $true -or $DesiredEnableOnPrem -eq 'true' -or $DesiredEnableOnPrem -eq 'True') { + $DesiredEnableOnPrem = 'True' + } else { + $DesiredEnableOnPrem = 'False' + } + + # Get existing directory settings for password rules + try { + $ExistingSettings = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/settings' -tenantid $Tenant | Where-Object { $_.templateId -eq $PasswordRuleTemplateId } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to get Smart Lockout settings: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return + } + + # Extract current values + if ($null -ne $ExistingSettings) { + $CurrentLockoutDuration = ($ExistingSettings.values | Where-Object { $_.name -eq 'LockoutDurationInSeconds' }).value + $CurrentLockoutThreshold = ($ExistingSettings.values | Where-Object { $_.name -eq 'LockoutThreshold' }).value + $CurrentEnableOnPrem = ($ExistingSettings.values | Where-Object { $_.name -eq 'EnableBannedPasswordCheckOnPremises' }).value + $CurrentOnPremMode = ($ExistingSettings.values | Where-Object { $_.name -eq 'BannedPasswordCheckOnPremisesMode' }).value + } + + $StateIsCorrect = $null -ne $ExistingSettings -and + $CurrentLockoutDuration -eq $DesiredLockoutDuration -and + $CurrentLockoutThreshold -eq $DesiredLockoutThreshold -and + $CurrentEnableOnPrem -eq $DesiredEnableOnPrem -and + $CurrentOnPremMode -eq $DesiredOnPremMode + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Smart Lockout is already configured correctly.' -sev Info + } else { + try { + if ($null -eq $ExistingSettings) { + # Create new directory setting with desired values + $Body = @{ + templateId = $PasswordRuleTemplateId + values = @( + @{ name = 'EnableBannedPasswordCheck'; value = 'False' } + @{ name = 'BannedPasswordList'; value = '' } + @{ name = 'LockoutDurationInSeconds'; value = $DesiredLockoutDuration } + @{ name = 'LockoutThreshold'; value = $DesiredLockoutThreshold } + @{ name = 'EnableBannedPasswordCheckOnPremises'; value = $DesiredEnableOnPrem } + @{ name = 'BannedPasswordCheckOnPremisesMode'; value = $DesiredOnPremMode } + ) + } + $JsonBody = ConvertTo-Json -Depth 10 -InputObject $Body -Compress + $null = New-GraphPostRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/settings' -Type POST -Body $JsonBody + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Smart Lockout created: Duration=$DesiredLockoutDuration, Threshold=$DesiredLockoutThreshold, OnPrem=$DesiredEnableOnPrem, Mode=$DesiredOnPremMode" -sev Info + } else { + # Update existing directory setting, preserving banned password list values + $CurrentBannedPasswordCheck = ($ExistingSettings.values | Where-Object { $_.name -eq 'EnableBannedPasswordCheck' }).value + $CurrentBannedPasswordList = ($ExistingSettings.values | Where-Object { $_.name -eq 'BannedPasswordList' }).value + + $Body = @{ + values = @( + @{ name = 'EnableBannedPasswordCheck'; value = $CurrentBannedPasswordCheck } + @{ name = 'BannedPasswordList'; value = $CurrentBannedPasswordList } + @{ name = 'LockoutDurationInSeconds'; value = $DesiredLockoutDuration } + @{ name = 'LockoutThreshold'; value = $DesiredLockoutThreshold } + @{ name = 'EnableBannedPasswordCheckOnPremises'; value = $DesiredEnableOnPrem } + @{ name = 'BannedPasswordCheckOnPremisesMode'; value = $DesiredOnPremMode } + ) + } + $JsonBody = ConvertTo-Json -Depth 10 -InputObject $Body -Compress + $null = New-GraphPostRequest -tenantid $Tenant -Uri "https://graph.microsoft.com/beta/settings/$($ExistingSettings.id)" -Type PATCH -Body $JsonBody + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Smart Lockout updated: Duration=$DesiredLockoutDuration, Threshold=$DesiredLockoutThreshold, OnPrem=$DesiredEnableOnPrem, Mode=$DesiredOnPremMode" -sev Info + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to configure Smart Lockout: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Smart Lockout is compliant.' -sev Info + } else { + $AlertObject = @{ + LockoutDurationInSeconds = $CurrentLockoutDuration ?? 'Not Configured' + LockoutThreshold = $CurrentLockoutThreshold ?? 'Not Configured' + EnableBannedPasswordCheckOnPremises = $CurrentEnableOnPrem ?? 'Not Configured' + BannedPasswordCheckOnPremisesMode = $CurrentOnPremMode ?? 'Not Configured' + DesiredLockoutDurationInSeconds = $DesiredLockoutDuration + DesiredLockoutThreshold = $DesiredLockoutThreshold + DesiredEnableOnPrem = $DesiredEnableOnPrem + DesiredOnPremMode = $DesiredOnPremMode + } + Write-StandardsAlert -message 'Smart Lockout is not configured correctly' -object $AlertObject -tenant $Tenant -standardName 'SmartLockout' -standardId $Settings.standardId + } + } + + if ($Settings.report -eq $true) { + $CurrentValue = @{ + LockoutDurationInSeconds = $CurrentLockoutDuration ?? 'Not Configured' + LockoutThreshold = $CurrentLockoutThreshold ?? 'Not Configured' + EnableBannedPasswordCheckOnPremises = $CurrentEnableOnPrem ?? 'Not Configured' + BannedPasswordCheckOnPremisesMode = $CurrentOnPremMode ?? 'Not Configured' + } + $ExpectedValue = @{ + LockoutDurationInSeconds = $DesiredLockoutDuration + LockoutThreshold = $DesiredLockoutThreshold + EnableBannedPasswordCheckOnPremises = $DesiredEnableOnPrem + BannedPasswordCheckOnPremisesMode = $DesiredOnPremMode + } + + Set-CIPPStandardsCompareField -FieldName 'standards.SmartLockout' ` + -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant + + Add-CIPPBPAField -FieldName 'SmartLockout' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} From c5a8a20739f1cc1265dbb6f0d8cf399e63df51b4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 27 May 2026 14:24:02 +0200 Subject: [PATCH 48/90] smart lockout standard --- .../Invoke-CIPPStandardSmartLockout.ps1 | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 index 91ffd8babc82..8c6237bcc290 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSmartLockout.ps1 @@ -78,10 +78,10 @@ function Invoke-CIPPStandardSmartLockout { } $StateIsCorrect = $null -ne $ExistingSettings -and - $CurrentLockoutDuration -eq $DesiredLockoutDuration -and - $CurrentLockoutThreshold -eq $DesiredLockoutThreshold -and - $CurrentEnableOnPrem -eq $DesiredEnableOnPrem -and - $CurrentOnPremMode -eq $DesiredOnPremMode + $CurrentLockoutDuration -eq $DesiredLockoutDuration -and + $CurrentLockoutThreshold -eq $DesiredLockoutThreshold -and + $CurrentEnableOnPrem -eq $DesiredEnableOnPrem -and + $CurrentOnPremMode -eq $DesiredOnPremMode if ($Settings.remediate -eq $true) { if ($StateIsCorrect) { @@ -135,14 +135,14 @@ function Invoke-CIPPStandardSmartLockout { Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Smart Lockout is compliant.' -sev Info } else { $AlertObject = @{ - LockoutDurationInSeconds = $CurrentLockoutDuration ?? 'Not Configured' - LockoutThreshold = $CurrentLockoutThreshold ?? 'Not Configured' - EnableBannedPasswordCheckOnPremises = $CurrentEnableOnPrem ?? 'Not Configured' - BannedPasswordCheckOnPremisesMode = $CurrentOnPremMode ?? 'Not Configured' - DesiredLockoutDurationInSeconds = $DesiredLockoutDuration - DesiredLockoutThreshold = $DesiredLockoutThreshold - DesiredEnableOnPrem = $DesiredEnableOnPrem - DesiredOnPremMode = $DesiredOnPremMode + LockoutDurationInSeconds = $CurrentLockoutDuration ?? 'Not Configured' + LockoutThreshold = $CurrentLockoutThreshold ?? 'Not Configured' + EnableBannedPasswordCheckOnPremises = $CurrentEnableOnPrem ?? 'Not Configured' + BannedPasswordCheckOnPremisesMode = $CurrentOnPremMode ?? 'Not Configured' + DesiredLockoutDurationInSeconds = $DesiredLockoutDuration + DesiredLockoutThreshold = $DesiredLockoutThreshold + DesiredEnableOnPrem = $DesiredEnableOnPrem + DesiredOnPremMode = $DesiredOnPremMode } Write-StandardsAlert -message 'Smart Lockout is not configured correctly' -object $AlertObject -tenant $Tenant -standardName 'SmartLockout' -standardId $Settings.standardId } @@ -150,14 +150,14 @@ function Invoke-CIPPStandardSmartLockout { if ($Settings.report -eq $true) { $CurrentValue = @{ - LockoutDurationInSeconds = $CurrentLockoutDuration ?? 'Not Configured' - LockoutThreshold = $CurrentLockoutThreshold ?? 'Not Configured' + LockoutDurationInSeconds = $CurrentLockoutDuration ?? 'Not Configured' + LockoutThreshold = $CurrentLockoutThreshold ?? 'Not Configured' EnableBannedPasswordCheckOnPremises = $CurrentEnableOnPrem ?? 'Not Configured' BannedPasswordCheckOnPremisesMode = $CurrentOnPremMode ?? 'Not Configured' } $ExpectedValue = @{ - LockoutDurationInSeconds = $DesiredLockoutDuration - LockoutThreshold = $DesiredLockoutThreshold + LockoutDurationInSeconds = $DesiredLockoutDuration + LockoutThreshold = $DesiredLockoutThreshold EnableBannedPasswordCheckOnPremises = $DesiredEnableOnPrem BannedPasswordCheckOnPremisesMode = $DesiredOnPremMode } From f85963b72168e37d8820de7a7f469240dc968fd0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 27 May 2026 17:03:21 +0200 Subject: [PATCH 49/90] Sharepoint management functionality. --- Modules/CIPPCore/Public/Get-CIPPSPOSite.ps1 | 62 +++++++ Modules/CIPPCore/Public/Set-CIPPSPOSite.ps1 | 75 +++++++++ Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 | 96 +++++++---- .../Public/Start-CIPPSiteVersionCleanup.ps1 | 80 +++++++++ .../Invoke-CIPPStandardSPOVersionControl.ps1 | 153 ++++++++++++++++++ 5 files changed, 436 insertions(+), 30 deletions(-) create mode 100644 Modules/CIPPCore/Public/Get-CIPPSPOSite.ps1 create mode 100644 Modules/CIPPCore/Public/Set-CIPPSPOSite.ps1 create mode 100644 Modules/CIPPCore/Public/Start-CIPPSiteVersionCleanup.ps1 create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 diff --git a/Modules/CIPPCore/Public/Get-CIPPSPOSite.ps1 b/Modules/CIPPCore/Public/Get-CIPPSPOSite.ps1 new file mode 100644 index 000000000000..3f4a118c1f9e --- /dev/null +++ b/Modules/CIPPCore/Public/Get-CIPPSPOSite.ps1 @@ -0,0 +1,62 @@ +function Get-CIPPSPOSite { + <# + .SYNOPSIS + Get SharePoint Site properties via CSOM + + .DESCRIPTION + Retrieves all SharePoint site properties from the tenant using the CSOM GetSitePropertiesFromSharePoint method. + Returns all site properties including version policy settings. + + .PARAMETER TenantFilter + Tenant to query + + .EXAMPLE + Get-CIPPSPOSite -TenantFilter 'contoso.onmicrosoft.com' + + .FUNCTIONALITY + Internal + + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$TenantFilter + ) + + $SharePointInfo = Get-SharePointAdminLink -Public $false -tenantFilter $TenantFilter + $AdminUrl = $SharePointInfo.AdminUrl + + $XML = @' +false +'@ + + $AdditionalHeaders = @{ + 'Accept' = 'application/json;odata=verbose' + } + + $AllSites = [System.Collections.Generic.List[object]]::new() + $StartIndex = $null + + do { + if ($null -ne $StartIndex) { + $XML = @" +$([System.Security.SecurityElement]::Escape($StartIndex))false +"@ + } + + $Results = New-GraphPostRequest -scope "$AdminUrl/.default" -tenantid $TenantFilter -Uri "$AdminUrl/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders + + # The response contains multiple objects; find the one with _Child_Items_ (site list) and NextStartIndexFromSharePoint + $SiteCollection = $Results | Where-Object { $_._Child_Items_ } + if ($SiteCollection) { + foreach ($Site in $SiteCollection._Child_Items_) { + [void]$AllSites.Add($Site) + } + $StartIndex = $SiteCollection.NextStartIndexFromSharePoint + } else { + $StartIndex = $null + } + } while ($StartIndex) + + return $AllSites +} diff --git a/Modules/CIPPCore/Public/Set-CIPPSPOSite.ps1 b/Modules/CIPPCore/Public/Set-CIPPSPOSite.ps1 new file mode 100644 index 000000000000..4af8e4f6b55a --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPSPOSite.ps1 @@ -0,0 +1,75 @@ +function Set-CIPPSPOSite { + <# + .SYNOPSIS + Set SharePoint Site properties via CSOM + + .DESCRIPTION + Sets properties on an individual SharePoint site using the CSOM GetSitePropertiesByUrl + SetProperty + Update pattern. + + .PARAMETER TenantFilter + Tenant to apply settings to + + .PARAMETER SiteUrl + Full URL of the SharePoint site to modify + + .PARAMETER Properties + Hashtable of site properties to change. Supported value types: Boolean, String, Int32. + + .EXAMPLE + $Properties = @{ + InheritVersionPolicyFromTenant = $false + EnableAutoExpirationVersionTrim = $false + ApplyToNewDocumentLibraries = $true + ApplyToExistingDocumentLibraries = $true + MajorVersionLimit = 50 + ExpireVersionsAfterDays = 365 + } + Set-CIPPSPOSite -TenantFilter 'contoso.onmicrosoft.com' -SiteUrl 'https://contoso.sharepoint.com/sites/MySite' -Properties $Properties + + .FUNCTIONALITY + Internal + + #> + [CmdletBinding(SupportsShouldProcess = $true)] + param( + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + [Parameter(Mandatory = $true)] + [string]$SiteUrl, + [Parameter(Mandatory = $true)] + [hashtable]$Properties + ) + + $SharePointInfo = Get-SharePointAdminLink -Public $false -tenantFilter $TenantFilter + $AdminUrl = $SharePointInfo.AdminUrl + + $AllowedTypes = @('Boolean', 'String', 'Int32') + $SetProperty = [System.Collections.Generic.List[string]]::new() + $x = 106 + foreach ($Property in $Properties.Keys) { + $PropertyType = $Properties[$Property].GetType().Name + if ($PropertyType -in $AllowedTypes) { + $PropertyToSet = if ($PropertyType -eq 'Boolean') { $Properties[$Property].ToString().ToLower() } else { $Properties[$Property] } + $SetProperty.Add("$PropertyToSet") + $x++ + } + } + + if ($SetProperty.Count -eq 0) { + Write-Error 'No valid properties found' + return + } + + # CSOM pattern: Tenant Constructor → GetSitePropertiesByUrl → SetProperty(s) → Update + $XML = @" +$($SetProperty -join '')$([System.Security.SecurityElement]::Escape($SiteUrl))false +"@ + + $AdditionalHeaders = @{ + 'Accept' = 'application/json;odata=verbose' + } + + if ($PSCmdlet.ShouldProcess($SiteUrl, 'Set Site Properties')) { + New-GraphPostRequest -scope "$AdminUrl/.default" -tenantid $TenantFilter -Uri "$AdminUrl/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders + } +} diff --git a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 index ea3e9ded3851..a7d79797147f 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1 @@ -1,10 +1,10 @@ function Set-CIPPSPOTenant { <# .SYNOPSIS - Set SharePoint Tenant properties + Set SharePoint Tenant properties or invoke methods .DESCRIPTION - Set SharePoint Tenant properties via SPO API + Set SharePoint Tenant properties via SPO CSOM API, or invoke a CSOM method on the Tenant object. .PARAMETER TenantFilter Tenant to apply settings to @@ -13,7 +13,14 @@ function Set-CIPPSPOTenant { Tenant Identity (Get from Get-CIPPSPOTenant) .PARAMETER Properties - Hashtable of tenant properties to change + Hashtable of tenant properties to change (uses SetProperty actions) + + .PARAMETER MethodName + Name of the CSOM method to invoke on the Tenant object + + .PARAMETER MethodParameters + Ordered array of parameter hashtables for the method call. Each entry must have 'Type' and 'Value' keys. + Supported types: Boolean, String, Int32, Int64. .PARAMETER SharepointPrefix Prefix for the sharepoint tenant @@ -24,20 +31,35 @@ function Set-CIPPSPOTenant { } Get-CippSPOTenant -TenantFilter 'contoso.onmicrosoft.com' | Set-CIPPSPOTenant -Properties $Properties + .EXAMPLE + $MethodParams = @( + @{ Type = 'Boolean'; Value = $false } + @{ Type = 'Int32'; Value = 50 } + @{ Type = 'Int32'; Value = 365 } + ) + Get-CIPPSPOTenant -TenantFilter 'contoso.onmicrosoft.com' | Set-CIPPSPOTenant -MethodName 'SetFileVersionPolicy' -MethodParameters $MethodParams + .FUNCTIONALITY Internal #> - [CmdletBinding(SupportsShouldProcess = $true)] + [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Properties')] param( - [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'Properties')] + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'Method')] [string]$TenantFilter, - [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)] + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'Properties')] + [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, ParameterSetName = 'Method')] [Alias('_ObjectIdentity_')] [string]$Identity, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Properties')] [hashtable]$Properties, - [Parameter(ValueFromPipelineByPropertyName = $true)] + [Parameter(Mandatory = $true, ParameterSetName = 'Method')] + [string]$MethodName, + [Parameter(Mandatory = $true, ParameterSetName = 'Method')] + [array]$MethodParameters, + [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Properties')] + [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Method')] [string]$SharepointPrefix ) @@ -51,42 +73,56 @@ function Set-CIPPSPOTenant { $AdminUrl = "https://$($tenantName)-admin.sharepoint.com" } $Identity = $Identity -replace "`n", ' ' - $AllowedTypes = @('Boolean', 'String', 'Int32') - $SetProperty = [System.Collections.Generic.List[string]]::new() - $x = 114 - foreach ($Property in $Properties.Keys) { - # Get property type - $PropertyType = $Properties[$Property].GetType().Name - if ($PropertyType -in $AllowedTypes) { - if ($PropertyType -eq 'Boolean') { - $PropertyToSet = $Properties[$Property].ToString().ToLower() - } else { - $PropertyToSet = $Properties[$Property] - } - $xml = @" + + if ($PSCmdlet.ParameterSetName -eq 'Properties') { + $AllowedTypes = @('Boolean', 'String', 'Int32') + $SetProperty = [System.Collections.Generic.List[string]]::new() + $x = 114 + foreach ($Property in $Properties.Keys) { + # Get property type + $PropertyType = $Properties[$Property].GetType().Name + if ($PropertyType -in $AllowedTypes) { + if ($PropertyType -eq 'Boolean') { + $PropertyToSet = $Properties[$Property].ToString().ToLower() + } else { + $PropertyToSet = $Properties[$Property] + } + $xml = @" $($PropertyToSet) "@ - $SetProperty.Add($xml) - $x++ + $SetProperty.Add($xml) + $x++ + } + } + + if (($SetProperty | Measure-Object).Count -eq 0) { + Write-Error 'No valid properties found' + return } - } - if (($SetProperty | Measure-Object).Count -eq 0) { - Write-Error 'No valid properties found' - return + $ActionsXml = $SetProperty -join '' + $Description = $Properties.Keys -join ', ' + } else { + # Method call + $Params = foreach ($Param in $MethodParameters) { + $ParamValue = if ($Param.Type -eq 'Boolean') { $Param.Value.ToString().ToLower() } else { $Param.Value } + "$ParamValue" + } + $ActionsXml = "$($Params -join '')" + $Description = $MethodName } - # Query tenant settings + # Build CSOM request $XML = @" - $($SetProperty -join '') + $ActionsXml "@ $AdditionalHeaders = @{ 'Accept' = 'application/json;odata=verbose' } - if ($PSCmdlet.ShouldProcess(($Properties.Keys -join ', '), 'Set Tenant Properties')) { + if ($PSCmdlet.ShouldProcess($Description, 'Set Tenant Properties')) { New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders # Invalidate cached tenant data so subsequent reads reflect the change diff --git a/Modules/CIPPCore/Public/Start-CIPPSiteVersionCleanup.ps1 b/Modules/CIPPCore/Public/Start-CIPPSiteVersionCleanup.ps1 new file mode 100644 index 000000000000..ef9e032aa9f6 --- /dev/null +++ b/Modules/CIPPCore/Public/Start-CIPPSiteVersionCleanup.ps1 @@ -0,0 +1,80 @@ +function Start-CIPPSiteVersionCleanup { + <# + .SYNOPSIS + Start a file version batch delete job for a SharePoint site + + .DESCRIPTION + Creates a new file version batch delete job via the CSOM NewFileVersionBatchDeleteJob method. + This triggers cleanup of old file versions on a SharePoint site based on the specified parameters. + + .PARAMETER TenantFilter + Tenant to run the cleanup on + + .PARAMETER SiteUrl + Full URL of the SharePoint site to clean up + + .PARAMETER BatchDeleteMode + Cleanup mode as an enum value: + 0 = DeleteOlderThanDays + 1 = CountLimits + 2 = SyncPolicy (apply the site's current version policy) + + .PARAMETER DeleteOlderThanDays + Delete versions older than this many days. Use -1 to skip (when using SyncPolicy mode). + + .PARAMETER MajorVersionLimit + Maximum major versions to keep. Use -1 to skip (when using SyncPolicy mode). + + .PARAMETER MajorWithMinorVersionsLimit + Maximum major versions that retain minor versions. Use -1 to skip (when using SyncPolicy mode). + + .PARAMETER SyncListPolicy + Whether to sync the list-level policy. Defaults to $false. + + .EXAMPLE + # Sync site policy (apply current version settings to all existing versions) + Start-CIPPSiteVersionCleanup -TenantFilter 'contoso.onmicrosoft.com' -SiteUrl 'https://contoso.sharepoint.com/sites/MySite' -BatchDeleteMode 2 + + .EXAMPLE + # Delete versions older than 365 days + Start-CIPPSiteVersionCleanup -TenantFilter 'contoso.onmicrosoft.com' -SiteUrl 'https://contoso.sharepoint.com/sites/MySite' -BatchDeleteMode 0 -DeleteOlderThanDays 365 + + .FUNCTIONALITY + Internal + + #> + [CmdletBinding(SupportsShouldProcess = $true)] + param( + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + [Parameter(Mandatory = $true)] + [string]$SiteUrl, + [Parameter(Mandatory = $false)] + [int]$BatchDeleteMode = 2, + [Parameter(Mandatory = $false)] + [int]$DeleteOlderThanDays = -1, + [Parameter(Mandatory = $false)] + [int]$MajorVersionLimit = -1, + [Parameter(Mandatory = $false)] + [int]$MajorWithMinorVersionsLimit = -1, + [Parameter(Mandatory = $false)] + [bool]$SyncListPolicy = $false + ) + + $SharePointInfo = Get-SharePointAdminLink -Public $false -tenantFilter $TenantFilter + $AdminUrl = $SharePointInfo.AdminUrl + $EscapedSiteUrl = [System.Security.SecurityElement]::Escape($SiteUrl) + $SyncListPolicyValue = $SyncListPolicy.ToString().ToLower() + + $XML = @" +$EscapedSiteUrl$BatchDeleteMode$DeleteOlderThanDays$MajorVersionLimit$MajorWithMinorVersionsLimit$SyncListPolicyValue +"@ + + $AdditionalHeaders = @{ + 'Accept' = 'application/json;odata=verbose' + } + + if ($PSCmdlet.ShouldProcess($SiteUrl, 'Start file version batch delete job')) { + return New-GraphPostRequest -scope "$AdminUrl/.default" -tenantid $TenantFilter -Uri "$AdminUrl/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders + } +} diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 new file mode 100644 index 000000000000..fec828fc82ad --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 @@ -0,0 +1,153 @@ +function Invoke-CIPPStandardSPOVersionControl { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) SPOVersionControl + .SYNOPSIS + (Label) Set SharePoint File Version Limits + .DESCRIPTION + (Helptext) Configures SharePoint Online file versioning to either use automatic version trimming managed by Microsoft, or enforce a fixed major version limit with optional version expiration. + (DocsDescription) Configures the SharePoint Online tenant-level file versioning policy. When automatic version trimming is enabled, Microsoft intelligently manages version cleanup. When disabled, you can set a fixed maximum number of major versions to retain and optionally expire versions after a specified number of days. This helps manage storage consumption while preserving version history as needed. + .NOTES + CAT + SharePoint Standards + TAG + EXECUTIVETEXT + Controls how SharePoint Online manages file version history at the tenant level. Automatic trimming lets Microsoft optimize storage by cleaning up old versions intelligently. Manual limits give administrators precise control over the maximum number of versions retained and their expiration, ensuring predictable storage usage and compliance with data retention policies. + ADDEDCOMPONENT + {"type":"switch","name":"standards.SPOVersionControl.EnableAutoTrim","label":"Enable Automatic Version Trimming (Microsoft managed)"} + {"type":"number","name":"standards.SPOVersionControl.MajorVersionLimit","label":"Maximum Major Versions (when auto trim is off)","default":50} + {"type":"number","name":"standards.SPOVersionControl.ExpireVersionsAfterDays","label":"Expire Versions After Days (0 = never, when auto trim is off)","default":0} + {"type":"switch","name":"standards.SPOVersionControl.ApplyToExistingSites","label":"Apply to all existing sites and document libraries"} + IMPACT + Medium Impact + ADDEDDATE + 2026-05-27 + POWERSHELLEQUIVALENT + Set-SPOTenant -EnableAutoExpirationVersionTrim $true or Set-SPOTenant -EnableAutoExpirationVersionTrim $false -MajorVersionLimit 50 -ExpireVersionsAfterDays 365 + RECOMMENDEDBY + "CIPP" + REQUIREDCAPABILITIES + "SHAREPOINTWAC" + "SHAREPOINTSTANDARD" + "SHAREPOINTENTERPRISE" + "SHAREPOINTENTERPRISE_EDU" + "SHAREPOINTENTERPRISE_GOV" + "ONEDRIVE_BASIC" + "ONEDRIVE_ENTERPRISE" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param($Tenant, $Settings) + $TestResult = Test-CIPPStandardLicense -StandardName 'SPOVersionControl' -TenantFilter $Tenant -Preset SharePoint + + if ($TestResult -eq $false) { + return $true + } + + $DesiredAutoTrim = [bool]$Settings.EnableAutoTrim + $DesiredMajorVersionLimit = [int]($Settings.MajorVersionLimit ?? 50) + $DesiredExpireVersionsAfterDays = [int]($Settings.ExpireVersionsAfterDays ?? 0) + + try { + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, EnableAutoExpirationVersionTrim, MajorVersionLimit, ExpireVersionsAfterDays + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Could not get the SPOVersionControl state for $Tenant. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return + } + + if ($DesiredAutoTrim) { + $StateIsCorrect = $CurrentState.EnableAutoExpirationVersionTrim -eq $true + } else { + $StateIsCorrect = ($CurrentState.EnableAutoExpirationVersionTrim -eq $false) -and + ($CurrentState.MajorVersionLimit -eq $DesiredMajorVersionLimit) -and + ($CurrentState.ExpireVersionsAfterDays -eq $DesiredExpireVersionsAfterDays) + } + + $CurrentValue = [PSCustomObject]@{ + EnableAutoExpirationVersionTrim = $CurrentState.EnableAutoExpirationVersionTrim + MajorVersionLimit = $CurrentState.MajorVersionLimit + ExpireVersionsAfterDays = $CurrentState.ExpireVersionsAfterDays + } + $ExpectedValue = [PSCustomObject]@{ + EnableAutoExpirationVersionTrim = $DesiredAutoTrim + MajorVersionLimit = if ($DesiredAutoTrim) { $null } else { $DesiredMajorVersionLimit } + ExpireVersionsAfterDays = if ($DesiredAutoTrim) { $null } else { $DesiredExpireVersionsAfterDays } + } + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'SharePoint version control settings are already configured correctly' -sev Info + } else { + try { + # SetFileVersionPolicy method: params are (Boolean isAutoTrimEnabled, Int32 majorVersionLimit, Int32 expireVersionsAfterDays) + # When auto trim is on, pass -1 for the version/expiry params + if ($DesiredAutoTrim) { + $MethodParams = @( + @{ Type = 'Boolean'; Value = $true } + @{ Type = 'Int32'; Value = -1 } + @{ Type = 'Int32'; Value = -1 } + ) + } else { + $MethodParams = @( + @{ Type = 'Boolean'; Value = $false } + @{ Type = 'Int32'; Value = $DesiredMajorVersionLimit } + @{ Type = 'Int32'; Value = $DesiredExpireVersionsAfterDays } + ) + } + $CurrentState | Set-CIPPSPOTenant -MethodName 'SetFileVersionPolicy' -MethodParameters $MethodParams + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully configured SharePoint version control (AutoTrim: $DesiredAutoTrim, MajorVersionLimit: $DesiredMajorVersionLimit, ExpireVersionsAfterDays: $DesiredExpireVersionsAfterDays)" -sev Info + + # Apply to all existing sites and their document libraries + if ($Settings.ApplyToExistingSites -eq $true) { + $Sites = @(New-GraphGetRequest -uri "https://graph.microsoft.com/beta/sites/getAllSites?`$select=webUrl&`$top=999" -tenantid $Tenant -AsApp $true) + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Applying version policy to $($Sites.Count) existing sites" -sev Info + + $SiteProperties = @{ + InheritVersionPolicyFromTenant = $true + EnableAutoExpirationVersionTrim = $DesiredAutoTrim + ApplyToNewDocumentLibraries = $true + ApplyToExistingDocumentLibraries = $true + } + if (-not $DesiredAutoTrim) { + $SiteProperties.MajorVersionLimit = $DesiredMajorVersionLimit + $SiteProperties.ExpireVersionsAfterDays = $DesiredExpireVersionsAfterDays + } + + foreach ($Site in $Sites) { + try { + Set-CIPPSPOSite -TenantFilter $Tenant -SiteUrl $Site.webUrl -Properties $SiteProperties + } catch { + write-host "Failed to set version policy for site $($Site.webUrl). Exception" -ForegroundColor Red + $SiteError = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set version policy for site $($Site.webUrl): $($SiteError.NormalizedError)" -sev Error -LogData $SiteError + } + } + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Finished applying version policy to existing sites" -sev Info + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set SharePoint version control settings. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'SharePoint version control settings are configured correctly' -sev Info + } else { + $Message = "SharePoint version control is not configured correctly. Current: AutoTrim=$($CurrentState.EnableAutoExpirationVersionTrim), MajorVersionLimit=$($CurrentState.MajorVersionLimit), ExpireVersionsAfterDays=$($CurrentState.ExpireVersionsAfterDays). Expected: AutoTrim=$DesiredAutoTrim, MajorVersionLimit=$DesiredMajorVersionLimit, ExpireVersionsAfterDays=$DesiredExpireVersionsAfterDays" + Write-StandardsAlert -message $Message -object $CurrentState -tenant $Tenant -standardName 'SPOVersionControl' -standardId $Settings.standardId + } + } + + if ($Settings.report -eq $true) { + Set-CIPPStandardsCompareField -FieldName 'standards.SPOVersionControl' -CurrentValue $CurrentValue -ExpectedValue $ExpectedValue -TenantFilter $Tenant + Add-CIPPBPAField -FieldName 'SPOVersionControl' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + } +} From b7d4f5e2ca653947ec0ffb8f6403f1ba7407eb6b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 27 May 2026 17:03:26 +0200 Subject: [PATCH 50/90] Sharepoint management functionality. --- .../Standards/Invoke-CIPPStandardSPOVersionControl.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 index fec828fc82ad..816f79cee071 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 @@ -54,7 +54,7 @@ function Invoke-CIPPStandardSPOVersionControl { $DesiredExpireVersionsAfterDays = [int]($Settings.ExpireVersionsAfterDays ?? 0) try { - $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, EnableAutoExpirationVersionTrim, MajorVersionLimit, ExpireVersionsAfterDays + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, EnableAutoExpirationVersionTrim, MajorVersionLimit, ExpireVersionsAfterDays } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Could not get the SPOVersionControl state for $Tenant. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage @@ -123,12 +123,12 @@ function Invoke-CIPPStandardSPOVersionControl { try { Set-CIPPSPOSite -TenantFilter $Tenant -SiteUrl $Site.webUrl -Properties $SiteProperties } catch { - write-host "Failed to set version policy for site $($Site.webUrl). Exception" -ForegroundColor Red + Write-Host "Failed to set version policy for site $($Site.webUrl). Exception" -ForegroundColor Red $SiteError = Get-CippException -Exception $_ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set version policy for site $($Site.webUrl): $($SiteError.NormalizedError)" -sev Error -LogData $SiteError } } - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Finished applying version policy to existing sites" -sev Info + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Finished applying version policy to existing sites' -sev Info } } catch { $ErrorMessage = Get-CippException -Exception $_ From b7c72180ac5963a3c41211bc2a14e1a9a6bce796 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Wed, 27 May 2026 17:11:35 +0200 Subject: [PATCH 51/90] fix: update terminology from "Temporary Access Password" to "Temporary Access Pass" Fixes #6060 --- Config/standards.json | 107 ++++++++++++++++++------ Modules/CIPPCore/Public/New-CIPPTAP.ps1 | 4 +- 2 files changed, 84 insertions(+), 27 deletions(-) diff --git a/Config/standards.json b/Config/standards.json index ce9752a56d19..1e2ceba142dc 100644 --- a/Config/standards.json +++ b/Config/standards.json @@ -344,7 +344,12 @@ { "name": "standards.DisableBasicAuthSMTP", "cat": "Global Standards", - "tag": ["CIS M365 5.0 (6.5.4)", "NIST CSF 2.0 (PR.IR-01)", "ZTNA21799", "CISAMSEXO51"], + "tag": [ + "CIS M365 5.0 (6.5.4)", + "NIST CSF 2.0 (PR.IR-01)", + "ZTNA21799", + "CISAMSEXO51" + ], "helpText": "Disables SMTP AUTH organization-wide, impacting POP and IMAP clients that rely on SMTP for sending emails. Default for new tenants. For more information, see the [Microsoft documentation](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission)", "docsDescription": "Disables tenant-wide SMTP basic authentication, including for all explicitly enabled users, impacting POP and IMAP clients that rely on SMTP for sending emails. For more information, see the [Microsoft documentation](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission).", "executiveText": "Disables outdated email authentication methods that are vulnerable to security attacks, forcing applications and devices to use modern, more secure authentication protocols. This reduces the risk of email-based security breaches and credential theft.", @@ -410,7 +415,13 @@ { "name": "standards.AuthMethodsSettings", "cat": "Entra (AAD) Standards", - "tag": ["EIDSCA.AG01", "EIDSCA.AG02", "EIDSCA.AG03", "EIDSCAAG02", "EIDSCAAG03"], + "tag": [ + "EIDSCA.AG01", + "EIDSCA.AG02", + "EIDSCA.AG03", + "EIDSCAAG02", + "EIDSCAAG03" + ], "helpText": "Configures the report suspicious activity settings and system credential preferences in the authentication methods policy.", "docsDescription": "Controls the authentication methods policy settings for reporting suspicious activity and system credential preferences. These settings help enhance the security of authentication in your organization.", "executiveText": "Configures security settings that allow users to report suspicious login attempts and manages how the system handles authentication credentials. This enhances overall security by enabling early detection of potential security threats and optimizing authentication processes.", @@ -724,7 +735,7 @@ "tag": ["ZTNA21845", "ZTNA21846", "EIDSCAAT01", "EIDSCAAT02"], "helpText": "Enables TAP and sets the default TAP lifetime to 1 hour. This configuration also allows you to select if a TAP is single use or multi-logon.", "docsDescription": "Enables Temporary Password generation for the tenant.", - "executiveText": "Enables temporary access passwords that IT administrators can generate for employees who are locked out or need emergency access to systems. These time-limited passwords provide a secure way to restore access without compromising long-term security policies.", + "executiveText": "Enables temporary access passes that IT administrators can generate for employees who are locked out or need emergency access to systems. These time-limited passwords provide a secure way to restore access without compromising long-term security policies.", "addedComponent": [ { "type": "autoComplete", @@ -744,7 +755,7 @@ ] } ], - "label": "Enable Temporary Access Passwords", + "label": "Enable Temporary Access Passes", "impact": "Low Impact", "impactColour": "info", "addedDate": "2022-03-15", @@ -833,7 +844,12 @@ { "name": "standards.DisableTenantCreation", "cat": "Entra (AAD) Standards", - "tag": ["CIS M365 5.0 (1.2.3)", "CISA (MS.AAD.6.1v1)", "ZTNA21772", "ZTNA21787"], + "tag": [ + "CIS M365 5.0 (1.2.3)", + "CISA (MS.AAD.6.1v1)", + "ZTNA21772", + "ZTNA21787" + ], "helpText": "Restricts creation of M365 tenants to the Global Administrator or Tenant Creator roles.", "docsDescription": "Users by default are allowed to create M365 tenants. This disables that so only admins can create new M365 tenants.", "executiveText": "Prevents regular employees from creating new Microsoft 365 organizations, ensuring all new tenants are properly managed and controlled by IT administrators. This prevents unauthorized shadow IT environments and maintains centralized governance over Microsoft 365 resources.", @@ -1160,7 +1176,11 @@ { "name": "standards.StaleEntraDevices", "cat": "Entra (AAD) Standards", - "tag": ["Essential 8 (1501)", "NIST CSF 2.0 (ID.AM-08)", "NIST CSF 2.0 (PR.PS-03)"], + "tag": [ + "Essential 8 (1501)", + "NIST CSF 2.0 (ID.AM-08)", + "NIST CSF 2.0 (PR.PS-03)" + ], "helpText": "**Remediate is currently not available**. Cleans up Entra devices that have not connected/signed in for the specified number of days.", "docsDescription": "Remediate is currently not available. Cleans up Entra devices that have not connected/signed in for the specified number of days. First disables and later deletes the devices. More info can be found in the [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices)", "executiveText": "Automatically identifies and removes inactive devices that haven't connected to company systems for a specified period, reducing security risks from abandoned or lost devices. This maintains a clean device inventory and prevents potential unauthorized access through dormant device registrations.", @@ -1218,7 +1238,12 @@ { "name": "standards.DisableSMS", "cat": "Entra (AAD) Standards", - "tag": ["CIS M365 5.0 (2.3.5)", "EIDSCA.AS04", "NIST CSF 2.0 (PR.AA-03)", "EIDSCAAS04"], + "tag": [ + "CIS M365 5.0 (2.3.5)", + "EIDSCA.AS04", + "NIST CSF 2.0 (PR.AA-03)", + "EIDSCAAS04" + ], "helpText": "This blocks users from using SMS as an MFA method. If a user only has SMS as a MFA method, they will be unable to log in.", "docsDescription": "Disables SMS as an MFA method for the tenant. If a user only has SMS as a MFA method, they will be unable to sign in.", "executiveText": "Disables SMS text messages as a multi-factor authentication method due to security vulnerabilities like SIM swapping attacks. This forces users to adopt more secure authentication methods like authenticator apps or hardware tokens, significantly improving account security.", @@ -1233,7 +1258,12 @@ { "name": "standards.DisableVoice", "cat": "Entra (AAD) Standards", - "tag": ["CIS M365 5.0 (2.3.5)", "EIDSCA.AV01", "NIST CSF 2.0 (PR.AA-03)", "EIDSCAAV01"], + "tag": [ + "CIS M365 5.0 (2.3.5)", + "EIDSCA.AV01", + "NIST CSF 2.0 (PR.AA-03)", + "EIDSCAAV01" + ], "helpText": "This blocks users from using Voice call as an MFA method. If a user only has Voice as a MFA method, they will be unable to log in.", "docsDescription": "Disables Voice call as an MFA method for the tenant. If a user only has Voice call as a MFA method, they will be unable to sign in.", "executiveText": "Disables voice call authentication due to security vulnerabilities and social engineering risks. This forces users to adopt more secure authentication methods like authenticator apps, improving overall account security by eliminating phone-based attack vectors.", @@ -2071,7 +2101,12 @@ { "name": "standards.DisableExternalCalendarSharing", "cat": "Exchange Standards", - "tag": ["CIS M365 5.0 (1.3.3)", "exo_individualsharing", "ZTNA21803", "CISAMSEXO62"], + "tag": [ + "CIS M365 5.0 (1.3.3)", + "exo_individualsharing", + "ZTNA21803", + "CISAMSEXO62" + ], "helpText": "Disables the ability for users to share their calendar with external users. Only for the default policy, so exclusions can be made if needed.", "docsDescription": "Disables external calendar sharing for the entire tenant. This is not a widely used feature, and it's therefore unlikely that this will impact users. Only for the default policy, so exclusions can be made if needed by making a new policy and assigning it to users.", "executiveText": "Prevents employees from sharing their calendars with external parties, protecting sensitive meeting information and internal schedules from unauthorized access. This security measure helps maintain confidentiality of business activities while still allowing internal collaboration.", @@ -2106,7 +2141,11 @@ { "name": "standards.DisableAdditionalStorageProviders", "cat": "Exchange Standards", - "tag": ["CIS M365 5.0 (6.5.3)", "exo_storageproviderrestricted", "ZTNA21817"], + "tag": [ + "CIS M365 5.0 (6.5.3)", + "exo_storageproviderrestricted", + "ZTNA21817" + ], "helpText": "Disables the ability for users to open files in Outlook on the Web, from other providers such as Box, Dropbox, Facebook, Google Drive, OneDrive Personal, etc.", "docsDescription": "Disables additional storage providers in OWA. This is to prevent users from using personal storage providers like Dropbox, Google Drive, etc. Usually this has little user impact.", "executiveText": "Prevents employees from accessing personal cloud storage services like Dropbox or Google Drive through Outlook on the web, reducing data security risks and ensuring company information stays within approved corporate systems. This helps maintain data governance and prevents accidental data leaks.", @@ -2388,7 +2427,11 @@ { "name": "standards.DisableSharedMailbox", "cat": "Exchange Standards", - "tag": ["CIS M365 5.0 (1.2.2)", "CISA (MS.AAD.10.1v1)", "NIST CSF 2.0 (PR.AA-01)"], + "tag": [ + "CIS M365 5.0 (1.2.2)", + "CISA (MS.AAD.10.1v1)", + "NIST CSF 2.0 (PR.AA-01)" + ], "helpText": "Blocks login for all accounts that are marked as a shared mailbox. This is Microsoft best practice to prevent direct logons to shared mailboxes.", "docsDescription": "Shared mailboxes can be directly logged into if the password is reset, this presents a security risk as do all shared login credentials. Microsoft's recommendation is to disable the user account for shared mailboxes. It would be a good idea to review the sign-in reports to establish potential impact.", "executiveText": "Prevents direct login to shared mailbox accounts (like info@company.com), ensuring they can only be accessed through authorized users' accounts. This security measure eliminates the risk of shared passwords and unauthorized access while maintaining proper access control and audit trails.", @@ -4286,7 +4329,12 @@ { "name": "standards.SPDisallowInfectedFiles", "cat": "SharePoint Standards", - "tag": ["CIS M365 5.0 (7.3.1)", "CISA (MS.SPO.3.1v1)", "NIST CSF 2.0 (DE.CM-09)", "ZTNA21817"], + "tag": [ + "CIS M365 5.0 (7.3.1)", + "CISA (MS.SPO.3.1v1)", + "NIST CSF 2.0 (DE.CM-09)", + "ZTNA21817" + ], "helpText": "Ensure Office 365 SharePoint infected files are disallowed for download", "executiveText": "Prevents employees from downloading files that have been identified as containing malware or viruses from SharePoint and OneDrive. This security measure protects against malware distribution through file sharing while maintaining access to clean, safe documents.", "addedComponent": [], @@ -4328,7 +4376,13 @@ { "name": "standards.SPExternalUserExpiration", "cat": "SharePoint Standards", - "tag": ["CIS M365 5.0 (7.2.9)", "CISA (MS.SPO.1.5v1)", "ZTNA21803", "ZTNA21804", "ZTNA21858"], + "tag": [ + "CIS M365 5.0 (7.2.9)", + "CISA (MS.SPO.1.5v1)", + "ZTNA21803", + "ZTNA21804", + "ZTNA21858" + ], "helpText": "Ensure guest access to a site or OneDrive will expire automatically", "executiveText": "Automatically expires external user access to SharePoint sites and OneDrive after a specified period, reducing security risks from forgotten or unnecessary guest accounts. This ensures external access is regularly reviewed and maintained only when actively needed.", "addedComponent": [ @@ -4353,7 +4407,12 @@ { "name": "standards.SPEmailAttestation", "cat": "SharePoint Standards", - "tag": ["CIS M365 5.0 (7.2.10)", "CISA (MS.SPO.1.6v1)", "ZTNA21803", "ZTNA21804"], + "tag": [ + "CIS M365 5.0 (7.2.10)", + "CISA (MS.SPO.1.6v1)", + "ZTNA21803", + "ZTNA21804" + ], "helpText": "Ensure re-authentication with verification code is restricted", "executiveText": "Requires external users to periodically re-verify their identity through email verification codes when accessing SharePoint resources, adding an extra security layer for external collaboration. This helps ensure continued legitimacy of external access over time.", "addedComponent": [ @@ -4620,7 +4679,12 @@ { "name": "standards.unmanagedSync", "cat": "SharePoint Standards", - "tag": ["CIS M365 5.0 (7.2.3)", "CISA (MS.SPO.2.1v1)", "NIST CSF 2.0 (PR.AA-05)", "ZTNA24824"], + "tag": [ + "CIS M365 5.0 (7.2.3)", + "CISA (MS.SPO.2.1v1)", + "NIST CSF 2.0 (PR.AA-05)", + "ZTNA24824" + ], "helpText": "Entra P1 required. Block or limit access to SharePoint and OneDrive content from unmanaged devices (those not hybrid AD joined or compliant in Intune). These controls rely on Microsoft Entra Conditional Access policies and can take up to 24 hours to take effect.", "docsDescription": "Entra P1 required. Block or limit access to SharePoint and OneDrive content from unmanaged devices (those not hybrid AD joined or compliant in Intune). These controls rely on Microsoft Entra Conditional Access policies and can take up to 24 hours to take effect. 0 = Allow Access, 1 = Allow limited, web-only access, 2 = Block access. All information about this can be found in Microsofts documentation [here.](https://learn.microsoft.com/en-us/sharepoint/control-access-from-unmanaged-devices)", "executiveText": "Restricts access to company files from personal or unmanaged devices, ensuring corporate data can only be accessed from properly secured and monitored devices. This critical security control prevents data leaks while allowing controlled access through web browsers when necessary.", @@ -4779,7 +4843,7 @@ } ] }, - { + { "type": "switch", "name": "standards.TeamsGlobalMeetingPolicy.AllowPSTNUsersToBypassLobby", "label": "Allow dial-in users to bypass lobby" @@ -5106,10 +5170,7 @@ "condition": { "field": "standards.TeamsFederationConfiguration.DomainControl.value", "compareType": "isOneOf", - "compareValue": [ - "AllowSpecificExternal", - "BlockSpecificExternal" - ] + "compareValue": ["AllowSpecificExternal", "BlockSpecificExternal"] } } ], @@ -6205,11 +6266,7 @@ { "name": "standards.ColleagueImpersonationAlert", "cat": "Exchange Standards", - "tag": [ - "Exchange", - "Security", - "Transport Rules" - ], + "tag": ["Exchange", "Security", "Transport Rules"], "helpText": "Creates/updates 5x Exchange Online transport rules (A-E, F-J, K-O, P-T, U-Z) that prepend an HTML disclaimer banner to inbound emails where the sender display name matches a mailbox in the organisation. Accepted tenant domains are always exempt automatically. Inactive users are removed and enabled users are added. Any manually configured sender or domain exemptions already present on existing rules are preserved.", "docsDescription": "Creates five Exchange Online transport rules grouped by the first letter of user display names (A-E, F-J, K-O, P-T, U-Z). Each rule fires when an external sender's From header matches a display name in that group, prepends a configurable HTML warning banner, and skips emails from accepted organisational domains. Any manually configured sender or domain exemptions on existing rules are preserved when the standard runs. The disclaimer HTML is fully customisable via the standard settings.", "executiveText": "Protects staff from display-name impersonation attacks by injecting a visible warning banner on emails that appear to come from a colleague but originate externally. Rules are maintained automatically across all letter groups and updated whenever the standard runs.", diff --git a/Modules/CIPPCore/Public/New-CIPPTAP.ps1 b/Modules/CIPPCore/Public/New-CIPPTAP.ps1 index 120a81ff1264..e84584171d84 100644 --- a/Modules/CIPPCore/Public/New-CIPPTAP.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPTAP.ps1 @@ -49,7 +49,7 @@ function New-CIPPTAP { # Create parameter string for logging $paramString = ' with ' + ($logParts -join ', ') - Write-LogMessage -headers $Headers -API $APIName -message "Created Temporary Access Password (TAP) for $UserID$paramString" -Sev 'Info' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message "Created Temporary Access Pass (TAP) for $UserID$paramString" -Sev 'Info' -tenant $TenantFilter # Build result text with parameters $resultText = "The TAP for $UserID is $($GraphRequest.temporaryAccessPass) - This TAP is usable for the next $($GraphRequest.LifetimeInMinutes) minutes" @@ -69,7 +69,7 @@ function New-CIPPTAP { } catch { $ErrorMessage = Get-CippException -Exception $_ - $Result = "Failed to create Temporary Access Password (TAP) for $($UserID): $($ErrorMessage.NormalizedError)" + $Result = "Failed to create Temporary Access Pass (TAP) for $($UserID): $($ErrorMessage.NormalizedError)" Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage throw $Result } From bdd86024c2b7de08eacc764076ef13b66b87fcca Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 27 May 2026 20:24:44 +0200 Subject: [PATCH 52/90] Add version cleanup --- .../Invoke-ExecSPOVersionCleanup.ps1 | 43 +++++++++++++++++++ .../Invoke-CIPPStandardSPOVersionControl.ps1 | 1 - 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSPOVersionCleanup.ps1 diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSPOVersionCleanup.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSPOVersionCleanup.ps1 new file mode 100644 index 000000000000..700f5df2160e --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSPOVersionCleanup.ps1 @@ -0,0 +1,43 @@ +function Invoke-ExecSPOVersionCleanup { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Sharepoint.Site.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $TenantFilter = $Request.Body.tenantFilter + $SiteUrl = $Request.Body.SiteUrl + $BatchDeleteMode = [int]($Request.Body.BatchDeleteMode ?? 2) + $DeleteOlderThanDays = [int]($Request.Body.DeleteOlderThanDays ?? -1) + $MajorVersionLimit = [int]($Request.Body.MajorVersionLimit ?? -1) + $MajorWithMinorVersionsLimit = [int]($Request.Body.MajorWithMinorVersionsLimit ?? -1) + + try { + $Params = @{ + TenantFilter = $TenantFilter + SiteUrl = $SiteUrl + BatchDeleteMode = $BatchDeleteMode + DeleteOlderThanDays = $DeleteOlderThanDays + MajorVersionLimit = $MajorVersionLimit + MajorWithMinorVersionsLimit = $MajorWithMinorVersionsLimit + } + $null = Start-CIPPSiteVersionCleanup @Params + $Result = "Successfully started version cleanup job for $SiteUrl" + Write-LogMessage -API $APIName -tenant $TenantFilter -headers $Headers -message $Result -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -tenant $TenantFilter -headers $Headers -message "Failed to start version cleanup for $SiteUrl : $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $Result = "Failed to start version cleanup: $($ErrorMessage.NormalizedError)" + $StatusCode = [HttpStatusCode]::InternalServerError + } + + return [HttpResponseContext]@{ + StatusCode = $StatusCode ?? [HttpStatusCode]::OK + Body = @{ Results = $Result } + } +} diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 index 816f79cee071..cc43fe2c4b0b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1 @@ -123,7 +123,6 @@ function Invoke-CIPPStandardSPOVersionControl { try { Set-CIPPSPOSite -TenantFilter $Tenant -SiteUrl $Site.webUrl -Properties $SiteProperties } catch { - Write-Host "Failed to set version policy for site $($Site.webUrl). Exception" -ForegroundColor Red $SiteError = Get-CippException -Exception $_ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set version policy for site $($Site.webUrl): $($SiteError.NormalizedError)" -sev Error -LogData $SiteError } From 829ced812ed7c07f63842f63eb6d91f84e326594 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Wed, 27 May 2026 22:43:03 +0200 Subject: [PATCH 53/90] feat(mailboxes): cache mailbox and archive usage metrics Add mailbox usage enrichment to the mailbox DB cache using the Graph mailbox usage report. Also cache archive-enabled status, archive size, and archive item count for archive-enabled mailboxes. Expose ArchiveEnabled in live mailbox results without fetching live archive statistics. Fixes https://github.com/KelvinTegelaar/CIPP/issues/6061 --- .../DBCache/Set-CIPPDBCacheMailboxes.ps1 | 62 +++++++++++++++++++ .../Administration/Invoke-ListMailboxes.ps1 | 2 + 2 files changed, 64 insertions(+) diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 index 4da4e1784514..af466b9be767 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheMailboxes.ps1 @@ -26,6 +26,7 @@ function Set-CIPPDBCacheMailboxes { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching mailboxes' -sev Debug # Get mailboxes and user details in a single bulk request + $ZeroArchiveGuid = '00000000-0000-0000-0000-000000000000' $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox,ForwardingSmtpAddress,DeliverToMailboxAndForward,ForwardingAddress,HiddenFromAddressListsEnabled,ExternalDirectoryObjectId,MessageCopyForSendOnBehalfEnabled,MessageCopyForSentAsEnabled,GrantSendOnBehalfTo,PersistedCapabilities,LitigationHoldEnabled,LitigationHoldDate,LitigationHoldDuration,ComplianceTagHoldApplied,RetentionHoldEnabled,InPlaceHolds,RetentionPolicy,RemotePowerShellEnabled,Guid,Identity' $BulkRequests = @( @{ CmdletInput = @{ CmdletName = 'Get-Mailbox'; Parameters = @{} } } @@ -49,6 +50,12 @@ function Set-CIPPDBCacheMailboxes { @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, @{ Name = 'displayName'; Expression = { $_.'DisplayName' } }, @{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } }, + @{ Name = 'ArchiveEnabled'; Expression = { $_.ArchiveGuid -and $_.ArchiveGuid.ToString() -ne $ZeroArchiveGuid } }, + @{ Name = 'ArchiveSize'; Expression = { 0 } }, + @{ Name = 'ArchiveItemCount'; Expression = { 0 } }, + @{ Name = 'storageUsedInBytes'; Expression = { 0 } }, + @{ Name = 'prohibitSendReceiveQuotaInBytes'; Expression = { 0 } }, + @{ Name = 'MailboxItemCount'; Expression = { 0 } }, @{ Name = 'recipientType'; Expression = { $_.'RecipientType' } }, @{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } }, @{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } }, @@ -73,6 +80,61 @@ function Set-CIPPDBCacheMailboxes { @{ Name = 'Identity'; Expression = { $MatchedUser.Identity } })) } + # $MailboxByUPN is the only lookup that stores mailbox objects. Enrichment steps below + # resolve back through this lookup before updating the objects written by Add-CIPPDbItem. + $MailboxByUPN = @{} + foreach ($Mailbox in @($Mailboxes)) { + if ($Mailbox.UPN) { + $MailboxByUPN[$Mailbox.UPN] = $Mailbox + } + } + + try { + $MailboxUsage = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMailboxUsageDetail(period='D7')?`$format=application%2fjson" -tenantid $TenantFilter + foreach ($Usage in @($MailboxUsage)) { + if ($Usage.userPrincipalName -and $MailboxByUPN.ContainsKey($Usage.userPrincipalName)) { + $Mailbox = $MailboxByUPN[$Usage.userPrincipalName] + $Mailbox.storageUsedInBytes = try { [int64]$Usage.storageUsedInBytes } catch { 0 } + $Mailbox.prohibitSendReceiveQuotaInBytes = try { [int64]$Usage.prohibitSendReceiveQuotaInBytes } catch { 0 } + $Mailbox.MailboxItemCount = try { [int64]$Usage.itemCount } catch { 0 } + } + } + } catch { + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache mailbox usage details: $($_.Exception.Message)" -sev Warning + } + + $ArchiveMailboxes = @($Mailboxes | Where-Object { $_.ArchiveEnabled -eq $true -and $_.UPN }) + if ($ArchiveMailboxes.Count -gt 0) { + Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Caching archive statistics for $($ArchiveMailboxes.Count) mailboxes" -sev Debug + + $MailboxUPNByArchiveStatsRequestId = @{} + $ArchiveStatsRequests = @(foreach ($Mailbox in $ArchiveMailboxes) { + $OperationGuid = [Guid]::NewGuid().ToString() + $MailboxUPNByArchiveStatsRequestId[$OperationGuid] = $Mailbox.UPN + + @{ + CmdletInput = @{ + CmdletName = 'Get-MailboxStatistics' + Parameters = @{ + Identity = $Mailbox.UPN + Archive = $true + } + } + OperationGuid = $OperationGuid + } + }) + + $ArchiveStatsResults = New-ExoBulkRequest -tenantid $TenantFilter -cmdletArray $ArchiveStatsRequests -useSystemMailbox $true + foreach ($ArchiveStat in @($ArchiveStatsResults)) { + if ($ArchiveStat.OperationGuid -and $MailboxUPNByArchiveStatsRequestId.ContainsKey($ArchiveStat.OperationGuid) -and -not $ArchiveStat.error) { + $ArchiveMailboxUPN = $MailboxUPNByArchiveStatsRequestId[$ArchiveStat.OperationGuid] + $ArchiveMailbox = $MailboxByUPN[$ArchiveMailboxUPN] + $ArchiveMailbox.ArchiveSize = try { Get-ExoOnlineStringBytes -SizeString $ArchiveStat.TotalItemSize } catch { 0 } + $ArchiveMailbox.ArchiveItemCount = try { [int64]$ArchiveStat.ItemCount } catch { 0 } + } + } + } + $Mailboxes | Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'Mailboxes' -AddCount Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $($Mailboxes.Count) mailboxes successfully" -sev Debug diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ListMailboxes.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ListMailboxes.ps1 index ab877da816d2..c66c5477f9ee 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ListMailboxes.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ListMailboxes.ps1 @@ -30,6 +30,7 @@ function Invoke-ListMailboxes { } # Original live EXO logic + $ZeroArchiveGuid = '00000000-0000-0000-0000-000000000000' $Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox,ForwardingSmtpAddress,DeliverToMailboxAndForward,ForwardingAddress,HiddenFromAddressListsEnabled,ExternalDirectoryObjectId,IsDirSynced,MessageCopyForSendOnBehalfEnabled,MessageCopyForSentAsEnabled,PersistedCapabilities,LitigationHoldEnabled,LitigationHoldDate,LitigationHoldDuration,ComplianceTagHoldApplied,RetentionHoldEnabled,InPlaceHolds,RetentionPolicy' $ExoRequest = @{ tenantid = $TenantFilter @@ -73,6 +74,7 @@ function Invoke-ListMailboxes { @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } }, @{ Name = 'displayName'; Expression = { $_.'DisplayName' } }, @{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } }, + @{ Name = 'ArchiveEnabled'; Expression = { $_.ArchiveGuid -and $_.ArchiveGuid.ToString() -ne $ZeroArchiveGuid } }, @{ Name = 'recipientType'; Expression = { $_.'RecipientType' } }, @{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } }, @{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } }, From 0ebb18803a9580b06c7bfedd4b07103e2ab2da41 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 27 May 2026 23:50:41 +0200 Subject: [PATCH 54/90] implement autopatch --- .../Invoke-CIPPStandardAutopatchGroup.ps1 | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopatchGroup.ps1 diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopatchGroup.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopatchGroup.ps1 new file mode 100644 index 000000000000..e5057e045ed3 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopatchGroup.ps1 @@ -0,0 +1,246 @@ +function Invoke-CIPPStandardAutopatchGroup { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) AutopatchGroup + .SYNOPSIS + (Label) Deploy Windows Autopatch Group + .DESCRIPTION + (Helptext) Deploys a Windows Autopatch group with configurable deployment ring settings for quality updates, feature updates, Edge, and Office. + (DocsDescription) Creates or updates a Windows Autopatch deployment group with Test and Last deployment rings. Configures quality update deferrals, feature update targeting, Edge and Office update channels per ring. Uses the Autopatch API proxy to manage the group configuration. + .NOTES + CAT + Intune Standards + TAG + EXECUTIVETEXT + Configures Windows Autopatch deployment groups to manage update delivery across devices. Autopatch automates Windows quality updates, feature updates, Edge, and Office updates using deployment rings with configurable deferrals and deadlines. + ADDEDCOMPONENT + {"type":"textField","name":"standards.AutopatchGroup.GroupName","label":"Group Name","required":true,"defaultValue":"Autopatch default group"} + {"type":"select","multiple":false,"name":"standards.AutopatchGroup.TargetOSVersion","label":"Target OS Version","required":true,"options":[{"label":"Windows 11, version 24H2","value":"Windows 11, version 24H2"},{"label":"Windows 11, version 25H2","value":"Windows 11, version 25H2"}],"defaultValue":"Windows 11, version 25H2"} + {"type":"switch","name":"standards.AutopatchGroup.EnableDriverUpdate","label":"Enable Driver Updates","defaultValue":true} + {"type":"switch","name":"standards.AutopatchGroup.InstallWin10OnWin11Ineligible","label":"Install latest Windows 10 on Windows 11 ineligible devices","defaultValue":false} + {"type":"number","name":"standards.AutopatchGroup.TestQualityDeferral","label":"Test Ring - Quality Update Deferral (days)","defaultValue":0,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"number","name":"standards.AutopatchGroup.TestQualityDeadline","label":"Test Ring - Quality Update Deadline (days)","defaultValue":1,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"number","name":"standards.AutopatchGroup.TestQualityGracePeriod","label":"Test Ring - Quality Update Grace Period (days)","defaultValue":1,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":7,"message":"Maximum value is 7"}}} + {"type":"number","name":"standards.AutopatchGroup.TestFeatureDeferral","label":"Test Ring - Feature Update Deferral (days)","defaultValue":0,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":365,"message":"Maximum value is 365"}}} + {"type":"number","name":"standards.AutopatchGroup.TestFeatureDeadline","label":"Test Ring - Feature Update Deadline (days)","defaultValue":5,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"select","multiple":false,"name":"standards.AutopatchGroup.TestEdgeChannel","label":"Test Ring - Edge Update Channel","options":[{"label":"Stable","value":"Stable"},{"label":"Beta","value":"Beta"},{"label":"Dev","value":"Dev"}],"defaultValue":"Beta"} + {"type":"select","multiple":false,"name":"standards.AutopatchGroup.TestOfficeChannel","label":"Test Ring - Office Update Channel","options":[{"label":"Current","value":"Current"},{"label":"Monthly Enterprise","value":"MonthlyEnterprise"},{"label":"Semi-Annual Enterprise","value":"SemiAnnual"}],"defaultValue":"MonthlyEnterprise"} + {"type":"number","name":"standards.AutopatchGroup.LastQualityDeferral","label":"Last Ring - Quality Update Deferral (days)","defaultValue":1,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"number","name":"standards.AutopatchGroup.LastQualityDeadline","label":"Last Ring - Quality Update Deadline (days)","defaultValue":2,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"number","name":"standards.AutopatchGroup.LastQualityGracePeriod","label":"Last Ring - Quality Update Grace Period (days)","defaultValue":2,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":7,"message":"Maximum value is 7"}}} + {"type":"number","name":"standards.AutopatchGroup.LastFeatureDeferral","label":"Last Ring - Feature Update Deferral (days)","defaultValue":0,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":365,"message":"Maximum value is 365"}}} + {"type":"number","name":"standards.AutopatchGroup.LastFeatureDeadline","label":"Last Ring - Feature Update Deadline (days)","defaultValue":5,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"select","multiple":false,"name":"standards.AutopatchGroup.LastEdgeChannel","label":"Last Ring - Edge Update Channel","options":[{"label":"Stable","value":"Stable"},{"label":"Beta","value":"Beta"},{"label":"Dev","value":"Dev"}],"defaultValue":"Stable"} + {"type":"select","multiple":false,"name":"standards.AutopatchGroup.LastOfficeChannel","label":"Last Ring - Office Update Channel","options":[{"label":"Current","value":"Current"},{"label":"Monthly Enterprise","value":"MonthlyEnterprise"},{"label":"Semi-Annual Enterprise","value":"SemiAnnual"}],"defaultValue":"MonthlyEnterprise"} + {"type":"number","name":"standards.AutopatchGroup.LastOfficeDeferral","label":"Last Ring - Office Update Deferral (days)","defaultValue":1,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"number","name":"standards.AutopatchGroup.LastOfficeDeadline","label":"Last Ring - Office Update Deadline (days)","defaultValue":2,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"number","name":"standards.AutopatchGroup.TestDnfDeferral","label":"Test Ring - Driver & Firmware Deferral (days)","defaultValue":0,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + {"type":"number","name":"standards.AutopatchGroup.LastDnfDeferral","label":"Last Ring - Driver & Firmware Deferral (days)","defaultValue":1,"validators":{"min":{"value":0,"message":"Minimum value is 0"},"max":{"value":30,"message":"Maximum value is 30"}}} + IMPACT + Medium Impact + ADDEDDATE + 2025-05-27 + POWERSHELLEQUIVALENT + Autopatch API - POST /api/autoPatch + RECOMMENDEDBY + MULTIPLE + True + DISABLEDFEATURES + {"report":false,"warn":false,"remediate":false} + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param( + $Tenant, + $Settings + ) + # This autoPatch proxy has been created by Microsoft to facilitate Autopatch group management until native Graph API support is available. It abstracts the underlying Graph API calls and provides a simplified interface for creating and updating Autopatch groups based on the provided settings. + + $AutopatchProxyBase = 'https://intuneautopatchbeta-bwhtaqgefgcyaaa8.westeurope-01.azurewebsites.net/api/autoPatch' + + # Extract settings with defaults + $GroupName = $Settings.GroupName ?? 'Autopatch default group' + $TargetOSVersion = $Settings.TargetOSVersion.value ?? $Settings.TargetOSVersion ?? 'Windows 11, version 25H2' + $EnableDriverUpdate = if ($null -ne $Settings.EnableDriverUpdate) { [bool]$Settings.EnableDriverUpdate } else { $true } + $InstallWin10OnWin11Ineligible = if ($null -ne $Settings.InstallWin10OnWin11Ineligible) { [bool]$Settings.InstallWin10OnWin11Ineligible } else { $false } + + # Test ring settings + $TestQualityDeferral = [int]($Settings.TestQualityDeferral ?? 0) + $TestQualityDeadline = [int]($Settings.TestQualityDeadline ?? 1) + $TestQualityGracePeriod = [int]($Settings.TestQualityGracePeriod ?? 1) + $TestFeatureDeferral = [int]($Settings.TestFeatureDeferral ?? 0) + $TestFeatureDeadline = [int]($Settings.TestFeatureDeadline ?? 5) + $TestEdgeChannel = $Settings.TestEdgeChannel.value ?? $Settings.TestEdgeChannel ?? 'Beta' + $TestOfficeChannel = $Settings.TestOfficeChannel.value ?? $Settings.TestOfficeChannel ?? 'MonthlyEnterprise' + $TestDnfDeferral = [int]($Settings.TestDnfDeferral ?? 0) + + # Last ring settings + $LastQualityDeferral = [int]($Settings.LastQualityDeferral ?? 1) + $LastQualityDeadline = [int]($Settings.LastQualityDeadline ?? 2) + $LastQualityGracePeriod = [int]($Settings.LastQualityGracePeriod ?? 2) + $LastFeatureDeferral = [int]($Settings.LastFeatureDeferral ?? 0) + $LastFeatureDeadline = [int]($Settings.LastFeatureDeadline ?? 5) + $LastEdgeChannel = $Settings.LastEdgeChannel.value ?? $Settings.LastEdgeChannel ?? 'Stable' + $LastOfficeChannel = $Settings.LastOfficeChannel.value ?? $Settings.LastOfficeChannel ?? 'MonthlyEnterprise' + $LastDnfDeferral = [int]($Settings.LastDnfDeferral ?? 1) + $LastOfficeDeferral = [int]($Settings.LastOfficeDeferral ?? 1) + $LastOfficeDeadline = [int]($Settings.LastOfficeDeadline ?? 2) + + # Get current autopatch groups + try { + $CurrentGroups = New-GraphGetRequest -uri $AutopatchProxyBase -tenantid $Tenant -AsApp $true + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not retrieve Autopatch groups: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return + } + + $ExistingGroup = if ($CurrentGroups) { + @($CurrentGroups) | Where-Object { $_.name -eq $GroupName } | Select-Object -First 1 + } + + # Build the autopatch group body + $Body = @{ + name = $GroupName + description = '' + globalUserManagedAadGroups = @() + deploymentGroups = @( + @{ + userManagedAadGroups = @() + name = "$GroupName - Test" + deploymentGroupPolicySettings = @{ + aadGroupName = "$GroupName - Test" + deviceConfigurationSetting = @{ + updateBehavior = 'AutoInstallAndRestart' + notificationSetting = 'DefaultNotifications' + qualityDeploymentSettings = @{ + deferral = $TestQualityDeferral + deadline = $TestQualityDeadline + gracePeriod = $TestQualityGracePeriod + } + featureDeploymentSettings = @{ + deferral = $TestFeatureDeferral + deadline = $TestFeatureDeadline + } + updateFrequencyUI = $null + installDays = $null + installTime = $null + activeHourEndTime = $null + activeHourStartTime = $null + } + featureUpdateAnchorCloudSetting = @{ + targetOSVersion = $TargetOSVersion + installLatestWindows10OnWindows11IneligibleDevice = $InstallWin10OnWin11Ineligible + } + dnfUpdateCloudSetting = @{ + approvalType = 'Automatic' + deploymentDeferralInDays = $TestDnfDeferral + } + edgeDCv2Setting = @{ + targetChannel = $TestEdgeChannel + } + officeDCv2Setting = @{ + targetChannel = $TestOfficeChannel + deferral = 0 + deadline = 1 + hideUpdateNotifications = $false + enableAutomaticUpdate = $true + hideEnableDisableUpdate = $true + enableOfficeMgmt = $false + updatePath = 'http://officecdn.microsoft.com/pr/55336b82-a18d-4dd6-b5f6-9e5095c314a6' + } + } + } + @{ + userManagedAadGroups = @() + name = "$GroupName - Last" + deploymentGroupPolicySettings = @{ + aadGroupName = "$GroupName - Last" + deviceConfigurationSetting = @{ + updateBehavior = 'AutoInstallAndRestart' + notificationSetting = 'DefaultNotifications' + qualityDeploymentSettings = @{ + deferral = $LastQualityDeferral + deadline = $LastQualityDeadline + gracePeriod = $LastQualityGracePeriod + } + featureDeploymentSettings = @{ + deferral = $LastFeatureDeferral + deadline = $LastFeatureDeadline + } + updateFrequencyUI = $null + installDays = $null + installTime = $null + activeHourEndTime = $null + activeHourStartTime = $null + } + featureUpdateAnchorCloudSetting = @{ + targetOSVersion = $TargetOSVersion + installLatestWindows10OnWindows11IneligibleDevice = $InstallWin10OnWin11Ineligible + } + dnfUpdateCloudSetting = @{ + approvalType = 'Automatic' + deploymentDeferralInDays = $LastDnfDeferral + } + edgeDCv2Setting = @{ + targetChannel = $LastEdgeChannel + } + officeDCv2Setting = @{ + targetChannel = $LastOfficeChannel + deferral = $LastOfficeDeferral + deadline = $LastOfficeDeadline + hideUpdateNotifications = $false + enableAutomaticUpdate = $true + hideEnableDisableUpdate = $true + enableOfficeMgmt = $false + updatePath = 'http://officecdn.microsoft.com/pr/55336b82-a18d-4dd6-b5f6-9e5095c314a6' + } + } + } + ) + type = 'User' + enableDriverUpdate = $EnableDriverUpdate + scopeTags = @(0) + enabledContentTypes = 31 + } | ConvertTo-Json -Compress -Depth 10 + + if ($Settings.remediate -eq $true) { + if ($ExistingGroup) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Autopatch group '$GroupName' already exists, updating." -sev Info + try { + $UpdateUri = "$AutopatchProxyBase/$($ExistingGroup.id)" + New-GraphPOSTRequest -uri $UpdateUri -tenantid $Tenant -body $Body -type PUT + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully updated Autopatch group '$GroupName'." -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to update Autopatch group '$GroupName': $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } else { + try { + New-GraphPOSTRequest -uri $AutopatchProxyBase -tenantid $Tenant -body $Body -type POST + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully created Autopatch group '$GroupName'." -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Autopatch group '$GroupName': $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + if ($ExistingGroup) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Autopatch group '$GroupName' is configured." -sev Info + } else { + Write-StandardsAlert -message "Autopatch group '$GroupName' is not configured." -object $GroupName ` + -tenant $Tenant -standardName 'AutopatchGroup' -standardId $Settings.standardId + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'AutopatchGroup' -FieldValue ([bool]$ExistingGroup) -StoreAs bool -Tenant $Tenant + } +} From e41d5322c698c331c3740b6d9d8d62ceec818971 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 28 May 2026 12:49:03 +0800 Subject: [PATCH 55/90] Update Add-CIPPDbItem.ps1 --- Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 index 5277b80b8f34..ba74ec1096d8 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 @@ -101,6 +101,7 @@ function Add-CIPPDbItem { PartitionKey = $TenantFilter RowKey = "$Type-Count" DataCount = [int]$NewCount + Type = $Type } -Force $CountMs = $Stopwatch.ElapsedMilliseconds - $CntStart } From 5b7c5a964b81876324525794b66387574d583c2b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 28 May 2026 13:56:30 +0800 Subject: [PATCH 56/90] Update Invoke-ListWorkerHealth.ps1 --- .../HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 index f1753f41d93b..8e28804c62d0 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 @@ -88,10 +88,6 @@ function Invoke-ListWorkerHealth { $Result = [Craft.Services.WorkerMetricsBridge]::DeleteJob($JobId) $Body = @{ Results = @{ Success = $Result; JobId = $JobId } } } - 'PurgeCompleted' { - $Purged = [Craft.Services.WorkerMetricsBridge]::PurgeCompleted() - $Body = @{ Results = @{ Success = $true; PurgedCount = $Purged } } - } 'ChangePriority' { $JobId = $Request.Query.JobId ?? $Request.Body.JobId $NewPriority = $Request.Query.Priority ?? $Request.Body.Priority From aefa69b052ed5d74d0a965f9918ac358f44a668d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Thu, 28 May 2026 12:31:02 +0200 Subject: [PATCH 57/90] add compliance admin by default --- .../GraphHelper/Get-NormalizedError.ps1 | 1 + .../CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 index 13398c7fddee..0b9c687bc2b4 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 @@ -73,6 +73,7 @@ function Get-NormalizedError { '*AADSTS9002313*' { 'The credentials used to connect to the Graph API are not available, please retry. If this issue persists you may need to execute the SAM wizard.' } '*One or more platform(s) is/are not configured for the customer. Please configure the platform before trying to purchase a SKU.*' { 'One or more platform(s) is/are not configured for the customer. Please configure the platform before trying to purchase a SKU.' } "One or more added object references already exist for the following modified properties: 'members'." { 'This user is already a member of the selected group.' } + '*is not present in the role definition of the current user*' { 'We do not have permissions to access this resource, try performing a CPV refresh in Application Settings -> Permissions. ' } default { $message } } diff --git a/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 b/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 index e7b98b0f7465..42ce73d32592 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 @@ -17,21 +17,34 @@ function Set-CIPPSAMAdminRoles { $ActionLogs = [System.Collections.Generic.List[object]]::new() + # Default roles always assigned for all tenants + $DefaultRoles = @( + [PSCustomObject]@{ value = '17315797-102d-40b4-93e0-432062caca18'; label = 'Compliance Administrator' } + ) + $SAMRolesTable = Get-CIPPTable -tablename 'SAMRoles' $Roles = Get-CIPPAzDataTableEntity @SAMRolesTable try { - $SAMRoles = $Roles.Roles | ConvertFrom-Json -ErrorAction Stop + $SAMRoles = @($Roles.Roles | ConvertFrom-Json -ErrorAction Stop) $Tenants = $Roles.Tenants | ConvertFrom-Json -ErrorAction Stop if ($Tenants.value) { $Tenants = $Tenants.value } } catch { - $ActionLogs.Add('CIPP-SAM roles not configured') - return $ActionLogs + $SAMRoles = @() + $Tenants = @() + } + + # Merge default roles with user-configured roles, avoiding duplicates + $ExistingValues = @($SAMRoles | ForEach-Object { $_.value }) + foreach ($DefaultRole in $DefaultRoles) { + if ($DefaultRole.value -notin $ExistingValues) { + $SAMRoles = @($SAMRoles) + @($DefaultRole) + } } - if (($SAMRoles | Measure-Object).count -gt 0 -and $Tenants -contains $TenantFilter -or $Tenants -contains 'AllTenants') { + if (($SAMRoles | Measure-Object).Count -gt 0 -and ($Tenants -contains $TenantFilter -or $Tenants -contains 'AllTenants' -or ($Tenants | Measure-Object).Count -eq 0)) { $InitialRequests = @( [PSCustomObject]@{ id = 'memberOf' From 25fcdc1cf1ba33c650e1030ba0b6aa46e4a743eb Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Thu, 28 May 2026 13:16:55 +0200 Subject: [PATCH 58/90] add 404 detection for non-existing roles --- Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 b/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 index 42ce73d32592..5ea7b643fcbc 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSAMAdminRoles.ps1 @@ -96,8 +96,10 @@ function Set-CIPPSAMAdminRoles { $Results | ForEach-Object { if ($_.status -eq 204) { $ActionLogs.Add("Added service principal to directory role $($_.id)") + } elseif ($_.status -eq 404) { + $ActionLogs.Add("Directory role $($_.id) does not exist in tenant, skipping") } else { - $ActionLogs.Add("Failed to add service principal to directoryRole $($_.id)") + $ActionLogs.Add("Failed to add service principal to directoryRole $($_.id): $($_ | ConvertTo-Json -Depth 5)") Write-Verbose ($_ | ConvertTo-Json -Depth 5) $HasFailures = $true } From 25e2b0f2cfa9f73ab3e61d630574b0fe91ce9498 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 28 May 2026 20:50:50 +0800 Subject: [PATCH 59/90] tweaks --- .../CIPPAlerts/Public/Alerts/Get-CIPPAlertAdminPassword.ps1 | 2 +- .../HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAdminPassword.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAdminPassword.ps1 index 1cea2229c059..35666a10d4ba 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAdminPassword.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertAdminPassword.ps1 @@ -12,7 +12,7 @@ function Get-CIPPAlertAdminPassword { $TenantFilter ) try { - $TenantId = (Get-Tenants | Where-Object -Property defaultDomainName -EQ $TenantFilter).customerId + $TenantId = (Get-Tenants -TenantFilter $TenantFilter).customerId # Get role assignments without expanding principal to avoid rate limiting $RoleAssignments = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '62e90394-69f5-4237-9190-012177145e10'" -tenantid $($TenantFilter) | Where-Object { $_.principalOrganizationId -EQ $TenantId } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 index 8e28804c62d0..2d5bdcc17ef1 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 @@ -15,6 +15,7 @@ function Invoke-ListWorkerHealth { switch ($Action) { 'Snapshot' { $Snapshot = [Craft.Services.WorkerMetricsBridge]::GetSnapshot() + try { $Snapshot.Memory | Add-Member -NotePropertyName 'TestDataCacheCount' -NotePropertyValue ([CIPP.TestDataCache]::Count) -ErrorAction SilentlyContinue } catch {} $Body = @{ Results = $Snapshot } } 'Summary' { @@ -100,6 +101,10 @@ function Invoke-ListWorkerHealth { $Result = [Craft.Services.WorkerMetricsBridge]::ChangePriority($JobId, [int]$NewPriority) $Body = @{ Results = @{ Success = $Result; JobId = $JobId; NewPriority = [int]$NewPriority } } } + 'CacheDiag' { + $Diag = [CIPP.TestDataCache]::GetDiagnostics() + $Body = @{ Results = $Diag } + } default { $Body = @{ Results = "Unknown action: $Action" } return [HttpResponseContext]@{ From 99dd88c54197a92ba31c5f03b312c7931bac936b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 28 May 2026 21:00:14 +0800 Subject: [PATCH 60/90] optimisation --- .../Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 | 2 +- .../CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderMalware.ps1 | 2 +- .../CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderStatus.ps1 | 2 +- Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 | 2 +- .../HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 | 2 +- .../Public/Standards/Invoke-CIPPStandardBranding.ps1 | 2 +- .../Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 | 2 +- Modules/CippExtensions/Public/New-CippExtAlert.ps1 | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 index db57af632bde..a3c9131c0208 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/BPA/Push-BPACollectData.ps1 @@ -5,7 +5,7 @@ function Push-BPACollectData { #> param($Item) - $TenantName = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Item.Tenant + $TenantName = Get-Tenants -TenantFilter $Item.Tenant $BPATemplateTable = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'BPATemplate'" $TemplatesLoc = (Get-CIPPAzDataTableEntity @BPATemplateTable -Filter $Filter).JSON | ConvertFrom-Json diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderMalware.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderMalware.ps1 index 4b3f6dd962a0..d651440f0cd3 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderMalware.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderMalware.ps1 @@ -12,7 +12,7 @@ function Get-CIPPAlertDefenderMalware { $TenantFilter ) try { - $TenantId = (Get-Tenants | Where-Object -Property defaultDomainName -EQ $TenantFilter).customerId + $TenantId = (Get-Tenants -TenantFilter $TenantFilter).customerId $AlertData = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/windowsDeviceMalwareStates?`$top=999&`$filter=tenantId eq '$($TenantId)'" | Where-Object { $_.malwareThreatState -eq 'Active' } | ForEach-Object { [PSCustomObject]@{ DeviceName = $_.managedDeviceName diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderStatus.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderStatus.ps1 index ec95245fa350..ee8d556ba771 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderStatus.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertDefenderStatus.ps1 @@ -11,7 +11,7 @@ function Get-CIPPAlertDefenderStatus { $TenantFilter ) try { - $TenantId = (Get-Tenants | Where-Object -Property defaultDomainName -EQ $TenantFilter).customerId + $TenantId = (Get-Tenants -TenantFilter $TenantFilter).customerId $AlertData = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/managedTenants/windowsProtectionStates?`$top=999&`$filter=tenantId eq '$($TenantId)'" | Where-Object { $_.realTimeProtectionEnabled -eq $false -or $_.MalwareprotectionEnabled -eq $false } | ForEach-Object { [PSCustomObject]@{ ManagedDeviceName = $_.managedDeviceName diff --git a/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 b/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 index 74c1110550a6..0f119b13f266 100644 --- a/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPBPAField.ps1 @@ -7,7 +7,7 @@ function Add-CIPPBPAField { $Tenant ) $Table = Get-CippTable -tablename 'cachebpav2' - $TenantName = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Tenant + $TenantName = Get-Tenants -TenantFilter $Tenant $CurrentContentsObject = (Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$BPAName' and PartitionKey eq '$($TenantName.customerId)'") Write-Information "Adding $FieldName to $BPAName for $Tenant. content is $FieldValue" if ($CurrentContentsObject.RowKey) { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 index fc54f6a3bc74..56dc7c0bda12 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/Autopilot/Invoke-AddAPDevice.ps1 @@ -13,7 +13,7 @@ function Invoke-AddAPDevice { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers - $TenantFilter = (Get-Tenants | Where-Object { $_.defaultDomainName -eq $Request.Body.TenantFilter.value }).customerId + $TenantFilter = (Get-Tenants -TenantFilter $Request.Body.TenantFilter.value).customerId $GroupName = if ($Request.Body.Groupname) { $Request.Body.Groupname } else { (New-Guid).GUID } Write-Host $GroupName diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 index fc1c34e5be27..050f5d6043f6 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 @@ -49,7 +49,7 @@ function Invoke-CIPPStandardBranding { ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'Branding' - $TenantId = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Tenant + $TenantId = Get-Tenants -TenantFilter $Tenant $Localizations = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/organization/$($TenantId.customerId)/branding/localizations" -tenantID $Tenant -AsApp $true # Get layoutTemplateType value using null-coalescing operator diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 index 97e4306f878b..0c6d45b2c533 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 @@ -44,7 +44,7 @@ function Invoke-CIPPStandardPhishProtection { return $true } #we're done. - $TenantId = Get-Tenants | Where-Object -Property defaultDomainName -EQ $Tenant + $TenantId = Get-Tenants -TenantFilter $Tenant $Table = Get-CIPPTable -TableName Config $CippConfig = (Get-CIPPAzDataTableEntity @Table) diff --git a/Modules/CippExtensions/Public/New-CippExtAlert.ps1 b/Modules/CippExtensions/Public/New-CippExtAlert.ps1 index bae549bf0719..303eaf480376 100644 --- a/Modules/CippExtensions/Public/New-CippExtAlert.ps1 +++ b/Modules/CippExtensions/Public/New-CippExtAlert.ps1 @@ -14,7 +14,7 @@ function New-CippExtAlert { 'HaloPSA' { if ($Configuration.HaloPSA.enabled) { $MappingFile = Get-CIPPAzDataTableEntity @MappingTable -Filter "PartitionKey eq 'HaloMapping'" - $TenantId = (Get-Tenants | Where-Object defaultDomainName -EQ $Alert.TenantId).customerId + $TenantId = (Get-Tenants -TenantFilter $Alert.TenantId).customerId Write-Host "TenantId: $TenantId" $MappedId = ($MappingFile | Where-Object { $_.RowKey -eq $TenantId }).IntegrationId Write-Host "MappedId: $MappedId" From 0cdc2e813bf8e713b8f0402f4735ff93b695e6ca Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Thu, 28 May 2026 15:21:15 +0200 Subject: [PATCH 61/90] new auth methods single standard --- .../Public/Set-CIPPAuthenticationPolicy.ps1 | 44 +- ...voke-CIPPStandardAuthenticationMethods.ps1 | 389 ++++++++++++++++++ 2 files changed, 411 insertions(+), 22 deletions(-) create mode 100644 Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 diff --git a/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 index b3fedd2310ab..767ce5747c3f 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAuthenticationPolicy.ps1 @@ -5,6 +5,9 @@ function Set-CIPPAuthenticationPolicy { [Parameter(Mandatory = $true)][ValidateSet('FIDO2', 'MicrosoftAuthenticator', 'SMS', 'TemporaryAccessPass', 'HardwareOATH', 'softwareOath', 'Voice', 'Email', 'x509Certificate', 'QRCodePin')]$AuthenticationMethodId, [Parameter(Mandatory = $true)][bool]$Enabled, # true = enabled or false = disabled $MicrosoftAuthenticatorSoftwareOathEnabled, + [ValidateSet('default', 'enabled', 'disabled')]$MicrosoftAuthenticatorDisplayLocation, + [ValidateSet('default', 'enabled', 'disabled')]$MicrosoftAuthenticatorDisplayAppInfo, + [ValidateSet('default', 'enabled', 'disabled')]$MicrosoftAuthenticatorCompanionApp, $TAPMinimumLifetime = 60, #Minutes $TAPMaximumLifetime = 480, #minutes $TAPDefaultLifeTime = 60, #minutes @@ -41,26 +44,30 @@ function Set-CIPPAuthenticationPolicy { # Microsoft Authenticator 'MicrosoftAuthenticator' { - # Remove numberMatchingRequiredState property if it exists - $CurrentInfo.featureSettings.PSObject.Properties.Remove('numberMatchingRequiredState') - if ($State -eq 'enabled') { - $CurrentInfo.featureSettings.displayAppInformationRequiredState.state = $State - $CurrentInfo.featureSettings.displayLocationInformationRequiredState.state = $State # Set MS authenticator OTP state if parameter is passed in - if ($null -ne $MicrosoftAuthenticatorSoftwareOathEnabled ) { + if ($null -ne $MicrosoftAuthenticatorSoftwareOathEnabled) { $CurrentInfo.isSoftwareOathEnabled = $MicrosoftAuthenticatorSoftwareOathEnabled $OptionalLogMessage = "and MS Authenticator software OTP to $MicrosoftAuthenticatorSoftwareOathEnabled" } + # Feature settings + if ($MicrosoftAuthenticatorDisplayAppInfo) { + $CurrentInfo.featureSettings.displayAppInformationRequiredState.state = $MicrosoftAuthenticatorDisplayAppInfo + } + if ($MicrosoftAuthenticatorDisplayLocation) { + $CurrentInfo.featureSettings.displayLocationInformationRequiredState.state = $MicrosoftAuthenticatorDisplayLocation + } + if ($MicrosoftAuthenticatorCompanionApp) { + $CurrentInfo.featureSettings.companionAppAllowedState.state = $MicrosoftAuthenticatorCompanionApp + } + # numberMatchingRequiredState is permanently enabled by Microsoft and can no longer be toggled + $CurrentInfo.featureSettings.PSObject.Properties.Remove('numberMatchingRequiredState') } } # SMS 'SMS' { - if ($State -eq 'enabled') { - Write-LogMessage -headers $Headers -API $APIName -tenant $Tenant -message "Setting $AuthenticationMethodId to enabled is not allowed" -sev Error - throw "Setting $AuthenticationMethodId to enabled is not allowed" - } + # No special configuration needed } # Temporary Access Pass @@ -87,31 +94,24 @@ function Set-CIPPAuthenticationPolicy { # Voice call 'Voice' { - # Disallow enabling voice - if ($State -eq 'enabled') { - Write-LogMessage -headers $Headers -API $APIName -tenant $Tenant -message "Setting $AuthenticationMethodId to enabled is not allowed" -sev Error - throw "Setting $AuthenticationMethodId to enabled is not allowed" - } + # No special configuration needed } # Email OTP 'Email' { - if ($State -eq 'enabled') { - Write-LogMessage -headers $Headers -API $APIName -tenant $Tenant -message "Setting $AuthenticationMethodId to enabled is not allowed" -sev Error - throw "Setting $AuthenticationMethodId to enabled is not allowed" - } + # No special configuration needed } # Certificate-based authentication 'x509Certificate' { - # Nothing special to do here + # No special configuration needed } # QR code 'QRCodePin' { if ($State -eq 'enabled') { - Write-LogMessage -headers $Headers -API $APIName -tenant $Tenant -message "Setting $AuthenticationMethodId to enabled is not allowed" -sev Error - throw "Setting $AuthenticationMethodId to enabled is not allowed" + $CurrentInfo.standardQRCodeLifetimeInDays = $QRCodeLifetimeInDays + $CurrentInfo.pinLength = $QRCodePinLength } } default { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 new file mode 100644 index 000000000000..a83592d2b056 --- /dev/null +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 @@ -0,0 +1,389 @@ +function Invoke-CIPPStandardAuthenticationMethods { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) AuthenticationMethods + .SYNOPSIS + (Label) Configure Authentication Methods + .DESCRIPTION + (Helptext) Configures all authentication methods for the tenant including Microsoft Authenticator, FIDO2, SMS, Voice, Email OTP, Temporary Access Pass, Software OATH, Hardware OATH, Certificate-based, and QR Code Pin. Enable or disable each method and optionally target specific groups. + (DocsDescription) Unified standard to configure all authentication method policies in a single place. Each method can be independently enabled or disabled, targeted to all users or specific groups using group name wildcards, and configured with method-specific settings such as TAP lifetime, QR code pin length, and Authenticator software OTP. + .NOTES + CAT + Entra (AAD) Standards + TAG + EXECUTIVETEXT + Provides centralized control over all tenant authentication methods from a single standard. Administrators can enable phishing-resistant methods like FIDO2 and Microsoft Authenticator while disabling less secure options like SMS and Voice. Each method supports group-level targeting using wildcard group names, allowing staged rollouts and granular control. + ADDEDCOMPONENT + {"type":"switch","name":"standards.AuthenticationMethods.MicrosoftAuthenticatorEnabled","label":"Microsoft Authenticator","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.MicrosoftAuthenticatorGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.MicrosoftAuthenticatorEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.MicrosoftAuthenticatorSoftwareOath","label":"Enable Software OTP in Authenticator","defaultValue":false,"condition":{"field":"standards.AuthenticationMethods.MicrosoftAuthenticatorEnabled","compareType":"is","compareValue":true}} + {"type":"autoComplete","multiple":false,"creatable":false,"label":"Show Application Name in Push Notifications","name":"standards.AuthenticationMethods.MicrosoftAuthenticatorDisplayAppInfo","options":[{"label":"Microsoft managed","value":"default"},{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}],"condition":{"field":"standards.AuthenticationMethods.MicrosoftAuthenticatorEnabled","compareType":"is","compareValue":true}} + {"type":"autoComplete","multiple":false,"creatable":false,"label":"Show Geographic Location in Push Notifications","name":"standards.AuthenticationMethods.MicrosoftAuthenticatorDisplayLocation","options":[{"label":"Microsoft managed","value":"default"},{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}],"condition":{"field":"standards.AuthenticationMethods.MicrosoftAuthenticatorEnabled","compareType":"is","compareValue":true}} + {"type":"autoComplete","multiple":false,"creatable":false,"label":"Companion App (Authenticator Lite)","name":"standards.AuthenticationMethods.MicrosoftAuthenticatorCompanionApp","options":[{"label":"Microsoft managed","value":"default"},{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}],"condition":{"field":"standards.AuthenticationMethods.MicrosoftAuthenticatorEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.FIDO2Enabled","label":"FIDO2 Security Keys","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.FIDO2Group","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.FIDO2Enabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.TAPEnabled","label":"Temporary Access Pass","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.TAPGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.TAPEnabled","compareType":"is","compareValue":true}} + {"type":"autoComplete","multiple":false,"creatable":false,"label":"TAP Usage Mode","name":"standards.AuthenticationMethods.TAPUsableOnce","options":[{"label":"Only Once","value":"true"},{"label":"Multiple Logons","value":"false"}],"condition":{"field":"standards.AuthenticationMethods.TAPEnabled","compareType":"is","compareValue":true}} + {"type":"number","name":"standards.AuthenticationMethods.TAPDefaultLifetime","label":"TAP Default Lifetime (minutes)","defaultValue":60,"condition":{"field":"standards.AuthenticationMethods.TAPEnabled","compareType":"is","compareValue":true}} + {"type":"number","name":"standards.AuthenticationMethods.TAPMinLifetime","label":"TAP Minimum Lifetime (minutes)","defaultValue":60,"condition":{"field":"standards.AuthenticationMethods.TAPEnabled","compareType":"is","compareValue":true}} + {"type":"number","name":"standards.AuthenticationMethods.TAPMaxLifetime","label":"TAP Maximum Lifetime (minutes)","defaultValue":480,"condition":{"field":"standards.AuthenticationMethods.TAPEnabled","compareType":"is","compareValue":true}} + {"type":"number","name":"standards.AuthenticationMethods.TAPDefaultLength","label":"TAP Length (characters)","defaultValue":8,"condition":{"field":"standards.AuthenticationMethods.TAPEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.SoftwareOathEnabled","label":"Third-Party Software OATH Tokens","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.SoftwareOathGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.SoftwareOathEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.HardwareOathEnabled","label":"Hardware OATH Tokens","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.HardwareOathGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.HardwareOathEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.SMSEnabled","label":"SMS","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.SMSGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.SMSEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.VoiceEnabled","label":"Voice Call","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.VoiceGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.VoiceEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.EmailEnabled","label":"Email OTP","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.EmailGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.EmailEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.x509CertificateEnabled","label":"Certificate-Based Authentication","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.x509CertificateGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.x509CertificateEnabled","compareType":"is","compareValue":true}} + {"type":"switch","name":"standards.AuthenticationMethods.QRCodePinEnabled","label":"QR Code Pin","defaultValue":false} + {"type":"textField","name":"standards.AuthenticationMethods.QRCodePinGroup","label":"Target Group Name (wildcard supported, blank = All Users)","required":false,"condition":{"field":"standards.AuthenticationMethods.QRCodePinEnabled","compareType":"is","compareValue":true}} + {"type":"number","name":"standards.AuthenticationMethods.QRCodeLifetimeInDays","label":"QR Code Lifetime (days, 1-395)","defaultValue":365,"condition":{"field":"standards.AuthenticationMethods.QRCodePinEnabled","compareType":"is","compareValue":true}} + {"type":"number","name":"standards.AuthenticationMethods.QRCodePinLength","label":"QR Code PIN Length (8-20)","defaultValue":8,"condition":{"field":"standards.AuthenticationMethods.QRCodePinEnabled","compareType":"is","compareValue":true}} + IMPACT + High Impact + ADDEDDATE + 2026-05-28 + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration + RECOMMENDEDBY + "CIPP" + DISABLEDFEATURES + {"report":false,"warn":false,"remediate":false} + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/alignment/templates/available-standards + #> + + param($Tenant, $Settings) + + # Map of method IDs used in the Graph API to our setting key names + # 'Id' matches the Graph API response id for lookups from the full policy + # 'RemediationId' matches the Set-CIPPAuthenticationPolicy ValidateSet for PATCH calls + $AuthMethods = @( + @{ Id = 'MicrosoftAuthenticator'; RemediationId = 'MicrosoftAuthenticator'; SettingKey = 'MicrosoftAuthenticator'; Label = 'Microsoft Authenticator' } + @{ Id = 'Fido2'; RemediationId = 'FIDO2'; SettingKey = 'FIDO2'; Label = 'FIDO2 Security Keys' } + @{ Id = 'TemporaryAccessPass'; RemediationId = 'TemporaryAccessPass'; SettingKey = 'TAP'; Label = 'Temporary Access Pass' } + @{ Id = 'softwareOath'; RemediationId = 'softwareOath'; SettingKey = 'SoftwareOath'; Label = 'Software OATH Tokens' } + @{ Id = 'HardwareOath'; RemediationId = 'HardwareOATH'; SettingKey = 'HardwareOath'; Label = 'Hardware OATH Tokens' } + @{ Id = 'Sms'; RemediationId = 'SMS'; SettingKey = 'SMS'; Label = 'SMS' } + @{ Id = 'Voice'; RemediationId = 'Voice'; SettingKey = 'Voice'; Label = 'Voice Call' } + @{ Id = 'Email'; RemediationId = 'Email'; SettingKey = 'Email'; Label = 'Email OTP' } + @{ Id = 'x509Certificate'; RemediationId = 'x509Certificate'; SettingKey = 'x509Certificate'; Label = 'Certificate-Based Authentication' } + @{ Id = 'QRCodePin'; RemediationId = 'QRCodePin'; SettingKey = 'QRCodePin'; Label = 'QR Code Pin' } + ) + + # Determine which methods the user has explicitly configured + $ConfiguredMethods = foreach ($Method in $AuthMethods) { + $EnabledKey = "$($Method.SettingKey)Enabled" + $EnabledValue = $Settings.$EnabledKey + if ($null -eq $EnabledValue) { continue } + $GroupName = $Settings."$($Method.SettingKey)Group" + [PSCustomObject]@{ + Id = $Method.Id + RemediationId = $Method.RemediationId + Key = $Method.SettingKey + Label = $Method.Label + Enabled = [bool]$EnabledValue + GroupName = if ([string]::IsNullOrWhiteSpace($GroupName)) { $null } else { $GroupName } + } + } + + if (-not $ConfiguredMethods -or $ConfiguredMethods.Count -eq 0) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'AuthenticationMethods: No authentication methods configured, skipping.' -sev Info + return + } + + try { + $FullPolicy = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -tenantid $Tenant -AsApp $true + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: Could not retrieve authentication methods policy. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return + } + + # Index the method configurations by ID for fast lookup + $CurrentConfigs = @{} + foreach ($Config in $FullPolicy.authenticationMethodConfigurations) { + $CurrentConfigs[$Config.id] = $Config + } + + # Resolve group names to IDs (cached to avoid duplicate lookups) + $GroupIdCache = @{} + foreach ($Method in $ConfiguredMethods) { + if ($Method.Enabled -and $Method.GroupName -and -not $GroupIdCache.ContainsKey($Method.GroupName)) { + try { + $EscapedName = $Method.GroupName -replace "'", "''" + $GroupFilter = [System.Uri]::EscapeDataString("startsWith(displayName,'$EscapedName')") + $MatchedGroups = @(New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$select=id,displayName&`$filter=$GroupFilter" -tenantid $Tenant) + if ($MatchedGroups.Count -gt 0) { + $GroupIdCache[$Method.GroupName] = @($MatchedGroups | ForEach-Object { $_.id }) + if ($MatchedGroups.Count -gt 1) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: Multiple groups matched '$($Method.GroupName)': $($MatchedGroups.displayName -join ', ')" -sev Info + } + } else { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: No group found matching '$($Method.GroupName)'" -sev Warning + $GroupIdCache[$Method.GroupName] = $null + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: Failed to resolve group '$($Method.GroupName)'. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $GroupIdCache[$Method.GroupName] = $null + } + } + } + + # --- Build expected vs current state and check compliance per method --- + $ComplianceResults = foreach ($Method in $ConfiguredMethods) { + $CurrentConfig = $CurrentConfigs[$Method.Id] + if (-not $CurrentConfig) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: Method '$($Method.Label)' not found in policy response." -sev Warning + continue + } + + $DesiredState = if ($Method.Enabled) { 'enabled' } else { 'disabled' } + $Drifts = [System.Collections.Generic.List[string]]::new() + + # -- State check -- + if ($CurrentConfig.state -ne $DesiredState) { + $Drifts.Add("state: '$($CurrentConfig.state)' -> '$DesiredState'") + } + + # -- Group targeting check (compare only targetType + id, ignore API-added properties) -- + $CurrentTargetIds = @($CurrentConfig.includeTargets | ForEach-Object { $_.id }) + if ($Method.Enabled -and $Method.GroupName) { + $ResolvedGroupIds = $GroupIdCache[$Method.GroupName] + if ($ResolvedGroupIds) { + $Diff = Compare-Object -ReferenceObject @($ResolvedGroupIds | Sort-Object) -DifferenceObject @($CurrentTargetIds | Sort-Object) -ErrorAction SilentlyContinue + if ($Diff) { + $Drifts.Add("includeTargets: current [$($CurrentTargetIds -join ', ')] -> expected [$($ResolvedGroupIds -join ', ')]") + } + } + } elseif ($Method.Enabled) { + if ('all_users' -notin $CurrentTargetIds) { + $Drifts.Add("includeTargets: current [$($CurrentTargetIds -join ', ')] -> expected [all_users]") + } + } + + # Build normalized includeTargets for expected config (only the properties we manage) + if ($Method.Enabled -and $Method.GroupName) { + $ResolvedGroupIds = $GroupIdCache[$Method.GroupName] + if ($ResolvedGroupIds) { + $NormalizedTargets = @($ResolvedGroupIds | ForEach-Object { @{ targetType = 'group'; id = $_ } }) + } else { + $NormalizedTargets = @($CurrentConfig.includeTargets | ForEach-Object { @{ targetType = $_.targetType; id = $_.id } }) + } + } elseif ($Method.Enabled) { + $NormalizedTargets = @(@{ targetType = 'group'; id = 'all_users' }) + } else { + # Disabled: mirror current targets (we don't manage them) + $NormalizedTargets = @($CurrentConfig.includeTargets | ForEach-Object { @{ targetType = $_.targetType; id = $_.id } }) + } + + # -- Build expected config with all comparable properties -- + $ExpectedConfig = @{ + state = $DesiredState + includeTargets = $NormalizedTargets + } + + switch ($Method.Id) { + 'MicrosoftAuthenticator' { + if ($Method.Enabled) { + $DesiredSoftwareOath = [bool]$Settings.MicrosoftAuthenticatorSoftwareOath + $ExpectedConfig['isSoftwareOathEnabled'] = $DesiredSoftwareOath + if ($CurrentConfig.isSoftwareOathEnabled -ne $DesiredSoftwareOath) { + $Drifts.Add("isSoftwareOathEnabled: '$($CurrentConfig.isSoftwareOathEnabled)' -> '$DesiredSoftwareOath'") + } + + # Feature settings: each has a .state property + $FeatureMap = @( + @{ Setting = 'MicrosoftAuthenticatorDisplayAppInfo'; Property = 'displayAppInformationRequiredState'; Label = 'Display App Info' } + @{ Setting = 'MicrosoftAuthenticatorDisplayLocation'; Property = 'displayLocationInformationRequiredState'; Label = 'Display Location' } + @{ Setting = 'MicrosoftAuthenticatorCompanionApp'; Property = 'companionAppAllowedState'; Label = 'Companion App' } + ) + foreach ($Feature in $FeatureMap) { + $DesiredFeatureState = $Settings."$($Feature.Setting)".value ?? $Settings."$($Feature.Setting)" + if ($DesiredFeatureState) { + $CurrentFeatureState = $CurrentConfig.featureSettings."$($Feature.Property)".state + $ExpectedConfig["featureSettings.$($Feature.Property)"] = $DesiredFeatureState + if ($CurrentFeatureState -ne $DesiredFeatureState) { + $Drifts.Add("$($Feature.Label): '$CurrentFeatureState' -> '$DesiredFeatureState'") + } + } + } + } + } + 'TemporaryAccessPass' { + if ($Method.Enabled) { + $TAPUsableOnce = $Settings.TAPUsableOnce.value ?? $Settings.TAPUsableOnce ?? 'true' + $TAPUsableOnceBool = [System.Convert]::ToBoolean($TAPUsableOnce) + $TAPDefaultLifetime = [int]($Settings.TAPDefaultLifetime ?? 60) + $TAPMinLifetime = [int]($Settings.TAPMinLifetime ?? 60) + $TAPMaxLifetime = [int]($Settings.TAPMaxLifetime ?? 480) + $TAPDefaultLength = [int]($Settings.TAPDefaultLength ?? 8) + + $ExpectedConfig['isUsableOnce'] = $TAPUsableOnceBool + $ExpectedConfig['defaultLifetimeInMinutes'] = $TAPDefaultLifetime + $ExpectedConfig['minimumLifetimeInMinutes'] = $TAPMinLifetime + $ExpectedConfig['maximumLifetimeInMinutes'] = $TAPMaxLifetime + $ExpectedConfig['defaultLength'] = $TAPDefaultLength + + if ([System.Convert]::ToBoolean($CurrentConfig.isUsableOnce) -ne $TAPUsableOnceBool) { + $Drifts.Add("isUsableOnce: '$($CurrentConfig.isUsableOnce)' -> '$TAPUsableOnceBool'") + } + if ([int]$CurrentConfig.defaultLifetimeInMinutes -ne $TAPDefaultLifetime) { + $Drifts.Add("defaultLifetimeInMinutes: '$($CurrentConfig.defaultLifetimeInMinutes)' -> '$TAPDefaultLifetime'") + } + if ([int]$CurrentConfig.minimumLifetimeInMinutes -ne $TAPMinLifetime) { + $Drifts.Add("minimumLifetimeInMinutes: '$($CurrentConfig.minimumLifetimeInMinutes)' -> '$TAPMinLifetime'") + } + if ([int]$CurrentConfig.maximumLifetimeInMinutes -ne $TAPMaxLifetime) { + $Drifts.Add("maximumLifetimeInMinutes: '$($CurrentConfig.maximumLifetimeInMinutes)' -> '$TAPMaxLifetime'") + } + if ([int]$CurrentConfig.defaultLength -ne $TAPDefaultLength) { + $Drifts.Add("defaultLength: '$($CurrentConfig.defaultLength)' -> '$TAPDefaultLength'") + } + } + } + 'QRCodePin' { + if ($Method.Enabled) { + $DesiredLifetime = [int]($Settings.QRCodeLifetimeInDays ?? 365) + $DesiredPinLength = [int]($Settings.QRCodePinLength ?? 8) + + $ExpectedConfig['standardQRCodeLifetimeInDays'] = $DesiredLifetime + $ExpectedConfig['pinLength'] = $DesiredPinLength + + if ([int]$CurrentConfig.standardQRCodeLifetimeInDays -ne $DesiredLifetime) { + $Drifts.Add("standardQRCodeLifetimeInDays: '$($CurrentConfig.standardQRCodeLifetimeInDays)' -> '$DesiredLifetime'") + } + if ([int]$CurrentConfig.pinLength -ne $DesiredPinLength) { + $Drifts.Add("pinLength: '$($CurrentConfig.pinLength)' -> '$DesiredPinLength'") + } + } + } + } + + [PSCustomObject]@{ + Method = $Method + CurrentConfig = $CurrentConfig + ExpectedConfig = [PSCustomObject]$ExpectedConfig + DesiredState = $DesiredState + Drifts = $Drifts + IsCompliant = $Drifts.Count -eq 0 + } + } + + if ($Settings.remediate -eq $true) { + foreach ($Result in $ComplianceResults) { + if ($Result.IsCompliant) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: $($Result.Method.Label) is already configured correctly." -sev Info + continue + } + + try { + $Params = @{ + Tenant = $Tenant + APIName = 'Standards' + AuthenticationMethodId = $Result.Method.RemediationId + Enabled = $Result.Method.Enabled + } + + # Add group targeting + if ($Result.Method.Enabled -and $Result.Method.GroupName) { + $ResolvedGroupIds = $GroupIdCache[$Result.Method.GroupName] + if ($ResolvedGroupIds) { + $Params['GroupIds'] = $ResolvedGroupIds + } + } + + # Add method-specific parameters + switch ($Result.Method.Id) { + 'MicrosoftAuthenticator' { + if ($Result.Method.Enabled) { + $Params['MicrosoftAuthenticatorSoftwareOathEnabled'] = [bool]$Settings.MicrosoftAuthenticatorSoftwareOath + $DisplayAppInfo = $Settings.MicrosoftAuthenticatorDisplayAppInfo.value ?? $Settings.MicrosoftAuthenticatorDisplayAppInfo + $DisplayLocation = $Settings.MicrosoftAuthenticatorDisplayLocation.value ?? $Settings.MicrosoftAuthenticatorDisplayLocation + $CompanionApp = $Settings.MicrosoftAuthenticatorCompanionApp.value ?? $Settings.MicrosoftAuthenticatorCompanionApp + if ($DisplayAppInfo) { $Params['MicrosoftAuthenticatorDisplayAppInfo'] = $DisplayAppInfo } + if ($DisplayLocation) { $Params['MicrosoftAuthenticatorDisplayLocation'] = $DisplayLocation } + if ($CompanionApp) { $Params['MicrosoftAuthenticatorCompanionApp'] = $CompanionApp } + } + } + 'TemporaryAccessPass' { + if ($Result.Method.Enabled) { + $TAPUsableOnce = $Settings.TAPUsableOnce.value ?? $Settings.TAPUsableOnce ?? 'true' + $Params['TAPisUsableOnce'] = $TAPUsableOnce + $Params['TAPDefaultLifeTime'] = [int]($Settings.TAPDefaultLifetime ?? 60) + $Params['TAPMinimumLifetime'] = [int]($Settings.TAPMinLifetime ?? 60) + $Params['TAPMaximumLifetime'] = [int]($Settings.TAPMaxLifetime ?? 480) + $Params['TAPDefaultLength'] = [int]($Settings.TAPDefaultLength ?? 8) + } + } + 'QRCodePin' { + if ($Result.Method.Enabled) { + $Params['QRCodeLifetimeInDays'] = [int]($Settings.QRCodeLifetimeInDays ?? 365) + $Params['QRCodePinLength'] = [int]($Settings.QRCodePinLength ?? 8) + } + } + } + + Set-CIPPAuthenticationPolicy @Params + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: Remediated $($Result.Method.Label). Changes: $($Result.Drifts -join '; ')" -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: Failed to configure $($Result.Method.Label). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + $NonCompliant = @($ComplianceResults | Where-Object { -not $_.IsCompliant }) + if ($NonCompliant.Count -eq 0) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'AuthenticationMethods: All configured authentication methods are compliant.' -sev Info + } else { + $AlertDetails = foreach ($Result in $NonCompliant) { + [PSCustomObject]@{ + Method = $Result.Method.Label + Drifts = $Result.Drifts -join '; ' + } + } + Write-StandardsAlert -message "AuthenticationMethods: $($NonCompliant.Count) method(s) not compliant: $(($NonCompliant.Method.Label) -join ', ')" -object $AlertDetails -tenant $Tenant -standardName 'AuthenticationMethods' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message "AuthenticationMethods: $($NonCompliant.Count) method(s) not compliant." -sev Info + } + } + + if ($Settings.report -eq $true) { + $CurrentValue = @{} + $ExpectedValue = @{} + foreach ($Result in $ComplianceResults) { + $CompareProperties = @($Result.ExpectedConfig.PSObject.Properties.Name) + $CurrentSnapshot = @{} + foreach ($Prop in $CompareProperties) { + if ($Prop -like 'featureSettings.*') { + $SubProp = $Prop -replace '^featureSettings\.', '' + $CurrentSnapshot[$Prop] = $Result.CurrentConfig.featureSettings.$SubProp.state + } elseif ($Prop -eq 'includeTargets') { + # Normalize current targets to only targetType + id for comparison + $CurrentSnapshot[$Prop] = @($Result.CurrentConfig.includeTargets | ForEach-Object { + @{ targetType = $_.targetType; id = $_.id } + }) + } else { + $CurrentSnapshot[$Prop] = $Result.CurrentConfig.$Prop + } + } + $CurrentValue[$Result.Method.Key] = [PSCustomObject]$CurrentSnapshot + $ExpectedValue[$Result.Method.Key] = $Result.ExpectedConfig + } + Set-CIPPStandardsCompareField -FieldName 'standards.AuthenticationMethods' -CurrentValue ([PSCustomObject]$CurrentValue) -ExpectedValue ([PSCustomObject]$ExpectedValue) -TenantFilter $Tenant + $AllCompliant = -not ($ComplianceResults | Where-Object { -not $_.IsCompliant }) + Add-CIPPBPAField -FieldName 'AuthenticationMethods' -FieldValue ([bool]$AllCompliant) -StoreAs bool -Tenant $Tenant + } +} From fc080e4afdecd4076e5a68fe581c75515ce34f26 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Thu, 28 May 2026 15:21:19 +0200 Subject: [PATCH 62/90] new auth methods single standard --- ...voke-CIPPStandardAuthenticationMethods.ps1 | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 index a83592d2b056..61e2423c7738 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuthenticationMethods.ps1 @@ -70,15 +70,15 @@ function Invoke-CIPPStandardAuthenticationMethods { # 'RemediationId' matches the Set-CIPPAuthenticationPolicy ValidateSet for PATCH calls $AuthMethods = @( @{ Id = 'MicrosoftAuthenticator'; RemediationId = 'MicrosoftAuthenticator'; SettingKey = 'MicrosoftAuthenticator'; Label = 'Microsoft Authenticator' } - @{ Id = 'Fido2'; RemediationId = 'FIDO2'; SettingKey = 'FIDO2'; Label = 'FIDO2 Security Keys' } - @{ Id = 'TemporaryAccessPass'; RemediationId = 'TemporaryAccessPass'; SettingKey = 'TAP'; Label = 'Temporary Access Pass' } - @{ Id = 'softwareOath'; RemediationId = 'softwareOath'; SettingKey = 'SoftwareOath'; Label = 'Software OATH Tokens' } - @{ Id = 'HardwareOath'; RemediationId = 'HardwareOATH'; SettingKey = 'HardwareOath'; Label = 'Hardware OATH Tokens' } - @{ Id = 'Sms'; RemediationId = 'SMS'; SettingKey = 'SMS'; Label = 'SMS' } - @{ Id = 'Voice'; RemediationId = 'Voice'; SettingKey = 'Voice'; Label = 'Voice Call' } - @{ Id = 'Email'; RemediationId = 'Email'; SettingKey = 'Email'; Label = 'Email OTP' } - @{ Id = 'x509Certificate'; RemediationId = 'x509Certificate'; SettingKey = 'x509Certificate'; Label = 'Certificate-Based Authentication' } - @{ Id = 'QRCodePin'; RemediationId = 'QRCodePin'; SettingKey = 'QRCodePin'; Label = 'QR Code Pin' } + @{ Id = 'Fido2'; RemediationId = 'FIDO2'; SettingKey = 'FIDO2'; Label = 'FIDO2 Security Keys' } + @{ Id = 'TemporaryAccessPass'; RemediationId = 'TemporaryAccessPass'; SettingKey = 'TAP'; Label = 'Temporary Access Pass' } + @{ Id = 'softwareOath'; RemediationId = 'softwareOath'; SettingKey = 'SoftwareOath'; Label = 'Software OATH Tokens' } + @{ Id = 'HardwareOath'; RemediationId = 'HardwareOATH'; SettingKey = 'HardwareOath'; Label = 'Hardware OATH Tokens' } + @{ Id = 'Sms'; RemediationId = 'SMS'; SettingKey = 'SMS'; Label = 'SMS' } + @{ Id = 'Voice'; RemediationId = 'Voice'; SettingKey = 'Voice'; Label = 'Voice Call' } + @{ Id = 'Email'; RemediationId = 'Email'; SettingKey = 'Email'; Label = 'Email OTP' } + @{ Id = 'x509Certificate'; RemediationId = 'x509Certificate'; SettingKey = 'x509Certificate'; Label = 'Certificate-Based Authentication' } + @{ Id = 'QRCodePin'; RemediationId = 'QRCodePin'; SettingKey = 'QRCodePin'; Label = 'QR Code Pin' } ) # Determine which methods the user has explicitly configured @@ -373,8 +373,8 @@ function Invoke-CIPPStandardAuthenticationMethods { } elseif ($Prop -eq 'includeTargets') { # Normalize current targets to only targetType + id for comparison $CurrentSnapshot[$Prop] = @($Result.CurrentConfig.includeTargets | ForEach-Object { - @{ targetType = $_.targetType; id = $_.id } - }) + @{ targetType = $_.targetType; id = $_.id } + }) } else { $CurrentSnapshot[$Prop] = $Result.CurrentConfig.$Prop } From 1f9fb1fd96197138746753ebede0ed11c6df92cc Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 28 May 2026 23:44:50 +0800 Subject: [PATCH 63/90] test invocation optimisations --- .../Public/Invoke-CIPPTestCollection.ps1 | 14 +- .../CIPP/Settings/Invoke-ListWorkerHealth.ps1 | 4 + Shared/CIPPSharp/CIPPTestDataCache.cs | 357 ++++++++++++++++-- Shared/CIPPSharp/bin/CIPPSharp.dll | Bin 33280 -> 41984 bytes 4 files changed, 338 insertions(+), 37 deletions(-) diff --git a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 index 3c9ca9619568..b386e1e4413b 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 @@ -169,30 +169,24 @@ function Invoke-CIPPTestCollection { foreach ($TestFunction in $TestFunctions) { $ItemStopwatch = [System.Diagnostics.Stopwatch]::StartNew() try { - Write-Information " [$SuiteName] Running $($TestFunction.Name) for $TenantFilter" - $TestOutput = @(& $TestFunction.Name -Tenant $TenantFilter) + $TestOutput = @(& $TestFunction -Tenant $TenantFilter) foreach ($Entity in $TestOutput) { - if ($Entity -is [hashtable] -and $Entity.PartitionKey -and $Entity.RowKey) { + if ($Entity -is [hashtable] -and $Entity.PartitionKey) { $ResultBatch.Add($Entity) } } if ($ResultBatch.Count -ge 100) { Add-CIPPAzDataTableEntity @Table -Entity @($ResultBatch) -Force - Write-Information " [$SuiteName] Flushed $($ResultBatch.Count) results to table" $ResultBatch.Clear() } $ItemStopwatch.Stop() - $ElapsedSeconds = [math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3) - $Timings.Add("$($TestFunction.Name) : ${ElapsedSeconds}s") - Write-Information " [$SuiteName] Completed $($TestFunction.Name) - ${ElapsedSeconds}s" + $Timings.Add("$($TestFunction.Name) : $([math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3))s") $SuccessCount++ } catch { $ItemStopwatch.Stop() - $ElapsedSeconds = [math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3) $FailedCount++ $Errors.Add("$($TestFunction.Name) : $($_.Exception.Message)") - $Timings.Add("$($TestFunction.Name) : ${ElapsedSeconds}s (FAILED)") - Write-Warning " [$SuiteName] Failed $($TestFunction.Name) after ${ElapsedSeconds}s: $($_.Exception.Message)" + $Timings.Add("$($TestFunction.Name) : $([math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3))s (FAILED)") } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 index 2d5bdcc17ef1..80177f19e9c6 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListWorkerHealth.ps1 @@ -105,6 +105,10 @@ function Invoke-ListWorkerHealth { $Diag = [CIPP.TestDataCache]::GetDiagnostics() $Body = @{ Results = $Diag } } + 'MemoryDetail' { + $Breakdown = [Craft.Services.WorkerMetricsBridge]::GetMemoryBreakdown() + $Body = @{ Results = $Breakdown } + } default { $Body = @{ Results = "Unknown action: $Action" } return [HttpResponseContext]@{ diff --git a/Shared/CIPPSharp/CIPPTestDataCache.cs b/Shared/CIPPSharp/CIPPTestDataCache.cs index 5c32b057677d..9f0f639a1d00 100644 --- a/Shared/CIPPSharp/CIPPTestDataCache.cs +++ b/Shared/CIPPSharp/CIPPTestDataCache.cs @@ -1,84 +1,387 @@ using System; +using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json; using System.Threading; namespace CIPP { ///

- /// Process-scoped, thread-safe cache for test data lookups. - /// Backed by a static ConcurrentDictionary so it is shared across - /// all PowerShell runspaces within the Azure Functions worker. - /// Expired entries are swept every 500 access calls to prevent unbounded growth. - /// TTL is 30 minutes as a safety net. + /// Process-scoped, thread-safe LRU cache for test data lookups. + /// Shared across all PowerShell runspaces. Bounded by both a byte-size + /// cap (default 50 MB) and a short TTL (default 1 minute) so that test + /// suites running against a single tenant get fast cache hits without + /// accumulating stale Gen2 roots that cause GC thrashing. /// public static class TestDataCache { + // ── Configuration ── + private static long _maxBytes = 50L * 1024 * 1024; // 50 MB default + private static TimeSpan _ttl = TimeSpan.FromMinutes(1); + + // ── State ── private static readonly ConcurrentDictionary _cache = new(); - private static readonly TimeSpan _ttl = TimeSpan.FromMinutes(30); + private static readonly LinkedList _lruOrder = new(); // head = oldest + private static readonly Dictionary> _lruIndex = new(); // key → node + private static readonly object _lruLock = new(); + private static long _currentBytes; private static int _accessCount; - private const int SweepInterval = 500; + private static long _hits; + private static long _misses; + private static long _evictions; private sealed class CacheEntry { public object? Value { get; } + public long SizeBytes { get; } public DateTime ExpiresUtc { get; } + public DateTime CreatedUtc { get; } - public CacheEntry(object? value, DateTime expiresUtc) + public CacheEntry(object? value, long sizeBytes, DateTime expiresUtc) { Value = value; + SizeBytes = sizeBytes; ExpiresUtc = expiresUtc; + CreatedUtc = DateTime.UtcNow; } public bool IsExpired => DateTime.UtcNow >= ExpiresUtc; } - private static void SweepIfDue() + /// Configure the cache limits. Call before first use or between test runs. + public static void Configure(long maxBytes = 50 * 1024 * 1024, int ttlSeconds = 60) { - var count = Interlocked.Increment(ref _accessCount); - if (count % SweepInterval == 0) - { - foreach (var kvp in _cache) - { - if (kvp.Value.IsExpired) - { - _cache.TryRemove(kvp.Key, out _); - } - } - } + _maxBytes = maxBytes; + _ttl = TimeSpan.FromSeconds(ttlSeconds); } public static bool TryGet(string key, out object? value) { - SweepIfDue(); + Interlocked.Increment(ref _accessCount); if (_cache.TryGetValue(key, out var entry) && !entry.IsExpired) { + // Promote to most-recently-used + lock (_lruLock) + { + if (_lruIndex.TryGetValue(key, out var node)) + { + _lruOrder.Remove(node); + _lruOrder.AddLast(node); + } + } + Interlocked.Increment(ref _hits); value = entry.Value; return true; } // Remove expired entry if present if (entry != null) - { - _cache.TryRemove(key, out _); - } + RemoveEntry(key); + Interlocked.Increment(ref _misses); value = null; return false; } public static void Set(string key, object? value) { - SweepIfDue(); - _cache[key] = new CacheEntry(value, DateTime.UtcNow + _ttl); + Interlocked.Increment(ref _accessCount); + + // Estimate size before taking lock + int itemCount = value is ICollection col ? col.Count : 0; + long sizeBytes = EstimateValueSize(value, itemCount); + + // If a single entry exceeds the cap, don't cache it at all + if (sizeBytes > _maxBytes) + return; + + // Remove existing entry for this key first + if (_cache.ContainsKey(key)) + RemoveEntry(key); + + // Evict LRU entries until we have room + while (Interlocked.Read(ref _currentBytes) + sizeBytes > _maxBytes) + { + string? victim; + lock (_lruLock) + { + if (_lruOrder.First == null) break; + victim = _lruOrder.First.Value; + } + RemoveEntry(victim); + Interlocked.Increment(ref _evictions); + } + + var entry = new CacheEntry(value, sizeBytes, DateTime.UtcNow + _ttl); + if (_cache.TryAdd(key, entry)) + { + Interlocked.Add(ref _currentBytes, sizeBytes); + lock (_lruLock) + { + var node = _lruOrder.AddLast(key); + _lruIndex[key] = node; + } + } + } + + private static void RemoveEntry(string key) + { + if (_cache.TryRemove(key, out var removed)) + { + Interlocked.Add(ref _currentBytes, -removed.SizeBytes); + lock (_lruLock) + { + if (_lruIndex.TryGetValue(key, out var node)) + { + _lruOrder.Remove(node); + _lruIndex.Remove(key); + } + } + } } public static void Clear() { - _cache.Clear(); + lock (_lruLock) + { + _cache.Clear(); + _lruOrder.Clear(); + _lruIndex.Clear(); + } + Interlocked.Exchange(ref _currentBytes, 0); Interlocked.Exchange(ref _accessCount, 0); + Interlocked.Exchange(ref _hits, 0); + Interlocked.Exchange(ref _misses, 0); + Interlocked.Exchange(ref _evictions, 0); } public static int Count => _cache.Count; + public static long CurrentBytes => Interlocked.Read(ref _currentBytes); + public static double CurrentMB => Math.Round(Interlocked.Read(ref _currentBytes) / (1024.0 * 1024.0), 2); + public static long MaxBytes => _maxBytes; + public static int TtlSeconds => (int)_ttl.TotalSeconds; + public static long Hits => Interlocked.Read(ref _hits); + public static long Misses => Interlocked.Read(ref _misses); + public static long Evictions => Interlocked.Read(ref _evictions); + public static double HitRate => (_hits + _misses) > 0 + ? Math.Round(_hits * 100.0 / (_hits + _misses), 1) : 0; + + // ── PSObject reflection (cached, resolved once at first use) ── + private static readonly object s_reflectLock = new(); + private static bool s_psResolved; + private static Type? s_psObjectType; + private static PropertyInfo? s_psPropsProp; // PSObject.Properties + private static PropertyInfo? s_psPropName; // PSPropertyInfo.Name + private static PropertyInfo? s_psPropValue; // PSPropertyInfo.Value + private const int SampleSize = 5; + private const int MaxDepth = 4; + + private static void EnsurePSResolved() + { + if (s_psResolved) return; + lock (s_reflectLock) + { + if (s_psResolved) return; + try + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + var t = asm.GetType("System.Management.Automation.PSObject"); + if (t == null) continue; + s_psObjectType = t; + s_psPropsProp = t.GetProperty("Properties"); + var piType = asm.GetType("System.Management.Automation.PSPropertyInfo"); + if (piType != null) + { + s_psPropName = piType.GetProperty("Name"); + s_psPropValue = piType.GetProperty("Value"); + } + break; + } + } + catch { /* SMA not loaded */ } + s_psResolved = true; + } + } + + /// + /// Estimate the serialized size of a cached value. For large collections, + /// samples a few items and extrapolates. Handles PSObject by unwrapping + /// NoteProperties into dictionaries via reflection. + /// + private static long EstimateValueSize(object? value, int itemCount) + { + if (value == null) return 0; + EnsurePSResolved(); + + try + { + // Large collection: sample a few items, extrapolate + if (itemCount > SampleSize && value is IEnumerable enumerable) + { + var sample = new List(); + foreach (var item in enumerable) + { + sample.Add(Unwrap(item, 0)); + if (sample.Count >= SampleSize) break; + } + if (sample.Count == 0) return 0; + var sampleBytes = JsonSerializer.SerializeToUtf8Bytes(sample).LongLength; + return sampleBytes * itemCount / sample.Count; + } + + // Small collection or single value: unwrap everything + var unwrapped = Unwrap(value, 0); + return JsonSerializer.SerializeToUtf8Bytes(unwrapped).LongLength; + } + catch { return 0; } + } + + /// + /// Recursively unwrap PSObject → Dictionary and IEnumerable → List + /// so System.Text.Json can serialize them. + /// + private static object? Unwrap(object? value, int depth) + { + if (value == null || depth > MaxDepth) return value; + + // PSObject → extract NoteProperties as Dictionary + if (s_psObjectType != null && s_psObjectType.IsInstanceOfType(value)) + return UnwrapPSObject(value, depth); + + // Collection → unwrap each element + if (value is IEnumerable enumerable && value is not string && value is not byte[]) + { + var list = new List(); + foreach (var item in enumerable) + list.Add(Unwrap(item, depth + 1)); + return list; + } + + return value; + } + + private static Dictionary UnwrapPSObject(object psObj, int depth) + { + var dict = new Dictionary(); + if (s_psPropsProp == null || s_psPropName == null || s_psPropValue == null) + return dict; + + if (s_psPropsProp.GetValue(psObj) is not IEnumerable props) return dict; + + foreach (var prop in props) + { + try + { + var name = s_psPropName.GetValue(prop)?.ToString(); + if (name != null) + dict[name] = Unwrap(s_psPropValue.GetValue(prop), depth + 1); + } + catch { /* skip properties that throw on access */ } + } + return dict; + } + + /// + /// Returns a diagnostic snapshot of the cache: entry count, keys grouped + /// by data type, estimated serialized size, and expiry details. + /// Designed for the worker-health dashboard — call from PS via + /// [CIPP.TestDataCache]::GetDiagnostics() or from CRAFT metrics bridge. + /// + public static CacheDiagnostics GetDiagnostics() + { + var now = DateTime.UtcNow; + var entries = _cache.ToArray(); // snapshot + + long totalBytes = 0; + var byType = new Dictionary(); + + foreach (var kvp in entries) + { + var parts = kvp.Key.Split('|', 2); + var dataType = parts.Length > 1 ? parts[1] : "?"; + + int itemCount = 0; + if (kvp.Value.Value is ICollection col) + itemCount = col.Count; + + long entryBytes = EstimateValueSize(kvp.Value.Value, itemCount); + totalBytes += entryBytes; + + if (!byType.TryGetValue(dataType, out var bucket)) + { + bucket = new TypeBucket { Type = dataType }; + byType[dataType] = bucket; + } + bucket.EntryCount++; + bucket.TotalBytes += entryBytes; + bucket.TotalItems += itemCount; + } + + int active = 0, expired = 0; + DateTime? earliestExpiry = null, latestExpiry = null; + foreach (var kvp in entries) + { + if (kvp.Value.IsExpired) { expired++; } else { active++; } + var exp = kvp.Value.ExpiresUtc; + if (earliestExpiry == null || exp < earliestExpiry) earliestExpiry = exp; + if (latestExpiry == null || exp > latestExpiry) latestExpiry = exp; + } + + return new CacheDiagnostics + { + TotalEntries = entries.Length, + ActiveEntries = active, + ExpiredEntries = expired, + EstimatedTotalMB = Math.Round(totalBytes / (1024.0 * 1024.0), 2), + TrackedTotalMB = CurrentMB, + MaxMB = Math.Round(_maxBytes / (1024.0 * 1024.0), 2), + TtlSeconds = (int)_ttl.TotalSeconds, + Hits = Interlocked.Read(ref _hits), + Misses = Interlocked.Read(ref _misses), + HitRate = HitRate, + Evictions = Interlocked.Read(ref _evictions), + EarliestExpiryUtc = earliestExpiry, + LatestExpiryUtc = latestExpiry, + AccessCount = _accessCount, + TypeBreakdown = byType.Values + .OrderByDescending(b => b.TotalBytes) + .ToList(), + }; + } + } + + /// Diagnostic snapshot returned by TestDataCache.GetDiagnostics(). + public class CacheDiagnostics + { + public int TotalEntries { get; set; } + public int ActiveEntries { get; set; } + public int ExpiredEntries { get; set; } + public double EstimatedTotalMB { get; set; } + public double TrackedTotalMB { get; set; } + public double MaxMB { get; set; } + public int TtlSeconds { get; set; } + public long Hits { get; set; } + public long Misses { get; set; } + public double HitRate { get; set; } + public long Evictions { get; set; } + public DateTime? EarliestExpiryUtc { get; set; } + public DateTime? LatestExpiryUtc { get; set; } + public int AccessCount { get; set; } + public List TypeBreakdown { get; set; } = new(); + } + + /// Per-data-type cache usage bucket. + public class TypeBucket + { + public string Type { get; set; } = ""; + public int EntryCount { get; set; } + public long TotalBytes { get; set; } + public int TotalItems { get; set; } + public double TotalMB => Math.Round(TotalBytes / (1024.0 * 1024.0), 2); } } diff --git a/Shared/CIPPSharp/bin/CIPPSharp.dll b/Shared/CIPPSharp/bin/CIPPSharp.dll index c35be9216c3f8a1a482f6ddd2d4344dbcd13979e..79dae0140c352bbf55f95f7c5f9466d8def44454 100644 GIT binary patch literal 41984 zcmeIbd3+q@kvCr5Gt)EE(v0TNY2CJE#@2!43mc5FZ21BkACir^B9Eo9En8B{Gm;Nv z%Mu102xlPNB$$u|maqv4*EV<A}5 zz;aVJnr0deXUED}P_!5VE%@DRD2Y`HpYt1{+o}ROt$13=wC~pR?a%*I&D3@4K6P1E zcB>i!8*Fwaw^hUJX>5a+Ho*qCDqNU^4Fjb&1z}QMu`1q-P~8sryZlB5jqWLh^?C0V zGj7#F#Y1%^s*-sTt!{^ho-8WnW`_{chTGoDH7i zT)Jl;XVQc2eVj=TdiHT9J!tHcOybu^P$!r8)qoA~>v?y9W6Zq;&~^{2G{hd|-CeeK zxeQ~Np?J5UGd7jg4|Ac0KnspD8wmSj^;(~@Fi4bq$j&KPR7WU(`LTGO-G88fXJS?r9J z)=Xwk<38d>*KkW8X-6NaNcR!8?62t~Z0dj1N9szIlk+2b-Htr&OAMU8#JJt(%5-se zAmXaqfx%vWBLhc{aa(SJF7A)IBksB#0TyK72r_O9mg~|UQBTBEw*%e3{6+?jAmgX> z;#sH;_vYDr&5Unmqn8RdGrlnjFun_*<&^Qw;>Iw;-0{u%4bh!Fz6+l`wlJqxNXGHw zn{!*zE5?oQIa>Dk=KOz6+p)s`sO{3@`y2?fXXe<~x%Ty3`+A;zoo8Rqx3Ba0%0APn zjRyf<>A}xB;Z|0O!Ot3b@Uuo9{H&1&KWo&3-hHf55Bm0rMvk#>Ko_C_+5nTF#DW)@ zGxvEwI~H&W#xO%^iMfBvNh(}&sfJWX8bd`8HPF}+W0;|e2)pcvz?K=q3{^zvv?Bsr zY78?}5n-tv5!iBLn4v@jM34)B>%&|VL!h+)C?ZI!@VREjFhdKInH@n?fBPy7=T}YR zmk-^U(_5r+<9iENPxTgCI#adN#1N^|_!uHp8XrTXMiN78-I;1xi%_l;wlcM{(Dvzh zX+2_&S7Odp=(knrn8P*sYx*r$?LX?b9CNq<|C*S?mH%sE4twLTiaG3`|1jp%Rj7^- zH6ljcjwpKYjSQUN#JC;BDs|}-QE$Xsx1*2+890KBpAyB8*F9|_)+K^q&_YHr1OcPS zVF&_7vBMAqj1q?-$WDD^F)Fo>#r4z@MwzchGKROzbL*vt5~Y?Ry{^bw#+T}QM*$|(GxGCKZ~^(fA% zN4cs-`RPmGxP|J<>#y~bt-sh3Kb=7_G_vk>|4H4aZ-f8;%lIE@gVHm%!T-LWbi#s* z_etRh#oEz>_YGpiM5t(AO0Ihq8AC9vkC0F?ScQ(x?YJHbcR%v#DfD>Lx&**v)vJQ-X$8lz=N6Hp8=*X!u z_5I%194uTKq9a(ExRZBfve&nJlTT#yM@Ony;GePtrhAjGe4s#h1d9WA@{gH3K0B5s zVVcQ(%rugIBV7r1NnZUGMzSTBbD@!3m&=)BB!_c3Q;g(Sayf-Y^7pwMy*SS+<$LII zl+Uk`S{SI8Y#RZ?UDsGLWduFe{ho0k6P#MzCFTyC4iITlkKQFsCSC z9!nm&Nfkq`hOb-`}WG!vu{|gSbgtPh-cq^g&6xr z6yn{tTacoCJLS4psf-~|ar|N`)@7G(((_>ROM*(_ds^{(nl$qWtjTV+d$_jU$W}MX zDKQtcjcm%5xi=%jDI+K)Tg82xyjfImO3ckFCbA&(W((e*(ZeaKtVFKN0~r}k8G+tx z6(7%(#VM++Ni4V}SMU=VJ)EM-+L|l#U`B>hMtX9S4`s^Y6jjz_7TlIA_)taL^F z=gNFCBf}{py}8K`XUgIfRThu5k-l8PPh|u-CFWsg_vgy|O{OePiNSg^W`rq&X#q2v$F`BUk1zmO(0HIAsKp zF1r=~mXncyWKM};P}Qe^nJEk#Glhwn7pFtG#4v{Hr~OX4u7xgcO2<+dH@m{PQ5425 zQy80WVZ_+u5gk`vB_$~gL36YQ$>Nw4Mxr@dn@e)<%}V8xvPdp2i{vs>7{BHym&tqd zv-ryEe?h>AISfI-nCdVDxzJwSbE>_hk2bpro~8g|NV3*T0}*Qz-R2&1dUaxC3 zrpTtiLJTo2#0_&zgi0jQb^63oNpv@QlXcKo?@7+Z1)0i(?>T*@36dB@`<<@5@>meY z#1NxQoM*Y|A7L{%VLgWUWj2)VZ$zD9qrj)|a@(jY=B>3cZDbnPHA2=Hk{Zt_7P}hB z*h9t+;A&V;B5ScLb`6Mam*#n=x7cc2*0o?w+25Ql>`dkEZ%B(MdrHKfI>Y)5bn`aZ z0M6xDO8`(O54EFC1?f6923%6NMvuFATeD`@B=rT(>9Rh{7q|5}zKHQWEwJ&{Q(#%w z0o&h{HbJd5(VzG>g5xtsC7i>xYRKDvb|ynP)~2XFh4C-?v2hmtW^rDzE}qF3r&tG( zH|nlm9ClkDX4K<#P1ldFG$NkZ^`NDWzHOIIpDg0w-T-dIP-PZ)OE%^`6uPaCO64fK&)0A+n~Gm!U;r{R&* zP0&*}$|)AO=KdC7s=v4_aCzU!b;cm0NRE$0C46)%gc|Y<>o#1c9CuAQPRK%R7f~ts zYsqyENS?p9Fbgl`pO12B(i^bOF;#yNOqxe9@w;HnY?WYQA7fG{3(Foeh1E-L)GFYp zg|&~a?*E`nDXDl{3@aD(Qyvs)g3C)2Tx6QyQqu$%>=4sNRQ$OM+D3UKDH2N;bCmru z?P7g&I9;T;n>LCm*F0|e8VqmjL7>O>p9=}QDGlo|l&&e6 zW;7T&Z?la3=Vek0*AzQ#rla-FzLb~6-0p5F(TECj?R5>!Lxbjd8(qcqhSxfTvP_o| zi$iI?Z-lGef*~Z8vK~TW!)2<(`n6J}sHZq;ggp^M)rzZOeG+1g`tdfYiBQ(VU`5^N z?gqNMN7X3oR;HtTyJGw2Fv{J*w?T?-zR%^0eG2pl`#knHz*1j<}S%@Zs`ctr`h)G_O@feRBBGQpH7$;sff$xN+L!X5l#7> z+sAqWI+V}J8k-YY1hw#)q~8^#cx-Nq(j@d z^bo;+H^MR_oVWq=g=s4$nkETOL6W6&(d!z0!w#1m}t-l91>Ir)aV_yW;*b(-OUI0~LPx2;C zMm#o07cj$}{A60*2pfelj9#;$DQqO)O$)ZNppo?WoSX~6s5kv38v=gc%{|kvl~`Od z%Eo24r|PqWh<@XfzMsM{bgs&y9k4ZN>E=NBYfMm*hX|xS9!}-`VdrUrqM8v zLccZh4C_m{Tq(O^~%P-b^n?F|+(^pHEF(KR90K8-mFidn0QC6R}34 zcw5wSe7d(VhOSb7^ETJE%anp`mxDh+SBeW`VMztcJ4JF(*n_~O#^4m1x{5;;zw+p| z`^n37;}~`$IHg>l2DfWMi>^-_VXxaxEfPYvj=sShEt zl{YTVt$T60?yiyzncCZx{?QpKJx#aA>ROsOsBA9BItTrn%3x>eC~PrD7KoAm0K9R+ z2)A}&Oo+e;x3nS}bS&jqP`93iptKMoGq_&6b=qtTMT1|Y>g4F$BZubF9OXvXElO2- zx+>2>rSu}ZK2N3VgHFt0S*=10yA24Zujk(4^r;oJ`hHb6Uc>QzcmnDsu~|)d*7ID= z;%#}UuLH?9qTb@DZ_W#FLf9MjeG(HxdDI{A$1wcN>b<4K7S^_A)C`+9_mnhylVNoH zdUx0?xi^|u9L&BKseCg4+ms|%1saF%WM@5dc(mDdA@L7q`>+XGB=i` zZbs|Zh4Xl!XhaK;6fUs-2}$9CI$qHk(NH*4vVm802D7k|GoZt|!KOdL*Zd9a7bEN| z*%0-GePQp-Jr_)U|NZxi1I4&~;(4zKake~SsHocCU?D? z$F`G#v1ddhST@38X2axgBpMBSqJ@z{Ves4>3`aq|h~(I}S;imngd?$+fMV{=%4M7> z*LPGd!_JjcpVg>O3JYPZ)^F~ywlLzidia91>2|()ozR+&;Z`5>W0wM27NZi<$BXUY zw5&L9rG$}=^}I8MKsw&@h7)=_0j)ifln(#aFw^L$`{Zg^`}sOD7bAvuK1Q9-dKtAG z`4||oGQI=IdjP9&w7D3#JMFFe>NI`eU+ESwnaphMH{$1N-!B?X0@5q8L z^(qvM&P4vyYmCie?4KD!`@2&Z4dy7%$_nE-h4-$D`O14&LBLqzFa!Z(slyNi3ZtNleSF9Lt+HBpXN|`cWt_1rM+gW<#iEp`l* zo0fyTF^-d#`EMrWW?yTUjteKGjyvW4kP-A@?$4NesT^SlLd@o!^9e`pq%FsL!DX5} zFBjH_Q}BXm%)J^JM=-;>8)lDS0(0Y>8NJ)x5R)4;4YhLO-!MT=_i@Ny#cBC+EZ^XH z;}@K{1}y7GD5Kc=F(98fyooFBa>ssxYvToaah)kQj&$$7AqeYx2LU6R9z(hHZ(!*} z`Y3jJihc%jBWaomOpFxip*|<8^#7)Qs^MeY;DBFz1DKIuW*1{MZ;f_sbtb(x_ zj8!r=lQFJBY!+kHz>bHE;erkmisW8k>TS^b(B<4D)w>L?Q`_&~AR(X+BjmdqwyLZ3@8Cs^`gxf0{tu&AP(;i~ zUhEwZVQlWgc~PG}%-XEqG7BpY>vupJO~2s32adddVAhw8N6^jz%-qH2W2C6Xk3PH? zndcUuZS>)NK-J+Clrn)cz7j;3Xnr`q`n*!>k5E%rd~Y-m`xCHeu!J9=thfHmL_vKV zYmL7!iZw>^5@aj4-UHdNJRFMV2_yDCCr7X>C zfM_@zj=2~K9?ze0+>ZjVLNOcfR6RCafJIz^QxA56s!cc;4y5p8oN0LgZ1)&UdjXO? zFwOD-n{wKuaK3nOir;bm?ri7hMNH-VJSNZAmr^KSE}=)TlH@|>f#Q?3DHu0FJIp)T7+IFAx(x{X)MetOZ0HbzH5L{*51DaO%UH5UtYp^7XwUtYfljQ57p? zm1ezHnw{78(FtlimvI6fVHEo&fUwG$Q&T@FTw_%*S{p8p)W#4OO8deO#~I0Waq%-Qy-ZWn+zU?kJN=*GgZf63cyua zl~S9%;uw!y>ns3!_ZP#17r^a%3gHRXRM63iy$axWQB_zTrX=1GoUdh*Ghhd5Og^|Gic?4H6rTqf z^ZA<1=K#fgiPkqFjvb3m=J`}ckxU-%6%}*mDwOE~7uKNf<2wINo<+w(0bzb>M%mYz^s|(j4TZ8KtA=i&<9SCR6 zXFeWtA^3+%Ae^md2sG+Lf5bJ4tE1ApU5o{T#tPTMa9l$bi?iCVa>=%T8ZB(UaG{3DmtXmK z@0{7x+%%(kMhjLigwI>_0=|R3F$ojP#(JW+CIN3BO!o9|NwLHVY@B?4@Yc0c2PW)^M~ewW~i7=zb*M1Ou6^g?*o1cNq47~USbqpX0Q z!qUaSDaVqiK}#d+iVV6c%i9H=npNzwlY7xNQ3 zmr|@V|2M)}D)3^_`GDk|U%)ai2X{@li~hZo>+*6D=Uq{{YeGA1DPzw0(SMj|P)g6o zS>j;uvEm?|sAaejxIur`c1`%2o%eN(t^Ex08q^@Ae=kr1&m4oz2Hk=BM(Cj;#_uoe zEHr5hI!*eDhO|hP zcx)ES7?~}jR1tgZBKXsw z{K5@@Ps73>eJ;Qr+fem-Wsv3qqK8N+sVbJ-G>J9*9JLG5cg44hMe=&Fye!C;*9qtA zMXcw}$m`sVDG%|6do6eE5S1fh%}rC4h*NVBWf7Z@)*@+szGDmOtCE(7eUt|3{px== z<|2+xqA^hYwM>;#lcbq7Or3>KGI83+gG^1sXQ`OFRjBE-7G;L$kupwOOq+$eUUYWQ zwQL@ta;kl*9g{njCZlay^gisG7a?GWV z&}Xvp-AsQkX^%0#eOz-N3*0C)Ns_xlBy&WwFEUv5u94=?r zDe%rJ#-|AXkqKu(|4EZexh#)MTpu_asmsf!18%N351ReeX9GT1&hTHV82+Jx;X?x7 z6HdM0YYHFMTIh*j8}bg6t^|Bz;yS=TMK?h{Br-J4F5{ zM~Zs@{~Toa;Yd&3n$cGyKERJheb`0>^BMj+kKs!K8_a&-`2sH%_<7;~TmISLHwc`T z$1-;b{(!jy_z{7X0oHRz-Y{Sx{Gidf!s!&rVU#sPd(yZDseWxC&8Am9AI>YLU34Sh zr5eKr1m7?C3H?rRek7dP^dRtaG=^W*KLhx*i}ADF3=g=O^J~E`@i6Cp4|D!Sa82?? zHRjwQd9RYZdG4=*)9L;$;Ah+?0cUvL#?bBy`2f30--n$KY3}?C#}E8K5vM*G-K}AM zK<_eWdsPJdt&zPs%Q9bz=0StY#qj&C30R}8aZd*PlzS>**wX~K$1?+vOM%B+ za{xaqoIiQklfQ5+%+KyowKRl&#eTamzZm2CVx%t0+k{@%1`pTLn7IL*1R%>?37I1l z&tF}`(ds(i9ln*=mT$1BwZXaAUvE@YX|ilCw%ogg`Z^t~IA*x$d7JumpwHu`KikyC z=t0cg+iD4~>wMnImAZ$LHq}=?*W;n9Y-(-!j(iVYZBtiQ?F4m$O<`>h>Z3Mw*Mw0} z_bLju{K@5^?+B%A$)jr~a(Q1Twk4lx5OtVhTk@%SGE>+2x+kvG12o5`3aaON0<=I; zQojIg5K8GRpikKp>kQE`n_`_Ix{yO4biO=cr5>iWHg&vet|v^F+0+fFUzj%8)Jv#e zn6}x}Pb-E&C2i{FicwHk+0@%r*Mhp*rq);70P03X!Ojtsa7-v=XBqwD6t+dxeFEKq zMXpo#3G_Q`bJ)&zQHOGR&!)`aE&1i-ovP9vubS_vpaPpZTy2zmlff)LF&zJyq0VQ`3v@%&($zZR#(T59e3YLYoR#{T--_6oon%R71T&-9~rT zd?~+%zGGA06>28drG%O1DWUGLsUOdu?iw z_XyISwy6thp9fWegI3nkH}M(wB)Z?G78ZRse=@n7Robts|0RD4Jz!I9Wk1ZHN-qd? zC*2-+Gk+TW!KTjg{w{wy?QY?Gchb{k`GIq3IZjcT`bJp@)Q5$-t#r3gzZ2>@-`#=v zo^$Cvn|dg4Xa2e5#m11^@2#SPbRHGhlveye{&`etQ@@&c%sr24Z0h}qg@JiARZ-&W z^J$(?chb6U=Ad2wW(C~{J<(& zfa3;EyS939U=5ApkVtyLq#x$D6U|d;Ev2DA2i>hG-&?g8fqK}c-Y;JT>a#X=i_s41 zMVqRs;Ivn5>fJKVchaVgS8>{}Z0ZKl@<*GxzLL{C=S%6n`Vi}E6Y4hF>0?`F&R2H+ zFmeefH*a9jm-ZK58CXxBwyEaouD}NR@B&WbKG7ezoUT~Jl=92v^f{rf^PN@R=eeAo zv8g#YRJxp=wJ8_UuAqOisUoCZL9g1>8x#6GSJE+?dK)KFSJF>xDq*ZLK19E=sjG}s z;6wB~o4NpLo%EhftwmZVdE3~s>wFI&Z6g)f)FVjSNToLQZdsovPBk`VqP#dwwW;G( z$K0E!#io8%wL7qh&b6r<%8$9bXrWDgtbBK%i!QRM`p_z)o7!#aoKPyzO;^~|^_9om z3F@|~yDE1F64d9UdG`f2(~wPl!uw%RBR2H~q-~+=Z0egx+d{Y46iw{&Y^A$wDui>a zt@NNxU0i%epobo{sg1=S2lb?)WK?aV=j^n71qW#xybwQVLQEHQ}a;5 zcKWGJorEWP>ECSXZFr)Wes5D>tLXFe(fc;_ZJa0ek#DhTl^JN2eyUZJ*wRlkg}R-d zE_^J|PfKk|i#{C~pmjp2ewL(oR=y;w4KEFeu)my z^+?-IV>a~*{hNWS=%7u#1{mRQ;}nEw@mfO%=O;61bIWY-*ePw}IQJ!KR*Z zQ}Cm-%%IL`GzvQHy z5b6NFlcG3yFa2Dl`9>m-828a{Z0eint@qI%ZOZai1n(nl4cFm1-$tSQib8o4k=A0T zg?#fpAE!k^snS1AtJ0DN&Vw!y>HvL<>Vtebq|x0t?9pjGA0N_l=>tZM>an}liSK^m z*+oD%-G`$f57kuj?*2ZU`Q*y)!KzTFC{lCzt;nm>FOkeagMk@-LlZ}5vN?#y#s0vZ zG{>dg6&&ANxQR6v|^37jjiRp4@g zYXn{@Fb=3u51@hBC|OoS*9(3dU^(3*@MXXnIte%hr6tQ6s8%9vtwh+VI_GTx#QB7r zDCNtE(hf)(c-Zgd>MH>cR&2rdDKTClcp>(TyMQy?Av?xaeQNd1G*ix@X39C#OgV>| zDd$i#+PJ9Z`Rd`g}0nH)Wp?6O_NUiz}(O0ymkn2sYRXDK+R-frnAzUT~6SSG!*YG*GX>LayDc8ir=k!|qo7xxh2-!}@i#UvtmX zo{qfeep37P#D50-m$GB-qx$O;Pk{d|@aJjk3+DoMmxVoT+O4%K^%dGkWUgn0);9?! zFZ$t$^F4lj5@LOa*m;Haju>NcB*W6bK-VON? za1I6!A(gGYLf}zt@`M{Sw(Sd^UeWMPJzDmxXHf9H+KZ7lJbSfu6W{QBQ-7iQUp*&9 zTd#&s)*5@Y0}~D78d$heFVnw0vBW6T?<}e`dbPiJry7Ux$owJ6+hPn#y?UjNgV0Hk zVIS@lI4F>9TL?Z|epLT-V7YNr`$gF*<7NF1h24P9PS|GrT>D1B4&$42WYR9c6O;A> zz5x72)CG4+;I~7Drr%oo2sq?>9FVm=rax8wBqSdxIA+{0lCNt=CcSUGu6-%u_ug;Y zwsF!H@57S%Y4LWe{>AERy`L8UKP=vOSR|j)9*@lTJf*!+&9(k;;oaV+kn3^OeGl-5 zwOJJpdA|$}_pvJ{e#P4>I`;}36!@ILpX={Nj(LBsA1wQ&_c!{d3jYk8d?DWpQu+&0 z?yHcT>pmu&Kk9t0bWHe%X}$NT_BuUUy~sCJ^OarbYth=UifYvweXD`j)}SA1hl{#= zty;a{yuN&kIQ1#ISzky`(V%MypvV0fX)Yd>ag^otzX^D?`%QSV)c+QGV1xf1ZAA0!#mF;~`ZfO{ zSaqBBnATtL4dCA`e9^z$b(Z%Pf4=@`^)LN}y07ege}&E-sL(k|?9ko~RGT|&Z7tq9 zbFxS_h~!L>oGFqV@ve67K#8l&u&cXlojFe|Z_|0iwdp+iK22QT9a=0n-*X!*+3UVd zn^1@uP224Iuz8y{rSu@JK$Ljgh@&p|o2{0$5Mcf+m_l69LboM!-dMF5pGN zZwG7@ZHuUj`U@6|&W*S~)L#%6ewXk^g)=6c1H!omlCwh(3jdJsKPmhp!Z|9OXR*ti z9eP%Do)pfHh4W+KXd2hTrLl}lOWUvz_`FMfrkVh6?jshX0p87 zWJ_uVZxz@koVeg)0uKp1D)6L0ciw%pCRCfp8d~$X_2c0DuwYE!A%RB)o|IHApHpk{ zxewIlv-|?VTfzTDL0mXv0uKr2NIq*mDx4F7pA?*MvW9jEaIPA`YXhQ9@K(V$3LY1k zQNhQA|DfQ91V1A9QNf=Jd>%Sa2jit-{|Z_^7}K1s)N2 zLf}b}p#s)ZBe1qW>=e9J@Qs4U3+^LVXjJeq;Xf$&A;FIbenQ}z;LHwDNOTIU4RH^z z4RH@&Ae`0^m$FgtxZvG@hycPlB%C9HpAdKwd4E{&rpQoOY8{qZ3%)>LYnWv=3LY2O z4Vl@YQNhOq9ujy|@}7jeE2KqOTdlxWfo%~Pzk-he-=2R+U`_M@_XmL+1&#`QP~Z`P zCj?RfeQqV7I9=aBJe~JTS&#?Pk{>rZWK7G_$8dW zK;TA!qXKJ6S+jLvGjW!2+ zyz@}f-M$u_t*j5u2CT1Se6*bL?(*{h@2ldImzwIuj8C`_aL+`RiB_{rYdPb;tYZ8g zk$*+xe^J5mAFW{dcdAwaURKcoI4gK5;8&`y1bnkL4mi7*@u^h_z`qGF?kQ%zYmx=H zcw&n2id}%W8^eGT1rJVO{x7STe@{8X`zjfp7h;)tuy7)4=q|n)@a+P&ZGmv!kzC)c;M9YnGu!f+V*gho zcO&&hkxY5-2i`5P(064}wGyB8c<}b(k3U`O2lS(rbhK6gFc06EprggYfMK+wj+QF| zEWw|SHZ21zL%ZtuTwWz$Ilf;<$1c4Vu#zSLR^cm>n;F2{0ChYoGYj|`Akla>N*#*=FrzENN~@U3(a@E$-N_XSo0-wvqb`MXuX z`v7%3W4Q*n1&A*Q!#7H3IEiQnp2TMsHLQ%*0Uv}d8Vv#Jc=F~_;JW~I+6`OqoB*Is zSHTtytE4M|?}aTI##$%v5!{>6Xg?rMPqE(8aKh3B{Ax-7KLDuXiJ&dOuLIQSAne7H z27o$FVYUOm0Z_*?K7GJ%1k~~5js^G`SgO%y0d=fxQ@}q5h!a6rs$mB*1pF{8)#(}7 zsMD8VolakdT{?Xg7KLcNHWhG#R*!yg8SpS&0emj%Y~h^aBXl>tfZg~pdXwIz_wekK zTk~q=+A8f5EvXG@!`ctD|D)-8r9MTUtuN6p(Rb;O>*u;|aXsq#d)Iedru$>==iL)M z&7Osxi#$6#U-Ep*^Ut1;aj$XG_=7R%-Rr%@`*+?~z2&}1zG=R&f3km$zsG--|HuB{ z`Tb_IIomwn>@kPU+sy}`A@l?HD4%#`zdnHzJ2Q*%Me}T&qMgc{jg#wBd5_7tUvAzK zIrYrt-H#K-Q}ZssiR-Dn=f>$fV*UUp#r*vwUijDx-+D35{1|V3L`f4P&O}5Gun3~+ zclcvGxc)@+7<4zDqUZDQW_~Of*QyeiTt8jnma9?fk?S8s9+4Q&%JZ4|J0-aAIG6k; zy(0NdS|s`L?NgE;PZ^eQev`JA@XTP+$r2nV;@U3xD+u3BhF7(AmFTV3_K6>AZMqip zWAtV1dFs*si5}EnqT~9@^b7qp;NPQW*AHl`>kYcj^-uJG>m_>5^((r_U8CLSuEP6Q z^ij`#?aRh~?Rn#6`l9y{?IrJ%+E2Zo*M95$f|lp|BHk}*m;1h_z3=-ZnrYtB#9&)bd`rKT8tmyx&EJ$xXixNax2E>=cg^nx z50ZAKGv%=*T4r_vYwg-G)RRoCu)4OhO2uRa(`R#XM>5{EeYIuvwkLWMU4uPVKTD}J zkvb)7iPazqhA9(V9%g_srVZLMwXu zw){>?pGhQRKKr?$85 ziuVlK;!H!w+C;p2Re$fE_JMdmNa+Ebv@$Wc)#_e2)YA*4T-GA1Zy=sj_~IQy@!p=n zJz2c8dRszSHfV9)OdVL!V4}}<3OjfDnY=rlmiG_NK(gHoXA-m};9y|sX2@|c68f#R ziEi}JuEF`U(DRhX`LemQv$fyq-_vIer8@QuB$_v#jyoOPB}4sPxLlOjvp(KClvo|_ zNrHrfyM~fU^s2TV>Dlq*9*`b5}Axa1L!r40f*TSTZNA1lai4 zhVj7?ry4k8qvT8HoqLA7;HTiFq0}No4mJUdl~(sqZ(=^pTb=CL86Qk6??dt)NaKsRsm4f*))G{ot(PGRgeNdVsH;&1Yi`0;@wXclt z2F6MiHfvUMYuof$a~3XcZk{>&oEeK|pR;gb^Q9>-5ZAkuJxG)(VqJeKzB#d~pVN91sF$55L$lGBBzw?- z`n%D4IOD(o9Js_vq6aXoT(-I$k=W|p2}Y`OAeAP#dOU7>Fppe?#Cci7{GQIvB|V8= zaCyzJE*0OB$l~$HSA9B_#a-Nw4+JH$IZM!$5m>WX?L%E%iBu|wx3cFHMk~VjPFc!i zXSLn6HqmG8jAMvoY+T+CMo+g4plp76t&`2fsL5KHWT&(z5CAh;m-Xb3yt3jRmo2hZ zMd55#hc&PaE4yqK(^-0Rhwc3Bot+CYwDD0Y+vBGW*YOfEM#r-krANT{H03-V4yQ>w zwd;=;Ii=5!7g%Bq^>>eFXL`!`G@dZVbK&*17+jh;UhkrK*VaUL>)>fqd9IO|Dm_=G0EMmPo=nRI z2M5#wocd%PPRmog?q8L>WGiAW*6LjeS`_c^N?4(LJ(0b?q2+Bo=n^U3yzrX)bekGli*~|PFOh6nyuTZwJn5`X zS_2oy`x1mg6CDr`Kd}9YZm8;4JJJRtkeTG&?Ar z$X>kIf>f%@8c;33V_nJ=gauY&GZGg{Ez((Fd1`q-G@(&fZB{v8`>IXbV4jd@SCt=A zh>by$-LEC3OZ9QV+p+b^@{}|gHi$j_@!sXw{bR!sPbIKc=uPldIagV0$3QXI!Lfu` zm0UM~Ne((C&{JDt)6kYJiR8khwM&{sT!QshOJ}DzL{=6(y@{kOu#gjj0dw@GA?Q$< zxiK?5XJeb;j4L9O)QU;FZ&UA{j-J769pl<6lcOV^gy)ywSN9UTtmO7gf>X+}o^C8< zGJL0qC8)!C1R)Hw@rld(H_M6#iSgbuN;w@?#%e~%+QjB|tg{C9ti_rKe=>INp{1vO6mw>%1W&y&NkwbuN(MIjUvB z*uN)}V2>2x5B6;8;gf+(DtCA3jB&ArarN~`6WG|F;8%4?2>)k>nFHXH%(;Ub5)E`0k#*p+4sfipzpG5LK}Q z0-pBW5=`=#C(#oW8{0C6;K|xv@n@J$AI1-5Ht{+$zasTg5bsXC!B*m}WXF-!vn=PfT+% z+r%`>UKXe6>`h{tdFmc9oshdlOtZ6gh-u!b8^m;i><`nFEOOHnk8!m-OtaFP!!#$o zH%xP6Yv@o;6l?A8UW7$Kk``gm({i<;M}zL{!AkrL=fjxw2Rl1Asl#IGMAVU3N*xP_ zHg86*P3oeSwX#5jN6`P0_DWY+YQ>hs+F)}W$NzF-F<@b?OLVJF=m@m)F~(quwWV2E zy%buV`8Mm+8(c(lIo$w?P5{V}0O1$X_8Ks?I+4WWu@h-Ln$q(~QbmB=wn;BfvC>jA zg&f8+)zqb+Tvl2LbYQlooP(&9DW^YY-HJgd!@@V40eiDaEU|ML9Tx8`ctLA#746ZF zW4jzidMl|$YKBA@cLbLttv(0GT!MhOwS9YHm&3{llWJ#@u7fipN`qMg?7tqIvN%TDYQ={$9;v21j1?|BG2Hk-FF-OkYwjLd zq;j^!B9zNvd$RY&%3FNm=_u|@*d?&26E>)m=fpH6wQ`P`R9ZTmh#4x9h;ke4?2{uX z;x<^>(~tEroaZcI(8z4xO6j8o%2Oj$eaM#zeS)VV#ehvQIMKE1GN;+02=WI16so@y}XFby~6PGP`| zbYi+)c(tKgS1PMWUe98(S9){eEMXmlWutR4r@ZIfFG<-=lSOaEL0KjuJKZ-kiQ65k zpLZ=Q5ukdo0Oc4oJu7~wW{vMouP?G@e7ow-p@)d3k3ispbV6?EStV-$cvV7N`v6wg zX_lH4o%yoKJ}r@&X5-H0+~M&6IhDsXIUUc*!|Cj641sKxJX)Ix41WM(ayrAV3@rvi(2W3J+DJEjp@yc>XPV9$~q8D*HRN;(|) zRNZVxbE2C%$1{v(&RNvb%9el*8-tZL)?ux%b|sQLqOh6jA0)XI%t5>tWS4!vk~(?$ zoWl{h*6zNZ{?)zl{333lvj|CDyxVfp5@}Sr zsoV)s24iVBh}%1h&}{)#LMQHHk7J6;w*HhO)15|D``dgardB9v(HxTRJ;8^Ac4~*c zrGcFk?izAlN!VQVDU5kp zr<|($W==h+GoD4TFwb}D)Kg@$$PUDR*}D?jI)8bk#I4-tsK*qEH?Qb)Nbk# zDE4Qc%&IEnaMWy*QYW{{$-MTm4{IfZEXEv0$JRvu!adIU09w+yZzZ>%;R1EtWOeTm zZFodL^*%;9hlf#rtW>rmxfgHTPlZAY1*FpAj>2|4M#mqhz;P%37&=9= zLN0O%3hIHLAv`jeoj%=8?*Kn8x(8535@jt0#0UQI&S;yFmbUO1;xeRSah;Vu(@t*( zH;(rZ(iTCgJEzQ9kg_Y;s_N)ya;$dhwHEnyKnq)(T~3yp#-d8aVi-RNV|#Lnauh8C z|5OF+mX+Y2>N3YA?D*_FPUX3cccOaSzTHyIQ=7$c!(u#pIRGhkIc^XG;&8^!n{g_; zgKb%Xv@SeFc?oppT07g8Xtg`RPH1wZGkVU+xOGMoBrTLSAXak2a_49Qrw14}YYOkp zsGlQAMch!Q=Agw+`l*@*M9UzwW;$jQGz>u}&b08iJ)?U$jv&(1e@oL@TTpHs_1gNu zwaHtWmZPan7-N4`t{H6k7WkFO`QWk6S|Jp_czlejXe&DLcn5(cKMw zyTo5y&LDpy@F`y9mfI{XnZWbNv+*3V(@Ny!K4;+zSwARGTDt6vEp~Pje9ZmiuWaE9 zxJ`Os#{gP~!w1`*lwQZ-;bEvixSu>s_e#< z`{NeWkK2yJYuYFHYMYDkY&F$&Nk1NfhRu?fM{y%4l~=XChOfR!AU%#2>gBdNE1oX# z1M54fPpnbVpIaf*qG?O&xI`x$v%RO4YIQu%fxioOa10#8v(!A|9Up$6Epxd~E9sQ@ zYKyd$)3ZJxW=u_?yiM>WM_vy2?8nnu>Dr5rPVh9qzTRTXcH?U_*!lsYi`ro^>){yL zgt*LYrM%Mz9@nD@&(d>r;V8vrsi@n8mTku~@|*DNyoyCsv>Mj%1jW6M<#_s_Oa6N$ zw)nU3*3dTb<#*ig^G7wSvH>yTRHyrxssy(tRtme)V=!m*HeXT1r$;HOxr`{`t(jiUVR?%pW0e{| zqE0sEt=Z7{7bU|h8USEGP>*VU{J2wOtS(qt5h}trF0%Tl9(6_W^LZ#j$J>Rs z8*fj9i^~Rc3MH%+h8}W|Qphpco>eca?sG`UkJy;oJhla-*@tu<4(UYc7qbJ$Azba;;z;XM0r z@zoI1Xh_9|Em2}a+q^~L(H9}XJ%BN=VQd>`K+Y%yP|CGBlCDS5!3iN_6>noPJcGPb-nq(g&Us#CZs z73`H-l@|qht)d~T zj&QeQGSHlaU=C$Ewe*=mb1DxC=~(7?Sg8t82?aHVw6`@rY6lXULgV__|5dfky_W_a}V$TBy+ z;6CWV4+e%zj3jj7$TFWqnNU=daq>EX3YuE?`FtMq$MpuAApn z73_Fe;(>PYO$7v4JEjcC^`#Yzrxp` z=z-^Xf;g(NhOYo-1}o6#g$>2y@$l$3VY_W(n;)SlGBy?&o2xoTkiE{fsrFJM+hYZ)_7B*D=rQr|7r5Ro-)ArA+#i0; zJlFTaaP<8b8{b&th6~8;1~8W5Xbj)t3KT1RcQ^C#15NL{cQf{{_ynZ8c}RYZqJ}>W zN%-bnd<&f6(+%CHS|4p^L@veqN{$?uT1<7tjAWQBqh=c$xzgdRW+{%l_7n;Np;R|2 zpef--g!`3l1pIKC3MZUR#;MB)Z-B@=B-tx78^bgq{U)nWLCK~+$>3#{e2uZgoT|nn*01}IyD zIwm574uddqFzsU63x)}Chhq$fUBnCYFm!iW!RBz$8-jRis#tvGj)$dLmmaAJ`b1@r zR}>hrTnf4ZI|4xF*+5l zVgyHUdje-P$ZKQ?)*N1aCP)>`eTX1f_%TF(LG)0=ic!->3wZo;49_H~xhE@wpi008 zWmv5sH1Q&UV^D<&1WYs1?2hpoA3$TEGjK6*Gw?7l7nvZjj{cPzp)cX*Df!G0p~{GBb$P52ecj$mQ>VOM^Q3rpC$EkmKqkpFRJyw%|8-@JBR5qb4EhTV}V+=$bRPWlneV^i7GDmN}aeT{Gs) znzLz6qB}8r_RLLP-Ltw{X5xFY-8=-QYt+d4n{w}n)t~tTkA&aOnta+U>Ca}_A5JC= zGI#;LP%juSP5}+$(f#-Z0>3RRr=xAQyykNgBZC@}1WWjMq3+?sadL91(8kCEI#?SK=NL_2HTFWFjF? zVsf7M>!2ev5#J$EldIzYQU7ZmP`~erFIpYVEke2q?yQaHv)nSgHypw5*r@KvEZ{Q% z*Hb$_sJI?;`C4Ghae{a;=;eS*fb;7s?q9zry_q|sywnvZd-CG$u0n#%z@-gu4O;m; zZwb!R__UMn7s*K>((r2mfSH?Zd?xLjWFi5t&$B59J*bg=K8f^2ux7QKn5uKd0r2KY9>-cf&rabSkWY~Za%?+Y z-ev?({w5Sv)Ah*9=a}kb7OJTQr?$=MHp_Dua<9)$<1@BM>77Xhi_)%Jp;Pu;M+`E0K4I<>1fFWD5e?fg2Wu$;bCeHXg))q zjX!SjE>N=oTR_RrBtU}OqZMtyr^$Vg>4k@C&?9qOCSwP;&;O;b&#K?0yZ_w(OYQrA KX8o^v;Qs>($pmiz literal 33280 zcmeHw3w)eqmG^nynR#dOCYfYra!)Tw+qBc>Leiv{KnqQqh87!oNm{6i=`@+P(V zrnN*HG#8D2%6idh?KzrLldm-sT?vk%xQlPYvj*=w@g^!3Tvv88gYB2sbAaIU%SAh` zVNw1s^R!83;q~0DL>n2olW3S7G4=HjQ2}_5KyJp=vNxh@hs1s(CX=b^GD(>Khp}y62bF z=btHK+?ov)AFV4_oy-qvb^ARm$H0+eT*-xN*id++)_%^hpL6Z!Jo{N^Kj-uFhiT7o zHF%C|>7H?}r2F0DTuJwP#<`O2H^!xs`1RxHlS}++zy|pB0t7D^?e7J&-NPyku}69L zpzU35!x&>I-fid$FJSd!+^8YY3IlDpIJ{8!Y=kk!(1Jrvj9$I)*(hU-q1C|r@^GW@ z*+^rIq16N+qbD}M54Q2EW@TyO)|`=KQ_nJzVxx?tEuAscIuBKxanIOkA)}<(88fYx zEOy39YcaDIag4Y%nRbYg9>mCubd0cNZze|A)ISv?brs6V1wp-Te?G?&11FXkw_~nS z7k3v1U3L30*{g44;K(s<%T?*({*XK9uG?S8f(#r%#%;lBUB)Bi33}@GBmApxWZ(!g zJ|&7<(H)NFHhyO2H?t9?!p+QYkHldSv`m@bEN+Z3%$?s{-w@r|^Sk8UXIJM$g;bnA zzq$4~=J#?fdwz5MH#2su@K23hdVVi~FneaH{aj{0m)p;F`+2_oyug00;3xY`r%p8u zC_VXEC)~;^G5J{|Pkz?Olb<#6<+;+As`_w~)>9Pa#^$vNzeH1RhFJ)q4BcQbjV9|eDdnIqsUPN`}Ki9vq zoc>j-?p2?oHRaE@{ui`YW{E$~*%0T^oqtI?XCJr!QSJTLk6XnXj@$oRf9iyE&gZc} zkit0KnBxHMAeD||rr>#4kzpj=dYcOOr038!ohT1=^<*zkuB`y3tI=;H-mBAawY3d& zLtp|craSR4I7ad^u4*Jsri)p4wu^-?SHe|Oh4a0MSVo|1qJjmEfupn)EPs+0gv1>p zBQ2gq=5Iyjo7sA#q1UqX+#oVuciNb;iD@#sMW+1?W!8wy71=VCZ1eTtI5y7~nPYD# z^BM-moA{#0*!`NO=O0Aoi8qw_JCV^mS^b*9_H=;b*z;bIxk6;5tUpI8RujDb9BB)7xHx=AjvK%4U?aKLlBAdFQ%TNuh5VJ=a^ydHVz zCe;kN8qS-TgEDUF{M_1a&(!7;RlB}%0;{BmUyv()MMj)UY|C)Obi1J&8^vtP-oe5! z58dDr3vHS67d$prRg*~NP@ZE=ZeYaR%&<5>L=!doWMpiTiRl2Gy!QY37ag4eQ37K zFv~zKWVnPksM*E)xfps-%q3w=s`@1Am`RkFN#sS|_hJf}Va%cWMZb~IwMM{@z)~0w zyTW)-6vi%77@KY#Kn>18dU`KcUY@FnzF9Zm@kJ8 z>q@j~jRD|R(sfpLg}c$4s6*{~Pof=?s#}>I{wn!|G%cRXcqAGSDXvA&-(j-KWMv zm-MaC<1X9Vtl2$D!UIN~%R(YRJFpyiA2^oHw9^Keh;nejL0-zgd=Fri*6^#3|M_s2g(EuMN1ZYZ>);U5oWo z-A2$8z7Dkb(zkc};&~zt?z_MZ8mi4AZ~5irveJ`BMb}cY_z?yFotdU8KPf zo#xS&1M1@ijsZAMUUm%VWs4^2(F1*A5Kz{XeL&6_7$;$YGz>#tBZ`F;l3*$6YBUgeXGs!5WT%JznDhUk20 zEKP8GX@Z+f6WnT=;D#My(S*t?AAq(=o=J+lg-Q0yBJmk^8x(}ku4-e6TTxi^sz!80 z<0TV|!Le=w$E;syShoYgg;4l|c=Grg4J9H0)`!3f>egT5G2c)z@cmP!(O?)V0vuoK zG?SKW;ccoqd#~Up$LJQQ6JLGt5H zyPsf$$3c^iH%l-MkYO%oqk9~&Pbx8qm30V=Ql4L9o8Sb~Xor8*rm$h@L?u|*lUMO0 zEQi^KS~(pxRLMyQ2clVARv<^12G37k=E^l-S)W21W!9$wIg5V=PY$%t;@NnSURGzy zAzSy3BTU@7I%;cr*pK=1lP^JuKE?(4`eA;YDV>j*te4ugpyzYYBRki|O<_zUW3mEb zVay?8l5GxS0vVHRb{MnAn5ZYXP;39i=}ztdr@#Il*APBKV`S5Qsp3ch)qzt^7c$WiT zfSxT?4seD4jx})Jb5jC>Wil>3FbWfIgHgV{e9hCS5?%^hm2dg7RGiD#tIE3_;$5z! z2i)WR{8T3a>>P^Nz17gHk8wc=Mf}Y2FV}-6_OGswZ3Tsc!8j6%8{{^Aj4=_$j4{UF z!SrEoP34fw3D0ckYz z1b+xP>YiZMmrn&T&V@naOj#>m1iu^1XZ0}*nY+CRW229~6{uS9p_B>ikvgzP3Kawj zW?xWYeH}IG%03t>3_lDk4`n+) zKWHlF=Z8WVfD)x5R2nF?xP`{bK&k9R#Y4#-f>jqPJF6Xm(AkX$gaReX0(;=jKm7XZ zub*}7^s%?0(`C)Bl6qt_#s|YsqRHe_0OLDABgUHM8Wk5H4hR9sthz?u{4ge!S?>uy z!*UT@Zj&8J`WSPpe?VE8^^bs|%3x*qBqJ5UitvwsGC?JH88CC1Cvobi?5+!tTW>@XM_K!!6AY&JI7v$gJS3@M$3EMMb(7v&MI` zHq{F(j6v?jKVfp}V_XoLr619a+O-H-|+J^NQUcJ|E-Mcxe{Dv14{~dGhCw2+a-7 zO?(EkE&K~m!MSz~tkLzsx#52W57S5b!lRk|C4*l9T$$CWI{ECXEX*_4dIdl+JmFsp zZlBKrRo1_O4$Vju`8e;q3Zk(rFk|+Z_3w=41AUvI^;&soW)eGKqfVJ)%~QD8!Tqx4 zGXomK@|?+^--52@&ufkRpS%Y%328K>k8w;kP5A$q1Q@Vur10^z3RFDSdg?|HcPj&nSI=tcELIpb5QJ~2h z9@L1|qla(96Eh(FD$wPgkIM1H%;qzq29&-Cxj3G6Ag~t5i~0CSfZl~?IfS$IjDS80 z{_pTatf%!n=O$_fUF^x#(+QgSsVV$Q$X|~(K5FpEN4>b+Zs;At`++HZOeuN^I)0eN z=UQI8e+u5qcy|F`p!O!caJeh|AM6|4OzZ(AcNc*6GN(C48C+{eVK&W_|}7PyX06P*8@iQcWT&Zb zTT^pWYjbN0axLL|Y=eMbnoTqp&CjdB7wM{j_oNcB`0gZ297URK^MSXuhrYB%`F`%j zTi17@>|3Ba3sJUu(Avquc)`coWgq!yeI5dUey_Fih7BP=@4%bKnPcHJ-BW&DOIGA67Gc>=F(yfwkgpz8~llo<4?z)b}YhKeWw z5rZBsIOaF#$Ax*t1}zi#Y60T`!S4}xOgP)5?veu5^WO4EEg}===Gs;S(Z1yJ%*qL2=Q4gNykdom+WS zWByU${77g1Y0-I?)V(a9WqxZeDY=#EF&zww6|>BTgG)+!=!3#}m;a_J*oj(x`jGir zv7ZX67+we5pigN_O1@^-eV4}8{tR^udJKHnVD5xxJ`I}!()q=V-(JM6%z+Ii z{aR{$#?PGh1P>PZDKGR|aTV2+JW=kar-R(e;VQ26Pa^X)ESyEpnPq`lbc%IUzbEu*$Sv-gKgp#QB3OGUfl)-hsDs^uNJqmC9++_K;z#D#gE|2^2P+3QrpN<0> z)Ktc$*TWKwX&LuoW;NqQu+~p!FbaP9jL3f-wpG!^Cfj+F)E(DY<{iZ>v%ufM!A2j( ztmoK56yc)evk1!eH1#=zUpw*{vH;2+k+M9#djYi>_1f{-MmcGq+A6$8J0f{59Rc;R zN~S8PNy;vgvIY1aCzm~Ha@it$la#413bmNFunF{W(Ycf^Cs%DTKF8n|mN5m%7G$lP zXcImGW2z1|--O$^lu(yo*0$3gI*?UwAB{`dMkyPjNh(DPyU?SX$U=^-rAF^+z%|}3 zz}E%;E5Wbw-UCjF?=yf&-@SmBdG7*1bM%@iiZ}`0l(vqV%Q`%4?xeX3iT7c$MJ>%OvxvuCLU#s>FPj%i>+U=R0S4IbE zE?}d^@D9Oy1W)RX;5;OprL+uqzQ%B~-Uav;7voSmd=Irw@N0Yk$ zOw3szbql4|hh1C2`L!zsxY~UP@Eh*y@UiHkf@gdN-50ziPoo3chq5?#0v1 zD;j-V|2|-~aBlKEgIeRRlX=>sF)$m-r);H7J zt!s3-O^uqfJR0p#RK?w)c6`zFkWk;Cw$eZ5x#&@wnrUwFxakiz^(p^#SU>jG5?J!Zkrk_F97vdHnp`R0P2H^f-S#ydFTg1 zDO>XC=p1hE8^pF0P|aLLu`LDEJddeseeG4hsd)J;Pa&;Rl=QEVE*DDaETX$@ ziggy#vo^&#i|GwXwVm)H`j;Ray<|Mn%ESezfqcP|D6q`qq55MfJUkZpLPbDYmnUeiLTuTHiwS zp_zP4CHdR}`z??x9HuYlVb+}ipv8i8IHkmVNp-p|Qc&=v_ zwb;~miWiu(=zN=smMt-7(`uU8k^BV4bB=YnX23d>gaij zT3z)K+|IgrYm2hq6!qgTK?ZGVUPa8DPo5T4b}%$-E~GZ0ZlQ1HjbOXJ!={!S*O`my zIh*=^=nnIIy0?w%-9o2A$3gu{sACnM66zf|MDQ3rmN(aPKJBupXYv+6(z2hTNsa5o%c?H$mRH^o&`3`EcskiB`m^)~tO>K3(Xhx{ZrXB;e zlQ!B^jr$dIfG)SG8{IFOQHt2qtDtt#UYlz7ykhR5q)m-@UNmEL*rp0#%U-(1rhZMY zn1l2ln_3K8hUiwCszB>;y3?laas9!x=suhJqpKi)m>#mJZhXUMKRs(xPq^pgC+TNG zoz{+G|C^%WmF&UO+TQ%dpeAjK%SNc6D^oT?RYG0s<48V0b8Lzu`2f}16i4zwYPBhj zQ4 z)P7?bsGHLDR)YGNUG_y|uIDhiqfsrkr8=RYGq-SQg@w(5QP?1!I2r<-mD$3ve5N27Mwpi`lEwY{1pKMNTbJtjTk zQVpdnvqQY3;j#Cb?yyTA^C>jiEWxLsfbP@@>2 zfvkIXsFbc3{1{+0-7fG6z#2LYI3KOu9crLj$tbmwNebkYR3N9M{U|kX>rhc~1z=n0 zZv3Sq<1+*=!H(}BaEAM3&vR0HSNVIWP0m(r#AmBE;%4;6N3_e=NNX@Dt?>U&GP!C2a%7;$GCfL(}wQI8WV6mqW5cD=7FZU~APE0IQ2m zXn8u1Z=T*>bse45zUO~Z`vPh`ubtG|N}tpo1pWu@5qwoJPk&7Crv!hNIzlV;=V@C- zxBd$%DCh%Zoxh;!qPYI5NLK2u>TC2r*!aro`}Ima>Ayx_sQ)Pc-+}+3>NUV0n=@T* z^zIK@rG86!x2saGu0G@{5Kf7}83N}4{@&F9*zRtJ%?7pWuNk*#9s1?|4)-H;bUZFi$*{yeKGyStXom#lMTaV~RtLA!Q`hVmb z_={ogMX&ZzW4Y&k`epe#Pp@dcpMI6U#nYx$RBiEW*6%Rxa`y`UcF6aG^I7v+l(MzG z0@M2F@@(gT#(rR(FMz`>4nQaFjHK>i z(Q_1>cK40K*{}0?;YQ)#PrHp1+DUq~yu?_jb%d&o7VYO)|2nj%j9TE^Dh%q-HW#-U z9oi1TdG-GVap@PRR$onDAg`+n@HW>yG%9fwL0kW5+${aN89jQ*_-pj&kH%fvSIYz5 zh_=0~+Izizs;Jg`v)KRF$OdygCpA~a0`F#RzNgt+slQk})Aec8?eKmK))A;1?aukyyw)-`Brv;PJueV_M) zrePi(hi%8Sd$jBFZ}T44K3eo)@3-~k#$DbAwO7j@^gaSV{J{H|_~9{lpj+RsEi`}S z-EZq$ZT!ajlt?}+lFy6e^CHPJ_nq$jXt&09y?$$GzV8=e|EuEpSH^`Q=D=MaATA_El@0fNdcpd!#{2|{<=uy&lLjO|!)xKBsTk<)Q&yzVjhhu-*vd>2+=qrGa($^vR zxJW*MQXl5iCn5i|$e$GXpNRZvk$(a5J}T6{30SMKe4X|fV59bgua@@FiGn)ZJpVRt z0lvrGUf6`&yAR|y;nwcMfYtOBz`67dzH*&({2ss#(YA*A zsjpzI=-h$tFZUHhgx@dxN#Pt3&eg&>3dse9cMAWw@INN}hlF!NI1l4Yu%Pf^(Ro@p zKNZeTg`;WQ3zx<+E{*$AE1bE)nJb(Q;j9tP8sS8Q)333$M+CoG@S`Y&JsMknT=*Xo z{zJk!A)JSW^PF%_3+JbzlXR}7>0*!0n(=WUwABh{u6{LrrvRVm!NLyV>=5~gNKOiV zMDRNWKQ8z~f}arlv_Q=@20gU`I|OzDE+~u$enjALfhPn$=VGf)3yv>TuvPB&<7=31 z_lKyeutqqm1nzLN&65J}6!?(9=LDV>NFJ8a1l9^4uO*bk9ye7yF4GF#==Jg zKWYfaV9rCv4Kz^jh;U8={x(nZeu#chP%E%QU_{^%FPGlwy@75mI4+zM!Z{86#|4^? zOWlCqDX8(WZMDLgC!AHn>F}}S4#6W*Iw|-O!S59OxX3&t_zB^k7O3TM-P$}EHGvU< zM+6=hcmgs%D|iI>cM47m$89ox1n{?cCj_1rsO5i%UM;B27i|Jha3a8en>Q)=kwUirPQj0hs{wHnzd+M5U|?R@a#N59rQxr^8t0x^}xe`I_O5=ivV@d=K*g3 z)bUAA3-Hx|I<28r;GKXvt)(Tvy8w0ChEH~M+&}QAO$PyWI)t@GqftN|KXbAY_!uD0 zLihw!$N8lL@M`EQr03CcA-#w;{q!^7etH>rJ8hLuc5a~C=rOF6&qD4sI)l%k+?rRb z);4OFX$fsa8`FNQ{hOxiGxhoUQoT#ROh2f9NOH z+dQA~+~;}N^9#?bp4UA8Wc<64=RM#17Vj6l-}L^-tNFY>zwdnC2H${h`E5i?$vw$u zxa`*}I1Tfsw$AG|-#U!vRNkXF8&BoEhMH4(JLR4uSKqgALY^x3FF21+<(0`fIJ@p~ zUsBpAKr9vF?Z>+a(S*|nV#h?}h^d%ca{}BeH!Tw#xMvXBpmzrNQ*8XXSO6c-;dv(D zmgnU`k3282+uX}voPZy?_=9U`?vwf^)djh}Ngt5{wgz$8 zgXi6WyXisg1jY1k(w+Ku>6HFBy{tb0{7Gtd{R8cBouq4B-=sTS-=#-fFVS1vFVR6y zpVnZ+v>zDPYfG`~{3+h0zLT`n`(tefyJN+L?R|Z%EqyJ`wBq7usxubZ9k-IHSby@u zo#}#}Xndd}IU4W3Z~#0=+Lg{#$ChYm>jT!&zkei_h;Fd@_pwUFWCe?ta&d1W(!Xz$ zWexU32c!L|m=$L!RVGr)a-_CIlOuyE5s?BBS)LYwws>;bN~G2%5>`UQq|i3FrR7|` zM1do7E)gXG%OHDBt8G1tm*q(1Sj{3Ma0FdCGB_C7IT+p1LhIMYM~0#ahuWAJh$f-~ zIm|Uyd{=Du2-v-mVEABhac zQlnYCv_4xxnUb=kZYnyI<(tLla?WB}A5XQS*!JSN1Z@fU2UyyRIsQRG+}aWyK+yE3 zE?k0OP!8kAuD-sGxD_8AvPP1E9f?F_bbUOQ;-Tw_T^U`8N_Jc4st$h2+82#;Yb-rA0$O%t zbhIoxmklVe+c}pA_=zY|M?4wjrnthgzP^Kr$nY}S9ZmIZ?d@8gRs!s-$&w|e8rZr+ z>ZR+ppQA4LNqA`_xdy-g#wLK#Z4HbJMlYlln-Z}DkyLd35W-^!kq2*D@y=)}5*tij zNK2M9cXTdZvV8U0=H|Ag%UahgUAB64^OCNv*4CEAOS{&tU9x6r*V5)StJ{{ZUftEa zymiTvwM*A7?P_UX-PPIA*-2~GZ`wqCFtMLfv7x9nlIn@}Q_pA;^P*{uHE3t%WYfjb zcr+11k+UMumS_ZPgZkQ-Ob41iL+COjD^X*3% zgjEy#yfM)+Fu?gGn!pdZ?TZe`fUHj{Jp-83NN@cqhm_Q~HJ*&@if)W^*Eqx~#-dB$cj#XpJFZUWxJ*9J)4fVXUvOD;6CDmlv|khT`Y&>C7wq*TV#vMJ=v^YYj_>D7}+eQ zv-GNdaDZI6udi=4HVF7mqV4T2Ya~7}ot<&`^fJk;)7ib8RyIXO2d&7!B}wc%rnR*` zd3HXQD@hdV5TMaS@@y6DQJh|c6$p`@nqFXcdSiTRJa%Y$@tR2gp6EbF>a3+a9c5C8 zcZfqmu<1 zh@zQjFL=^rwm3R~+Htj2-4NRq9J*rB%?^F zgHe7eUqlIsbz@@dFcJftl8Ex7GrDtR_wHz7b;3F*-V(F1HnjBhiP4g8V}sFzEC#58 zs6<}dIg+l-KF;u*ZEl7$t%*!g2NK=T&cV^%SSnk`G=FAl^hOeJUl)E(HG0rW?8_85 zt*na;Ai-z&P7__|!!}G`ME&%__3>S@4xlhHcupxNj%4a&lx&Ia>cQ%h8r_1`JDK6j z?ryDG!7qiqSw*}q#ncqU7)1`LOr5Ff7`-WhEkS=OQ{Z@4qTx_hfm1_P$M|Tbh8?8B zPsMh|_%x6y?Kv2Y4zJ(UIif;HawnyOQDr>E>`ja!Ln!HoQH_6#%^=M}_*^jKn9B5e^@3#cE_Z+s3O}6J*s@GDR4C)!#nUrs-Q+Fh_N2bDt zXnZ%&wef-EWicQK*+fdATN5!F!8^GxHY|t2HG87{`zY&F>P!k=zNjyTHC`~DlYBC= zw*?!JE)$VKvWELEmWh(+MK0Qykn^0Pv@txt(D`4UG&r2Qk)%zC$1OK)}dxC1e4tj=+xfu$(b z*SAw$Z^$;m-XEaj2#`cJOD*CFENZ(VxHpnB#IS`t8y!$l;t2Hc!6KF9NJz7?!U$TO zT%UFS0xoiwoHu~-uB$t82y*uBAh{`;Kq5SVG7j`~PE4o?nCtxX6%bZ$l@VpXWdCBO0%x)xL!7iEmba~*W&FXZ)b46vyd&RD`5>ecp|zBnPX4SzUV=Rm6K1@ zMl#*7lcRZ}@pgc_8pF{=W@XlFAlWw9Q^Vd~V1D9kYs=b`g!-^A=JSy#a3%`%asEtG zoR!lvMwMkqE>v@e`iA7lB>n4-#j!U@+s!i`=jkEju>*EvSrd%<_C-fI#MAQWrK^dT zgCt4DS4K{mgXn>rX9t`U7$?y*&MR~z7FRWMCMmBvST~&WnR1|WSA>t0*aMmAOMQEA zT*{)SpVK84*T#7t){U@_;Rwu;y*Mj2op4Pr9a)J!IVD~x1eH1$KarKkcZ0|Da<8IV(f*mA>_3lv)@Sy7!PPkrB5##ckP0u zau1V9WRkc@h7-rawTG~K;nM=PA7Y%HGc&sdj2D8*K9~ubId!Sk)0TiCHU=APtk>FL z9gHS8?y)tDr%3LnId=?#?6+@JsgL(BIUJGe7#NDhHw{MOX}QQ$j>uh%`?@{qa+dmV zTuKvCy208Vi#w%*S%j3XJ!Cm$(KISCCl^x6V66TrT!F1Y3<9cxK3uL(V~Wboc+!y> zNTX_WZN3syt5aGuhvd6C_%LOcu8;R8~Bv^x+mK60xylj;IR^*QU#M0Gry(Yq%aUp?xa zN3ipDF8Ab`8s`o5+-VJriZ*t!`8pC(fV-&&P-Nx`4Z86 zf*%px!)POcw$=hB1kY$|O-oxej6ZL*&=Lkd-CCPn&VRHR!FvQ{Yalg{)8-OL*`4fA zeRMQARy+OLf_nR*g)Po*C(BJoLQ2J27@vZ{v7DwHMeD$ys({_n4gOS@IWA$xXV-B$ z&trT5-Q)2ckZw*L7RL>1@p=C+q}b)S<^~*sGgRG-)7Tws%LbJ7xa?ae~G3AyU}h0{o3=!y~)2gEk_HRFvs3htyZ>tH+&R7 zt9G7zJnC`rrSdT4^GK*V)bi)l=gC2Hvuhj_-2>2fQ2fR1r1;lBrg)V{ZkLQ?6yG^n zitm~@z9lcmoQ1FEyiuLBbmcim?7SxUnB(M4j_^f1CNbE7132noQv=%*5_LQ;8Gpm* ztKO7TZw+cV^{D+nt+liR^NwRaj+{G)7T7A9Ie;g}<8Jhi$Bxr$+9%#lx)gtPO?CYe z$0N|NOX~70ZUm+3s?pbqI>Frq%_ES(SS^U8TYSU*&K(kKRQBgl$c$*(k~(hDNylvO zS*1E0&vWAMhaH>)Q}~V%&v?g&Z)i(9$F!18im!IdSUHjP203G467B7TFFEsa!e>98 z)k+uso+-r30Q-8kEjxg(ld<)~_==f&Doa?C%BknkUTEiecL6eFKW05g7xy%UHI;MC zE{w@?(gIVOaFq>V;2+QB#X>CQgcHc`SMLxhl;=WK>Zy*Ua|VCcdWhfJqujp)pR0y650F5B zNxz^g8TBw4j53@l?3o^eIg`hHr9qz_qLAh?LWH+wdNqgTEe%e-q=oQhC$cebjn2aO zBA=9SW4;g>UeN#m1N?eO%OjURIQfcy=8WRfU;z||w2&Thh1?;0DKVtu?ZVrQwLJ+V((%h|ArE>}EVf856k1XmEP;_36GBGFi>3{{y=C0| z^kTay!+zrbhA~Zp9y0WhSI6&Fmj*ZM{+WJuXkhYp2I6V*bfKb7v-5T3>OZ;-6u7-w zpbg*M3QQjNHf#84h*|ilIy8Qmqz=$ij-S1~#`vXVvd=4Y8#)ZM)ez?85 zUq@djkGTA%>8a7R85KrpFrxeM^H45GKuStQTvL)h(-aw>=|iUjqgs&bjN{>}A*L~q ziVa&r#D*^SmIfvthXh9eV_?JB>FArwucs34dr$!6T)$#X27{+ zW@*7<{1ReXH{0dQGvNZUIw-B!1c#5`RQ+IE*o!&+C`$K3i53=PMy0#!gI3Xy)knD7 zF&Su1fj@^bonHD(zqybng#?y49nMsZsDh#zL&n>gU`;%G(cL#$9Z@FL^WY=@EckO~ zGk4<8QNi)&^O=Zr_`43;4AT9W5T@51Tww+#PX<@G@oP_h4{2txiRpqk4X*G>Dk%w(;7y)H_yt$Y_Igd94-|&sGxmT5SE#WHt_ZFu<~VdpIM&&r89~e( z9%3|)fY#80%$aMzxx%*MilQ2%s}O6sM)C&ctM22ruYkC(fO7+ek6*q}jW5U1oJRcY ztBeRbjvktoUIb*uj!xT*&LDn`O7j)_?8ZBDtnGBH?L;~aiY$`E3;5zPp!|0l>QNij zRpcT*I9e6yGhO()ZGqPfcikSC{6KK>4x}9*x~B$uZa15Cos&)REX`{|S*F_3?gSEcA>n2llMrsK=}N~@An2L?+Z@e4~Y|4N%;8{e)>hv zOuwIJ{0WUUd<8J$5TK6=8;Ymnfyr;fcH73~d5Epxk(I$C9yJ^M`JAg@8;>^koUK&6 z>(e|om@5{Ch~o>H^+*ST6kG(|Ous8|WId;j$$MO;=7(N3C(jNgPJ(PIR!RI1zy64vg8;9zl@6+1dPn z=T+VTBJ#l_L-<2h=^TpFz7I^}6l510yhzO=c*+M$j_lXaEN>%1RHS@xM}`YOk8Vet73&T%>e7Xvo~4+DdN7ju2~ zR?Vj#ydeRlVqW$x1;Vf=E=DD|oxxB6VGgU^h+ju`*3d!xXcYDyNazSFLf+sUAw2@u zSBh|zEkzC&!|BIcQ_Y$_@}L+#Kt2oB`0+bJQ0eFFj~NG^?k}Dpjmt{ z`>zEEK2R@&G@JqB#U16O@kHm~U^hO|qal24D-n&# zw>`M@^*ZRlZ{008m#SRF|4aRU&2j<+ZhyZ=goq|#gHmTcld{5^rnl_0{Gd{sk z|Jrct10UdbE=<(rn-;z~=KHl#^e6%nW9jW(a}8o3R}My8ZE-l#3*NB-qUzIo#N zq*Rubgtn*q@tyM5h`Dv9dL-kIdiW+vOQ{ubC*q?S?|#rb@os^QZMa2ShSC9GEU_5n zZFsi;Z$(+V)M*6|zjBCogxw83k4Fc_fbY?UAUg;T)gU5sM Date: Fri, 29 May 2026 08:11:21 +0800 Subject: [PATCH 64/90] Update Initialize-CIPPAuth.ps1 --- .../Authentication/Initialize-CIPPAuth.ps1 | 215 ++++++++++-------- 1 file changed, 116 insertions(+), 99 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 index 9f618899c237..9974d0a4a6be 100644 --- a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 @@ -7,7 +7,8 @@ function Initialize-CIPPAuth { Loads SAM credentials from Key Vault (or DevSecrets table), auto-patches redirect URIs on the SAM and SSO app registrations, and configures EasyAuth if SSO credentials are provisioned but - EasyAuth is not yet enabled. + EasyAuth is not yet enabled. On a fresh deployment with nothing + configured, requests Craft's setup wizard mode. #> [CmdletBinding()] param() @@ -19,26 +20,41 @@ function Initialize-CIPPAuth { NeedsSetup = $true } - # 1. Determine Key Vault name + # -- Entry logging -- + $EasyAuthEnabled = [Craft.Services.AppLifecycleBridge]::IsEasyAuthConfigured() + $IsDevStorage = ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true') -or ($env:NonLocalHostAzurite -eq 'true') $KVName = ($env:WEBSITE_DEPLOYMENT_ID -split '-')[0] - # 2. Try loading SAM credentials - if ($KVName -or $env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + Write-Information "[Auth-Init] Starting — EasyAuth=$EasyAuthEnabled, DevStorage=$IsDevStorage, KVName='$KVName', DeploymentId='$env:WEBSITE_DEPLOYMENT_ID'" + + # 1. Try loading SAM credentials + if ($KVName -or $IsDevStorage) { $AuthState.HasKeyVault = [bool]$KVName + Write-Information "[Auth-Init] Credential source available (KV=$($AuthState.HasKeyVault), DevStorage=$IsDevStorage) — attempting SAM load" try { $Auth = Get-CIPPAuthentication if ($Auth -and $env:ApplicationID -and $env:TenantID) { $AuthState.HasSAMCredentials = $true $AuthState.NeedsSetup = $false $AuthState.IsConfigured = $true - Write-Information "[Auth-Init] SAM credentials loaded (AppID: $($env:ApplicationID))" + Write-Information "[Auth-Init] SAM credentials loaded (AppID: $($env:ApplicationID), TenantID: $($env:TenantID))" + } else { + Write-Information '[Auth-Init] SAM credential load returned but env vars not populated — credentials not available yet (expected on fresh deployment)' } } catch { - Write-Information "[Auth-Init] Could not load SAM credentials: $_" + $ErrorMessage = "$_" + # Distinguish "not found" from real access errors + if ($ErrorMessage -match 'SecretNotFound|not found|does not exist|Development variables not set') { + Write-Information "[Auth-Init] SAM credentials not found in storage — expected on fresh deployment" + } else { + Write-Information "[Auth-Init] ERROR accessing credential storage (possible permission/network issue): $ErrorMessage" + } } + } else { + Write-Information '[Auth-Init] No credential source available — WEBSITE_DEPLOYMENT_ID is not set and not using dev storage. Cannot load SAM credentials.' } - # 3. Auto-patch redirect URIs if we have credentials + # 2. Auto-patch redirect URIs if we have credentials if ($AuthState.HasSAMCredentials) { try { Update-CIPPSAMRedirectUri @@ -53,13 +69,86 @@ function Initialize-CIPPAuth { } } - # 4. If EasyAuth is not configured, check for SSO credentials and set it up - $EasyAuthEnabled = [Craft.Services.AppLifecycleBridge]::IsEasyAuthConfigured() - if (-not $EasyAuthEnabled -and $AuthState.HasSAMCredentials) { - # If the central migration app ID is set, configure EasyAuth with implicit auth - # (no client secret). This lets the user log in via the shared app while the - # ForcedSsoMigrationDialog guides them through creating their own CIPP-SSO app. - # Once they complete migration, step 5 detects the clientId change and cleans up. + # 3. Handle EasyAuth configuration based on current state + if ($EasyAuthEnabled) { + Write-Information '[Auth-Init] EasyAuth is already configured' + + # 3a. If CIPP_SSO_MIGRATION_APPID is set, check if migration is complete + if ($env:CIPP_SSO_MIGRATION_APPID) { + Write-Information '[Auth-Init] EasyAuth is active but CIPP_SSO_MIGRATION_APPID still set — checking if migration is complete...' + try { + $AuthConfigJson = $env:WEBSITE_AUTH_V2_CONFIG_JSON + if ($AuthConfigJson) { + $AuthConfig = $AuthConfigJson | ConvertFrom-Json -ErrorAction Stop + $ConfiguredAppId = $AuthConfig.identityProviders.azureActiveDirectory.registration.clientId + + if ($ConfiguredAppId -eq $env:CIPP_SSO_MIGRATION_APPID) { + Write-Information '[Auth-Init] EasyAuth clientId matches migration app — migration still pending' + } elseif ($ConfiguredAppId) { + Write-Information "[Auth-Init] EasyAuth clientId ($ConfiguredAppId) differs from migration app — migration complete, cleaning up" + $Removed = Remove-CIPPMigrationAppSetting -SettingName 'CIPP_SSO_MIGRATION_APPID' + if ($Removed) { + [Craft.Services.AppLifecycleBridge]::RequestRestart('SSO migration env var cleaned up during warmup') + } + } else { + Write-Information '[Auth-Init] No clientId found in EasyAuth config — skipping cleanup' + } + } + } catch { + Write-Information "[Auth-Init] Migration cleanup check failed (non-fatal): $_" + } + } + + # 3b. Reconcile EasyAuth issuer with SSOMultiTenant setting + if ($AuthState.HasSAMCredentials -and -not $env:CIPP_SSO_MIGRATION_APPID) { + try { + $AuthConfigJson = $env:WEBSITE_AUTH_V2_CONFIG_JSON + if ($AuthConfigJson) { + $AuthConfig = $AuthConfigJson | ConvertFrom-Json -ErrorAction Stop + $CurrentIssuer = $AuthConfig.identityProviders.azureActiveDirectory.registration.openIdIssuer + $ConfiguredAppId = $AuthConfig.identityProviders.azureActiveDirectory.registration.clientId + + if ($CurrentIssuer -and $ConfiguredAppId) { + $SSOMultiTenant = $false + if ($IsDevStorage) { + try { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + $SSOMultiTenant = $Secret.SSOMultiTenant -eq 'True' + } catch { } + } elseif ($KVName) { + try { + $MtVal = Get-CippKeyVaultSecret -VaultName $KVName -Name 'SSOMultiTenant' -AsPlainText -ErrorAction Stop + $SSOMultiTenant = $MtVal -eq 'True' + } catch { } + } + + $ExpectedIssuer = if ($SSOMultiTenant) { + 'https://login.microsoftonline.com/common/v2.0' + } else { + "https://login.microsoftonline.com/$($env:TenantID)/v2.0" + } + + if ($CurrentIssuer -ne $ExpectedIssuer) { + Write-Information "[Auth-Init] EasyAuth issuer mismatch: current=$CurrentIssuer expected=$ExpectedIssuer — updating" + $Configured = Set-CIPPSSOEasyAuth -AppId $ConfiguredAppId -MultiTenant $SSOMultiTenant -TenantId $env:TenantID + if ($Configured) { + Write-Information '[Auth-Init] EasyAuth issuer updated — requesting container restart' + [Craft.Services.AppLifecycleBridge]::RequestRestart('EasyAuth issuer updated to match SSOMultiTenant setting during warmup') + } + } else { + Write-Information "[Auth-Init] EasyAuth issuer matches SSOMultiTenant setting ($SSOMultiTenant) — no update needed" + } + } + } + } catch { + Write-Information "[Auth-Init] EasyAuth issuer reconciliation failed (non-fatal): $_" + } + } + } elseif ($AuthState.HasSAMCredentials) { + # EasyAuth NOT configured but we DO have SAM credentials — try to auto-configure + Write-Information '[Auth-Init] EasyAuth not configured but SAM credentials available — attempting auto-configuration' + if ($env:CIPP_SSO_MIGRATION_APPID) { Write-Information "[Auth-Init] CIPP_SSO_MIGRATION_APPID is set ($($env:CIPP_SSO_MIGRATION_APPID)) — configuring implicit auth EasyAuth" try { @@ -74,12 +163,12 @@ function Initialize-CIPPAuth { return $AuthState } - Write-Information '[Auth-Init] EasyAuth not configured — checking for SSO credentials...' + # Try to find SSO credentials and configure EasyAuth automatically try { $SSOAppId = $null $SSOMultiTenant = $false - if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + if ($IsDevStorage) { $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue $SSOAppId = $Secret.SSOAppId @@ -100,95 +189,23 @@ function Initialize-CIPPAuth { [Craft.Services.AppLifecycleBridge]::RequestRestart('EasyAuth configured from SSO credentials during warmup') } } else { - Write-Information '[Auth-Init] No SSO credentials found — enabling setup wizard' - [Craft.Services.AppLifecycleBridge]::RequestSetupMode('No SSO credentials found — setup wizard needed for initial EasyAuth configuration') + Write-Information '[Auth-Init] SAM credentials loaded but no SSO AppId found — enabling setup wizard' + [Craft.Services.AppLifecycleBridge]::RequestSetupMode('SAM credentials available but no SSO app configured — setup wizard needed') } } catch { Write-Information "[Auth-Init] SSO EasyAuth setup failed (non-fatal): $_" + [Craft.Services.AppLifecycleBridge]::RequestSetupMode('SSO setup failed — setup wizard needed for manual configuration') } + } else { + # No EasyAuth AND no SAM credentials — this is a fresh/unconfigured deployment + Write-Information '[Auth-Init] Fresh deployment detected — no EasyAuth configured and no SAM credentials available' + Write-Information '[Auth-Init] Requesting setup wizard mode for initial configuration' + [Craft.Services.AppLifecycleBridge]::RequestSetupMode('Fresh deployment — no credentials or EasyAuth configured') + $AuthState.NeedsSetup = $true } - # 5. Reconcile EasyAuth issuer with SSOMultiTenant setting: if EasyAuth is already - # configured, check whether the issuer URL matches the current SSOMultiTenant value - # from Key Vault. If it changed (e.g. toggled from single to multi-tenant), update - # the EasyAuth config via ARM and restart. - if ($EasyAuthEnabled -and $AuthState.HasSAMCredentials -and -not $env:CIPP_SSO_MIGRATION_APPID) { - try { - $AuthConfigJson = $env:WEBSITE_AUTH_V2_CONFIG_JSON - if ($AuthConfigJson) { - $AuthConfig = $AuthConfigJson | ConvertFrom-Json -ErrorAction Stop - $CurrentIssuer = $AuthConfig.identityProviders.azureActiveDirectory.registration.openIdIssuer - $ConfiguredAppId = $AuthConfig.identityProviders.azureActiveDirectory.registration.clientId - - if ($CurrentIssuer -and $ConfiguredAppId) { - # Read SSOMultiTenant from KV/DevSecrets - $SSOMultiTenant = $false - if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { - try { - $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' - $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue - $SSOMultiTenant = $Secret.SSOMultiTenant -eq 'True' - } catch { } - } elseif ($KVName) { - try { - $MtVal = Get-CippKeyVaultSecret -VaultName $KVName -Name 'SSOMultiTenant' -AsPlainText -ErrorAction Stop - $SSOMultiTenant = $MtVal -eq 'True' - } catch { } - } - - $ExpectedIssuer = if ($SSOMultiTenant) { - 'https://login.microsoftonline.com/common/v2.0' - } else { - "https://login.microsoftonline.com/$($env:TenantID)/v2.0" - } - - if ($CurrentIssuer -ne $ExpectedIssuer) { - Write-Information "[Auth-Init] EasyAuth issuer mismatch: current=$CurrentIssuer expected=$ExpectedIssuer — updating" - $Configured = Set-CIPPSSOEasyAuth -AppId $ConfiguredAppId -MultiTenant $SSOMultiTenant -TenantId $env:TenantID - if ($Configured) { - Write-Information '[Auth-Init] EasyAuth issuer updated — requesting container restart' - [Craft.Services.AppLifecycleBridge]::RequestRestart('EasyAuth issuer updated to match SSOMultiTenant setting during warmup') - } - } else { - Write-Information "[Auth-Init] EasyAuth issuer matches SSOMultiTenant setting ($SSOMultiTenant) — no update needed" - } - } - } - } catch { - Write-Information "[Auth-Init] EasyAuth issuer reconciliation failed (non-fatal): $_" - } - } - - # 6. Post-migration cleanup: if CIPP_SSO_MIGRATION_APPID is still set but EasyAuth - # is now configured, check whether the EasyAuth clientId still matches the migration - # app. If it differs, the customer's own CIPP-SSO app is active and we can remove - # the migration trigger env var. - if ($EasyAuthEnabled -and $env:CIPP_SSO_MIGRATION_APPID) { - Write-Information '[Auth-Init] EasyAuth is active but CIPP_SSO_MIGRATION_APPID still set — checking if migration is complete...' - try { - $AuthConfigJson = $env:WEBSITE_AUTH_V2_CONFIG_JSON - if ($AuthConfigJson) { - $AuthConfig = $AuthConfigJson | ConvertFrom-Json -ErrorAction Stop - $ConfiguredAppId = $AuthConfig.identityProviders.azureActiveDirectory.registration.clientId - - if ($ConfiguredAppId -eq $env:CIPP_SSO_MIGRATION_APPID) { - # EasyAuth is still using the central migration app — migration not done yet - Write-Information '[Auth-Init] EasyAuth clientId matches migration app — migration still pending' - } elseif ($ConfiguredAppId) { - # EasyAuth clientId differs from the migration app — customer's own app is active - Write-Information "[Auth-Init] EasyAuth clientId ($ConfiguredAppId) differs from migration app — migration complete, cleaning up" - $Removed = Remove-CIPPMigrationAppSetting -SettingName 'CIPP_SSO_MIGRATION_APPID' - if ($Removed) { - [Craft.Services.AppLifecycleBridge]::RequestRestart('SSO migration env var cleaned up during warmup') - } - } else { - Write-Information '[Auth-Init] No clientId found in EasyAuth config — skipping cleanup' - } - } - } catch { - Write-Information "[Auth-Init] Migration cleanup check failed (non-fatal): $_" - } - } + # -- Exit logging -- + Write-Information "[Auth-Init] Complete — IsConfigured=$($AuthState.IsConfigured), HasSAM=$($AuthState.HasSAMCredentials), NeedsSetup=$($AuthState.NeedsSetup), EasyAuth=$EasyAuthEnabled" return $AuthState } From e0f45f2035c77c7b8916a605a8d2e13ccd1c80d9 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Fri, 29 May 2026 14:41:44 +0800 Subject: [PATCH 65/90] Backup excluded tenants config --- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index ec73257e425c..f1f155657c97 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -70,6 +70,14 @@ function New-CIPPBackup { } $Entities | Select-Object * -ExcludeProperty DomainAnalyser, table, Timestamp, ETag, Results | Select-Object *, @{l = 'table'; e = { $CSVTable } } } + # Back up excluded tenant rows (user-configured exclusion state only) + $TenantsTable = Get-CippTable -tablename 'Tenants' + $ExcludedTenants = Get-AzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq true" + if ($ExcludedTenants) { + $CSVfile = @($CSVfile) + @( + $ExcludedTenants | Select-Object PartitionKey, RowKey, customerId, defaultDomainName, displayName, Excluded, ExcludeDate, ExcludeUser | Select-Object *, @{l = 'table'; e = { 'Tenants' } } + ) + } $RowKey = 'CIPPBackup' + '_' + (Get-Date).ToString('yyyy-MM-dd-HHmm') $BackupData = [string]($CSVfile | ConvertTo-Json -Compress -Depth 100) $TableName = 'CIPPBackup' From e3d57cf0ae0ef981173205cf0eaa6d5b26e3f371 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Fri, 29 May 2026 17:43:32 +0800 Subject: [PATCH 66/90] Update Invoke-CIPPStandardDeployCheckChromeExtension.ps1 --- .../Invoke-CIPPStandardDeployCheckChromeExtension.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 index 01469ff25668..aaa3a2b7858a 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 @@ -30,7 +30,6 @@ function Invoke-CIPPStandardDeployCheckChromeExtension { {"type":"autoComplete","multiple":true,"creatable":true,"required":false,"freeSolo":true,"name":"standards.DeployCheckChromeExtension.urlAllowlist","label":"URL Allowlist","placeholder":"e.g. https://example.com/*","helperText":"Enter URLs to allowlist in the extension. Press enter to add each URL. Wildcards are allowed. This should be used for sites that are being blocked by the extension but are known to be safe."} {"type":"switch","name":"standards.DeployCheckChromeExtension.domainSquattingEnabled","label":"Enable domain squatting detection","defaultValue":true} {"type":"textField","name":"standards.DeployCheckChromeExtension.companyName","label":"Company Name","placeholder":"YOUR-COMPANY","required":false} - {"type":"textField","name":"standards.DeployCheckChromeExtension.companyURL","label":"Company URL","placeholder":"https://yourcompany.com","required":false} {"type":"textField","name":"standards.DeployCheckChromeExtension.productName","label":"Product Name","placeholder":"YOUR-PRODUCT-NAME","required":false} {"type":"textField","name":"standards.DeployCheckChromeExtension.supportEmail","label":"Support Email","placeholder":"support@yourcompany.com","required":false} {"type":"textField","name":"standards.DeployCheckChromeExtension.supportUrl","label":"Support URL","placeholder":"https://support.yourcompany.com","required":false} @@ -108,7 +107,7 @@ function Invoke-CIPPStandardDeployCheckChromeExtension { $SupportUrl = $Settings.supportUrl ?? '' $PrivacyPolicyUrl = $Settings.privacyPolicyUrl ?? '' $AboutUrl = $Settings.aboutUrl ?? '' - $PrimaryColor = if ($Settings.primaryColor) { $Settings.primaryColor } else { '#F77F00' } + $PrimaryColor = if ($Settings.primaryColor) { '#{0}' -f ($Settings.primaryColor -replace '^#+', '') } else { '#F77F00' } $LogoUrl = $Settings.logoUrl ?? '' ########################################################################## From a17137cbe0abe8418d43791ed751300b0ed84723 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 14:19:45 -0400 Subject: [PATCH 67/90] chore: remove cipp processor queue C-003 --- Config/CIPPTimers.json | 8 --- .../Start-CIPPProcessorQueue.ps1 | 59 ------------------- 2 files changed, 67 deletions(-) delete mode 100644 Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 diff --git a/Config/CIPPTimers.json b/Config/CIPPTimers.json index 0802c9863096..745034562ce3 100644 --- a/Config/CIPPTimers.json +++ b/Config/CIPPTimers.json @@ -17,14 +17,6 @@ "RunOnProcessor": true, "PreferredProcessor": "usertasks" }, - { - "Id": "168decf3-7ddd-471e-ab46-8b40be0f18ae", - "Command": "Start-CIPPProcessorQueue", - "Description": "Timer to handle user initiated tasks", - "Cron": "0 */15 * * * *", - "Priority": 1, - "RunOnProcessor": true - }, { "Id": "44a40668-ed71-403c-8c26-b32e320086ad", "Command": "Start-AuditLogOrchestrator", diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 deleted file mode 100644 index cbc065c4f15c..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -function Start-CIPPProcessorQueue { - <# - .SYNOPSIS - Starts a specified function on the processor node - #> - [CmdletBinding(SupportsShouldProcess = $true)] - param() - - $QueueTable = Get-CIPPTable -tablename 'ProcessorQueue' - $QueueItems = Get-CIPPAzDataTableEntity @QueueTable -Filter "PartitionKey eq 'Function'" - - foreach ($QueueItem in $QueueItems) { - $FunctionName = $QueueItem.FunctionName ?? $QueueItem.RowKey - if ($PSCmdlet.ShouldProcess("Processing function $($FunctionName)")) { - Write-Information "Running queued function $($FunctionName)" - if ($QueueItem.Parameters) { - try { - $Parameters = $QueueItem.Parameters | ConvertFrom-Json -AsHashtable - } catch { - $Parameters = @{} - } - } else { - $Parameters = @{} - } - if (Get-Command -Name $FunctionName -ErrorAction SilentlyContinue) { - try { - # Prepare telemetry metadata - $metadata = @{ - FunctionName = $FunctionName - TriggerType = 'ProcessorQueue' - QueueRowKey = $QueueItem.RowKey - } - - # Add parameters info if available - if ($Parameters.Count -gt 0) { - $metadata['ParameterCount'] = $Parameters.Count - # Add common parameters - if ($Parameters.Tenant) { - $metadata['Tenant'] = $Parameters.Tenant - } - if ($Parameters.TenantFilter) { - $metadata['Tenant'] = $Parameters.TenantFilter - } - } - - # Wrap function execution with telemetry - - Invoke-Command -ScriptBlock { & $FunctionName @Parameters } - - } catch { - Write-Warning "Failed to run function $($FunctionName). Error: $($_.Exception.Message)" - } - } else { - Write-Warning "Function $($FunctionName) not found" - } - Remove-AzDataTableEntity -Force @QueueTable -Entity $QueueItem - } - } -} From 0fd3315b9d405ed4d459ac3c8f740a640fb74099 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 14:31:14 -0400 Subject: [PATCH 68/90] chore: disable cippcommand action previously removed in UI, not needed anymore C-004 --- .../Webhooks/Invoke-CIPPWebhookProcessing.ps1 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 index 6ca3b39290fa..aa2ddec7fcea 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 @@ -79,7 +79,7 @@ function Invoke-CippWebhookProcessing { "Completed BEC Remediate for $Username" Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $Username" -sev 'Info' } - 'cippcommand' { + <#'cippcommand' { $CommandSplat = @{} $action.parameters.psobject.properties | ForEach-Object { $CommandSplat.Add($_.name, $_.value) } if ($CommandSplat['userid']) { $CommandSplat['userid'] = $Data.UserId } @@ -88,6 +88,9 @@ function Invoke-CippWebhookProcessing { if ($CommandSplat['user']) { $CommandSplat['user'] = $Data.UserId } if ($CommandSplat['username']) { $CommandSplat['username'] = $Data.UserId } & $action.command.value @CommandSplat + }#> + default { + Write-Host "Unknown action: $action" } } } @@ -146,12 +149,12 @@ function Invoke-CippWebhookProcessing { } 'generateWebhook' { $CippAlert = @{ - Type = 'webhook' - Title = $GenerateJSON.Title - JSONContent = $JsonContent - TenantFilter = $TenantFilter - APIName = 'Audit Log Alerts' - SchemaSource = 'Audit Log Alert' + Type = 'webhook' + Title = $GenerateJSON.Title + JSONContent = $JsonContent + TenantFilter = $TenantFilter + APIName = 'Audit Log Alerts' + SchemaSource = 'Audit Log Alert' InvokingCommand = 'Start-AuditLogProcessingOrchestrator' } Write-Host 'Sending Webhook Content' From e98445f25614437873439fe27a2b0f0e95d5dc69 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 15:07:33 -0400 Subject: [PATCH 69/90] chore: sanitize cippid in public webhooks C-001 --- .../Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 index 1dce61f36442..aa8be40e3db2 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 @@ -25,8 +25,9 @@ function Invoke-PublicWebhooks { $body = $Request.Query.validationCode $StatusCode = [HttpStatusCode]::OK } elseif ($Request.Query.CIPPID) { + $CIPPID = ConvertTo-CIPPODataFilterValue -Value $Request.Query.CIPPID -Type Guid $WebhookTable = Get-CIPPTable -TableName webhookTable - $Webhookinfo = Get-CIPPAzDataTableEntity @WebhookTable -Filter "RowKey eq '$($Request.Query.CIPPID)'" -First 1 + $Webhookinfo = Get-CIPPAzDataTableEntity @WebhookTable -Filter "RowKey eq '$CIPPID'" -First 1 if (-not $Webhookinfo) { Write-Host "No matching CIPPID found: $($Request.Query.CIPPID)" $Body = 'This webhook is not authorized.' From 38e3ae9e070ba05968a07647d137efc0ae2491b3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 15:27:21 -0400 Subject: [PATCH 70/90] chore: block arbitrary cmdlets not in CIPP modules address C-007 --- .../Push-ExecScheduledCommand.ps1 | 15 +++++++++++++++ Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 13 +++++++++++++ .../Tools/Get-CIPPSchedulerBlockedCommands.ps1 | 10 ++++++++++ 3 files changed, 38 insertions(+) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index 403da6b88d0f..5fa07451f809 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -161,6 +161,21 @@ function Push-ExecScheduledCommand { } } + $Command = Get-Command -Name $Item.Command -ErrorAction SilentlyContinue + if ($Command.Module -notin @('CIPPCore', 'CIPPAlerts', 'CIPPStandards', 'CIPPTests', 'CIPPDB')) { + Write-LogMessage -headers $Headers -API 'ScheduledTask' -message "Blocked attempt to schedule command from unauthorized module: $($Command.ModuleName)\$RequestedCommand" -Sev 'Warning' + $Results = "Task blocked: The command '$RequestedCommand' is not permitted to run as a scheduled task." + if (!$IsMultiTenantExecution) { + Update-AzDataTableEntity -Force @Table -Entity @{ + PartitionKey = $task.PartitionKey + RowKey = $task.RowKey + Results = "$Results" + TaskState = $State + } + } + Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue + return + } if ($Item.Command -in (Get-CIPPSchedulerBlockedCommands)) { $Results = "Task blocked: '$($Item.Command)' is not permitted to run as a scheduled task." $State = 'Failed' diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index f9a5c0159923..3de8266ed5f3 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -63,6 +63,19 @@ function Add-CIPPScheduledTask { } $RequestedCommand = $task.Command.value ?? $task.Command + + $Command = Get-Command $RequestedCommand -ErrorAction SilentlyContinue + + if (!$Command) { + Write-LogMessage -headers $Headers -API 'ScheduledTask' -message "Blocked attempt to schedule non-existent command: $RequestedCommand" -Sev 'Warning' + return "Error - The command '$RequestedCommand' does not exist and cannot be scheduled." + } + + if ($Command.Module -notin @('CIPPCore', 'CIPPAlerts', 'CIPPStandards', 'CIPPTests', 'CIPPDB')) { + Write-LogMessage -headers $Headers -API 'ScheduledTask' -message "Blocked attempt to schedule command from unauthorized module: $($Command.ModuleName)\$RequestedCommand" -Sev 'Warning' + return "Error - The command '$RequestedCommand' is not permitted to run as a scheduled task." + } + if ($RequestedCommand -in (Get-CIPPSchedulerBlockedCommands)) { Write-LogMessage -headers $Headers -API 'ScheduledTask' -message "Blocked attempt to schedule restricted command: $RequestedCommand" -Sev 'Warning' return "Error - The command '$RequestedCommand' is not permitted to run as a scheduled task." diff --git a/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 b/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 index e46e3e82cd91..9ed724c78ed6 100644 --- a/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 +++ b/Modules/CIPPCore/Public/Tools/Get-CIPPSchedulerBlockedCommands.ps1 @@ -18,9 +18,17 @@ function Get-CIPPSchedulerBlockedCommands { 'Get-CIPPAzIdentityToken' 'Get-CIPPAuthentication' 'New-CIPPAzServiceSAS' + 'New-GraphPOSTRequest' + 'New-GraphGetRequest' + 'New-GraphBulkRequest' + 'New-ExoRequest' + + # Env + 'Set-CIPPEnvVarBackup' # Az Functions cmdlet 'Get-CIPPAzFunctionAppSetting' + 'Get-CIPPAzFunctionAppSubId' 'Update-CIPPAzFunctionAppSetting' # Extension authentication tokens @@ -32,6 +40,7 @@ function Get-CIPPSchedulerBlockedCommands { # Secret & key material 'Get-CippKeyVaultSecret' + 'Set-CippKeyVaultSecret' 'Remove-CippKeyVaultSecret' 'Get-ExtensionAPIKey' 'Set-ExtensionAPIKey' @@ -42,6 +51,7 @@ function Get-CIPPSchedulerBlockedCommands { # SAM permission enumeration - exposes which permissions the SAM app holds 'Get-CippSamPermissions' + 'Get-CIPPRolePermissions' # Direct storage access - bypasses CIPP data access controls 'Get-CIPPTable' From c18bda8795693324d834f54fe9235959e0e3372f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 15:31:19 -0400 Subject: [PATCH 71/90] fix: optimize checks --- .../Push-ExecScheduledCommand.ps1 | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index 5fa07451f809..fde3ca8e0658 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -162,9 +162,9 @@ function Push-ExecScheduledCommand { } $Command = Get-Command -Name $Item.Command -ErrorAction SilentlyContinue - if ($Command.Module -notin @('CIPPCore', 'CIPPAlerts', 'CIPPStandards', 'CIPPTests', 'CIPPDB')) { - Write-LogMessage -headers $Headers -API 'ScheduledTask' -message "Blocked attempt to schedule command from unauthorized module: $($Command.ModuleName)\$RequestedCommand" -Sev 'Warning' - $Results = "Task blocked: The command '$RequestedCommand' is not permitted to run as a scheduled task." + if ($null -eq $Command) { + $Results = "Task Failed: The command $($Item.Command) does not exist." + $State = 'Failed' if (!$IsMultiTenantExecution) { Update-AzDataTableEntity -Force @Table -Entity @{ PartitionKey = $task.PartitionKey @@ -173,13 +173,16 @@ function Push-ExecScheduledCommand { TaskState = $State } } + + Write-LogMessage -API 'Scheduler_UserTasks' -tenant $Tenant -tenantid $TenantInfo.customerId -message "Failed to execute task $($task.Name): The command $($Item.Command) does not exist." -sev Error Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue return } - if ($Item.Command -in (Get-CIPPSchedulerBlockedCommands)) { - $Results = "Task blocked: '$($Item.Command)' is not permitted to run as a scheduled task." + + if ($Command.Module -notin @('CIPPCore', 'CIPPAlerts', 'CIPPStandards', 'CIPPTests', 'CIPPDB')) { $State = 'Failed' - Write-LogMessage -API 'Scheduler_UserTasks' -tenant $Tenant -tenantid $TenantInfo.customerId -message "Blocked execution of restricted command '$($Item.Command)' in task $($task.Name)" -sev Warning + Write-LogMessage -headers $Headers -API 'ScheduledTask' -message "Blocked attempt to schedule command from unauthorized module: $($Command.ModuleName)\$($Item.Command)" -Sev 'Warning' + $Results = "Task blocked: The command '$($Item.Command)' is not permitted to run as a scheduled task." if (!$IsMultiTenantExecution) { Update-AzDataTableEntity -Force @Table -Entity @{ PartitionKey = $task.PartitionKey @@ -191,11 +194,10 @@ function Push-ExecScheduledCommand { Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue return } - - $Function = Get-Command -Name $Item.Command - if ($null -eq $Function) { - $Results = "Task Failed: The command $($Item.Command) does not exist." + if ($Item.Command -in (Get-CIPPSchedulerBlockedCommands)) { + $Results = "Task blocked: '$($Item.Command)' is not permitted to run as a scheduled task." $State = 'Failed' + Write-LogMessage -API 'Scheduler_UserTasks' -tenant $Tenant -tenantid $TenantInfo.customerId -message "Blocked execution of restricted command '$($Item.Command)' in task $($task.Name)" -sev Warning if (!$IsMultiTenantExecution) { Update-AzDataTableEntity -Force @Table -Entity @{ PartitionKey = $task.PartitionKey @@ -204,12 +206,12 @@ function Push-ExecScheduledCommand { TaskState = $State } } - - Write-LogMessage -API 'Scheduler_UserTasks' -tenant $Tenant -tenantid $TenantInfo.customerId -message "Failed to execute task $($task.Name): The command $($Item.Command) does not exist." -sev Error Remove-Variable -Name ScheduledTaskId -Scope Script -ErrorAction SilentlyContinue return } + $Function = $Command + try { $PossibleParams = $Function.Parameters.Keys $keysToRemove = [System.Collections.Generic.List[string]]@() From c69e2ce19d7c8c0df0cd2c4f2de9db936da5c736 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 15:33:15 -0400 Subject: [PATCH 72/90] fix: allow for command without .value --- Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 3de8266ed5f3..2051ccffce9d 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -203,7 +203,7 @@ function Add-CIPPScheduledTask { Tenant = [string]$tenantFilter excludedTenants = [string]$excludedTenants Name = [string]$task.Name - Command = [string]$task.Command.value + Command = [string]$RequestedCommand Parameters = [string]$Parameters ScheduledTime = [string]$task.ScheduledTime Recurrence = [string]$Recurrence From da7bd8c459ab0e75abbcc9a0be96224848a11284 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 15:43:15 -0400 Subject: [PATCH 73/90] chore: add devsecrets to restricted tables address C-010 --- .../HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1 index fb26f73d997c..db67ef64d2e1 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecRestoreBackup.ps1 @@ -14,7 +14,7 @@ function Invoke-ExecRestoreBackup { $AzureTableTypes = @( [string], [int], [long], [double], [bool], [datetime], [guid], [byte[]] ) - $RestrictedTables = @('AccessRoleGroups', 'AccessIPRanges', 'CustomRoles') # tables that require superadmin to restore + $RestrictedTables = @('AccessRoleGroups', 'AccessIPRanges', 'CustomRoles', 'DevSecrets') # tables that require superadmin to restore # Resolve the calling user's roles, including Entra group-based roles $CallingUser = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json From 2ed3f94c91dcfabbc1dbd773c25120bc712801d7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 29 May 2026 15:44:24 -0400 Subject: [PATCH 74/90] chore: remove write host address M-012 --- .../HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1 index c53b7f1690b7..516ecdc914af 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecTokenExchange.ps1 @@ -68,7 +68,6 @@ function Invoke-ExecTokenExchange { $FormData['client_secret'] = $ClientSecret } - Write-Host "Posting this data: $($FormData | ConvertTo-Json -Depth 15)" $Results = Invoke-RestMethod -Uri $TokenUrl -Method Post -Body $FormData -ContentType 'application/x-www-form-urlencoded' -ErrorAction Stop -SkipHttpErrorCheck } catch { $ErrorMessage = $_.Exception From f5f373681cd6e48c3b27b7140d46115c18f828a7 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 30 May 2026 12:18:36 +0800 Subject: [PATCH 75/90] Optimize CIPP DB orchestration --- .../CIPPDBCache/Push-ExecCIPPDBCache.ps1 | 15 ++- .../Activity Triggers/Tests/Push-CIPPTest.ps1 | 19 +++- .../Tests/Push-CIPPTestsList.ps1 | 16 +++- Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 | 9 +- .../Start-CIPPDBTestsRun.ps1 | 5 +- Modules/CIPPCore/Public/Get-CIPPDbItem.ps1 | 3 +- .../Public/Get-CIPPDomainAnalyser.ps1 | 12 +++ Modules/CIPPCore/Public/Get-CippDbRole.ps1 | 2 +- .../CIPPCore/Public/Get-CippDbRoleMembers.ps1 | 6 +- .../Public/Invoke-CIPPDBCacheCollection.ps1 | 6 +- .../Public/Invoke-CIPPTestCollection.ps1 | 72 +++++++++++---- Modules/CIPPCore/Public/New-CIPPDbRequest.ps1 | 14 ++- .../DBCache/Set-CIPPDbCacheTestData.ps1 | 86 +++++++++-------- .../CIS/Identity/Invoke-CippTestCIS_1_1_1.ps1 | 15 ++- .../CIS/Identity/Invoke-CippTestCIS_1_1_2.ps1 | 8 +- .../CIS/Identity/Invoke-CippTestCIS_1_1_4.ps1 | 20 ++-- .../CIS/Identity/Invoke-CippTestCIS_1_2_1.ps1 | 8 +- .../CIS/Identity/Invoke-CippTestCIS_1_3_1.ps1 | 6 +- .../Identity/Invoke-CippTestCIS_2_1_10.ps1 | 6 +- .../Identity/Invoke-CippTestCIS_2_1_14.ps1 | 6 +- .../CIS/Identity/Invoke-CippTestCIS_2_1_8.ps1 | 6 +- .../CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 | 6 +- .../Identity/Invoke-CippTestCIS_5_2_2_1.ps1 | 8 +- .../Identity/Invoke-CippTestCIS_5_2_2_4.ps1 | 8 +- .../CIS/Identity/Invoke-CippTestCIS_5_3_1.ps1 | 4 +- .../Identity/Invoke-CippTestCISAMSEXO101.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO102.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO103.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO11.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO112.ps1 | 12 +-- .../Identity/Invoke-CippTestCISAMSEXO113.ps1 | 12 +-- .../Identity/Invoke-CippTestCISAMSEXO121.ps1 | 14 +-- .../Identity/Invoke-CippTestCISAMSEXO122.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO131.ps1 | 8 +- .../Identity/Invoke-CippTestCISAMSEXO141.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO142.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO143.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO151.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO152.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO153.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO171.ps1 | 12 +-- .../Identity/Invoke-CippTestCISAMSEXO173.ps1 | 12 +-- .../Identity/Invoke-CippTestCISAMSEXO31.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO51.ps1 | 14 +-- .../Identity/Invoke-CippTestCISAMSEXO61.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO62.ps1 | 10 +- .../Identity/Invoke-CippTestCISAMSEXO71.ps1 | 8 +- .../Identity/Invoke-CippTestCISAMSEXO95.ps1 | 10 +- .../Invoke-CippTestCopilotReady001.ps1 | 14 +-- .../Invoke-CippTestCopilotReady002.ps1 | 22 ++--- .../Invoke-CippTestCopilotReady003.ps1 | 20 ++-- .../Invoke-CippTestCopilotReady004.ps1 | 16 ++-- .../Invoke-CippTestCopilotReady005.ps1 | 16 ++-- .../Invoke-CippTestCopilotReady006.ps1 | 16 ++-- .../Invoke-CippTestCopilotReady007.ps1 | 18 ++-- .../Invoke-CippTestCopilotReady008.ps1 | 22 ++--- .../Invoke-CippTestCopilotReady009.ps1 | 10 +- .../Invoke-CippTestCopilotReady010.ps1 | 14 +-- .../Invoke-CippTestCopilotReady011.ps1 | 20 ++-- .../Invoke-CippTestCopilotReady012.ps1 | 18 ++-- .../Invoke-CippTestCopilotReady013.ps1 | 16 ++-- .../Invoke-CippTestCopilotReady014.ps1 | 16 ++-- .../Invoke-CippTestCopilotReady015.ps1 | 4 +- .../Invoke-CippTestCopilotReady016.ps1 | 14 +-- .../Invoke-CippTestCopilotReady017.ps1 | 14 +-- .../Custom/Invoke-CippTestCustomScripts.ps1 | 36 +++++--- .../Invoke-CippTestGenericTest001.ps1 | 8 +- .../Invoke-CippTestGenericTest002.ps1 | 12 +-- .../Invoke-CippTestGenericTest003.ps1 | 14 +-- .../Invoke-CippTestGenericTest004.ps1 | 34 +++---- .../Invoke-CippTestGenericTest005.ps1 | 18 ++-- .../Invoke-CippTestGenericTest006.ps1 | 18 ++-- .../Invoke-CippTestGenericTest007.ps1 | 18 ++-- .../Invoke-CippTestGenericTest008.ps1 | 42 ++++----- .../Invoke-CippTestGenericTest009.ps1 | 34 +++---- .../Invoke-CippTestGenericTest010.ps1 | 22 ++--- .../Invoke-CippTestGenericTest011.ps1 | 54 +++++------ .../ORCA/Identity/Invoke-CippTestORCA100.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA101.ps1 | 20 ++-- .../ORCA/Identity/Invoke-CippTestORCA102.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA103.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA104.ps1 | 24 ++--- .../ORCA/Identity/Invoke-CippTestORCA105.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA106.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA107.ps1 | 22 ++--- .../Identity/Invoke-CippTestORCA108_1.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA109.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA110.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA111.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA112.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA113.ps1 | 24 ++--- .../ORCA/Identity/Invoke-CippTestORCA114.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA115.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA116.ps1 | 14 +-- .../Identity/Invoke-CippTestORCA118_1.ps1 | 14 +-- .../Identity/Invoke-CippTestORCA118_2.ps1 | 14 +-- .../Identity/Invoke-CippTestORCA118_3.ps1 | 14 +-- .../Identity/Invoke-CippTestORCA118_4.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA119.ps1 | 14 +-- .../Invoke-CippTestORCA120_malware.ps1 | 14 +-- .../Identity/Invoke-CippTestORCA120_phish.ps1 | 14 +-- .../Identity/Invoke-CippTestORCA120_spam.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA121.ps1 | 4 +- .../ORCA/Identity/Invoke-CippTestORCA123.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA124.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA139.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA140.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA141.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA142.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA143.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA156.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA158.ps1 | 8 +- .../ORCA/Identity/Invoke-CippTestORCA179.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA180.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA189.ps1 | 10 +- .../Identity/Invoke-CippTestORCA189_2.ps1 | 10 +- .../ORCA/Identity/Invoke-CippTestORCA205.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA220.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA221.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA222.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA223.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA224.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA225.ps1 | 8 +- .../ORCA/Identity/Invoke-CippTestORCA226.ps1 | 12 +-- .../ORCA/Identity/Invoke-CippTestORCA227.ps1 | 12 +-- .../ORCA/Identity/Invoke-CippTestORCA228.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA229.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA230.ps1 | 12 +-- .../ORCA/Identity/Invoke-CippTestORCA231.ps1 | 12 +-- .../ORCA/Identity/Invoke-CippTestORCA232.ps1 | 12 +-- .../ORCA/Identity/Invoke-CippTestORCA233.ps1 | 10 +- .../Identity/Invoke-CippTestORCA233_1.ps1 | 8 +- .../ORCA/Identity/Invoke-CippTestORCA234.ps1 | 8 +- .../ORCA/Identity/Invoke-CippTestORCA235.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA236.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA237.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA238.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA239.ps1 | 8 +- .../ORCA/Identity/Invoke-CippTestORCA240.ps1 | 8 +- .../ORCA/Identity/Invoke-CippTestORCA241.ps1 | 14 +-- .../ORCA/Identity/Invoke-CippTestORCA242.ps1 | 18 ++-- .../ORCA/Identity/Invoke-CippTestORCA243.ps1 | 12 +-- .../ORCA/Identity/Invoke-CippTestORCA244.ps1 | 14 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24541.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24542.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24543.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24545.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24547.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24548.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24549.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24553.ps1 | 12 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24569.ps1 | 14 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24576.ps1 | 14 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24839.ps1 | 14 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24840.ps1 | 14 +-- .../ZTNA/Devices/Invoke-CippTestZTNA24870.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21797.ps1 | 42 ++++----- .../Identity/Invoke-CippTestZTNA21799.ps1 | 8 +- .../Identity/Invoke-CippTestZTNA21804.ps1 | 8 +- .../Identity/Invoke-CippTestZTNA21806.ps1 | 10 +- .../Identity/Invoke-CippTestZTNA21811.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA21812.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21813.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA21814.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21815.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21816.ps1 | 87 ++++++++++-------- .../Identity/Invoke-CippTestZTNA21818.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21819.ps1 | 10 +- .../Identity/Invoke-CippTestZTNA21820.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21822.ps1 | 22 ++--- .../Identity/Invoke-CippTestZTNA21824.ps1 | 8 +- .../Identity/Invoke-CippTestZTNA21825.ps1 | 32 +++---- .../Identity/Invoke-CippTestZTNA21828.ps1 | 8 +- .../Identity/Invoke-CippTestZTNA21829.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21830.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21835.ps1 | 38 ++++---- .../Identity/Invoke-CippTestZTNA21836.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA21838.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21839.ps1 | 26 +++--- .../Identity/Invoke-CippTestZTNA21840.ps1 | 22 ++--- .../Identity/Invoke-CippTestZTNA21845.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA21846.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21848.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21849.ps1 | 32 +++---- .../Identity/Invoke-CippTestZTNA21850.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA21861.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21862.ps1 | 20 ++-- .../Identity/Invoke-CippTestZTNA21863.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21865.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21866.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA21872.ps1 | 24 ++--- .../Identity/Invoke-CippTestZTNA21883.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21889.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21892.ps1 | 14 +-- .../Identity/Invoke-CippTestZTNA21941.ps1 | 24 ++--- .../Identity/Invoke-CippTestZTNA21953.ps1 | 8 +- .../Identity/Invoke-CippTestZTNA21954.ps1 | 8 +- .../Identity/Invoke-CippTestZTNA21955.ps1 | 8 +- .../Identity/Invoke-CippTestZTNA21964.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA22124.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA22659.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA24570.ps1 | 20 ++-- .../Identity/Invoke-CippTestZTNA24572.ps1 | 12 +-- .../Identity/Invoke-CippTestZTNA24824.ps1 | 16 ++-- .../Identity/Invoke-CippTestZTNA24827.ps1 | 16 ++-- Shared/CIPPSharp/CIPPTestDataCache.cs | 39 +++++--- Shared/CIPPSharp/bin/CIPPSharp.dll | Bin 41984 -> 41472 bytes 207 files changed, 1645 insertions(+), 1491 deletions(-) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 index c9a16df871ff..522d40cecc12 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 @@ -45,8 +45,19 @@ function Push-ExecCIPPDBCache { # Build the full function name $FullFunctionName = "Set-CIPPDBCache$Name" - # Check if function exists - $Function = Get-Command -Name $FullFunctionName -ErrorAction SilentlyContinue + # Cache the resolved command per process so back-to-back HTTP-driven refreshes + # don't repeat the module command-table walk. + if (-not $script:CIPPDBCacheFunctionLookup) { + $script:CIPPDBCacheFunctionLookup = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) + Write-Information "[CacheInit] CIPPDBCacheFunctionLookup initialized in PID $PID" + } + if ($script:CIPPDBCacheFunctionLookup.ContainsKey($FullFunctionName)) { + Write-Information "[CacheHit] CIPPDBCacheFunctionLookup PID=$PID Key=$FullFunctionName Size=$($script:CIPPDBCacheFunctionLookup.Count)" + } else { + Write-Information "[CacheMiss] CIPPDBCacheFunctionLookup PID=$PID Key=$FullFunctionName Size=$($script:CIPPDBCacheFunctionLookup.Count) - resolving via Get-Command" + $script:CIPPDBCacheFunctionLookup[$FullFunctionName] = Get-Command -Name $FullFunctionName -ErrorAction SilentlyContinue + } + $Function = $script:CIPPDBCacheFunctionLookup[$FullFunctionName] if (-not $Function) { throw "Function $FullFunctionName does not exist" } diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 index 07574ef2b225..302276834eb2 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 @@ -12,6 +12,14 @@ function Push-CIPPTest { Write-Information "Running test $TestId for tenant $TenantFilter" + # Per-process cache of resolved test function commands so that a flat orchestrator + # firing thousands of activities doesn't repeat the module command-table walk + # for every task. + if (-not $script:CIPPTestFunctionLookup) { + $script:CIPPTestFunctionLookup = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) + Write-Information "[CacheInit] CIPPTestFunctionLookup initialized in PID $PID" + } + try { if ($TestId -like 'CustomScript-*') { $ScriptGuid = $TestId -replace '^CustomScript-', '' @@ -23,13 +31,20 @@ function Push-CIPPTest { $FunctionName = "Invoke-CippTest$TestId" - if (-not (Get-Command $FunctionName -Module CIPPTests -ErrorAction SilentlyContinue)) { + if ($script:CIPPTestFunctionLookup.ContainsKey($FunctionName)) { + Write-Information "[CacheHit] CIPPTestFunctionLookup PID=$PID Key=$FunctionName Size=$($script:CIPPTestFunctionLookup.Count)" + } else { + Write-Information "[CacheMiss] CIPPTestFunctionLookup PID=$PID Key=$FunctionName Size=$($script:CIPPTestFunctionLookup.Count) - resolving via Get-Command" + $script:CIPPTestFunctionLookup[$FunctionName] = Get-Command $FunctionName -Module CIPPTests -ErrorAction SilentlyContinue + } + $TestCommand = $script:CIPPTestFunctionLookup[$FunctionName] + if (-not $TestCommand) { Write-LogMessage -API 'Tests' -tenant $TenantFilter -message "Test function not found: $FunctionName" -sev Error return @{ testRun = $false } } Write-Information "Executing $FunctionName for $TenantFilter" - & $FunctionName -Tenant $TenantFilter + & $TestCommand -Tenant $TenantFilter Write-Host "Returning true, test has run for $tenantFilter" return @{ testRun = $true } diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsList.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsList.ps1 index 916fc512fba9..d018d1d0aa9d 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsList.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTestsList.ps1 @@ -22,11 +22,17 @@ function Push-CIPPTestsList { try { Write-Information "Building test suite list for tenant: $TenantFilter" - # Check if tenant has data before emitting any work - $DbCounts = Get-CIPPDbItem -TenantFilter $TenantFilter -CountsOnly - if (($DbCounts | Measure-Object -Property DataCount -Sum).Sum -eq 0) { - Write-Information "Tenant $TenantFilter has no data in database. Skipping tests." - return @() + # The orchestrator (Start-CIPPDBTestsRun) already filtered the tenant list to those + # with cached data, so the previous per-tenant `Get-CIPPDbItem -CountsOnly` recheck + # was a redundant Table query (one extra round-trip per tenant). The orchestrator + # may pass SkipDbCheck=$true when it has already verified data presence; otherwise + # we fall back to a check here for any direct invocations. + if (-not $Item.SkipDbCheck) { + $DbCounts = Get-CIPPDbItem -TenantFilter $TenantFilter -CountsOnly + if (($DbCounts | Measure-Object -Property DataCount -Sum).Sum -eq 0) { + Write-Information "Tenant $TenantFilter has no data in database. Skipping tests." + return @() + } } # Emit one task per suite — suite names must match the ValidateSet in Invoke-CIPPTestCollection. diff --git a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 index ba74ec1096d8..d0c5e5ad27f1 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDbItem.ps1 @@ -29,10 +29,15 @@ function Add-CIPPDbItem { $Batch = [System.Collections.Generic.List[hashtable]]::new() $NewRowKeys = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $TotalProcessed = 0 + # Cache regex instances so each row pays only the match cost, not regex compilation. + # Two passes preserve the original semantics: path/wildcard chars → '_', control chars → stripped. + $RowKeyPathRegex = [regex]::new('[/\\#?]') + $RowKeyControlRegex = [regex]::new('[\u0000-\u001F\u007F-\u009F]') if ($TenantFilter -match '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$') { try { - $TenantFilter = (Get-Tenants -TenantFilter $TenantFilter -IncludeErrors | Select-Object -First 1).defaultDomainName + $TenantLookup = @(Get-Tenants -TenantFilter $TenantFilter -IncludeErrors) + if ($TenantLookup.Count -gt 0) { $TenantFilter = $TenantLookup[0].defaultDomainName } } catch {} } } @@ -48,7 +53,7 @@ function Add-CIPPDbItem { foreach ($Item in @($InputObject)) { if ($null -eq $Item) { continue } $ItemId = $Item.ExternalDirectoryObjectId ?? $Item.id ?? $Item.Identity ?? $Item.skuId ?? $Item.userPrincipalName ?? [guid]::NewGuid().ToString() - $RowKey = "$Type-$ItemId" -replace '[/\\#?]', '_' -replace '[\u0000-\u001F\u007F-\u009F]', '' + $RowKey = $RowKeyControlRegex.Replace($RowKeyPathRegex.Replace("$Type-$ItemId", '_'), '') if ($NewRowKeys.Add($RowKey)) { $Batch.Add(@{ PartitionKey = $TenantFilter diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 index f310233164ba..00585cdbf107 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPDBTestsRun.ps1 @@ -58,11 +58,14 @@ function Start-CIPPDBTestsRun { return } - # Phase 1: Build per-tenant list activities (discover tests per tenant) + # Phase 1: Build per-tenant list activities (discover tests per tenant). + # The tenants below were already filtered by data presence above, so we pass + # SkipDbCheck=$true to avoid a redundant CountsOnly round-trip per tenant. $Batch = foreach ($Tenant in $AllTenantsList) { @{ FunctionName = 'CIPPTestsList' TenantFilter = $Tenant + SkipDbCheck = $true } } diff --git a/Modules/CIPPCore/Public/Get-CIPPDbItem.ps1 b/Modules/CIPPCore/Public/Get-CIPPDbItem.ps1 index 0fcb380e5aa1..ba4f2d4a5d03 100644 --- a/Modules/CIPPCore/Public/Get-CIPPDbItem.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPDbItem.ps1 @@ -62,8 +62,9 @@ function Get-CIPPDbItem { $Conditions.Add('DataCount ge 0') } $Filter = [string]::Join(' and ', $Conditions) + # -Property does the projection server-side; the trailing Select-Object was + # redundant (and rebuilt every row as a NoteProperty bag, slowing later filters). $Results = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property 'PartitionKey', 'RowKey', 'DataCount', 'Timestamp' - $Results = $Results | Select-Object PartitionKey, RowKey, DataCount, Timestamp } else { if (-not $Type) { throw 'Type parameter is required when CountsOnly is not specified' diff --git a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 index e1aed77dbd55..f2494efa09db 100644 --- a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 @@ -14,6 +14,17 @@ function Get-CIPPDomainAnalyser { #> [CmdletBinding()] param([string]$TenantFilter) + + if (-not $script:CIPPDomainAnalyserCache) { + $script:CIPPDomainAnalyserCache = @{} + } + $CacheKey = if ([string]::IsNullOrEmpty($TenantFilter)) { 'AllTenants' } else { $TenantFilter } + $CacheNow = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $CachedEntry = $script:CIPPDomainAnalyserCache[$CacheKey] + if ($CachedEntry -and ($CacheNow - $CachedEntry.Timestamp) -lt 300) { + return $CachedEntry.Results + } + $DomainTable = Get-CIPPTable -Table 'Domains' # Get all the things @@ -43,5 +54,6 @@ function Get-CIPPDomainAnalyser { } catch { $Results = @() } + $script:CIPPDomainAnalyserCache[$CacheKey] = @{ Results = $Results; Timestamp = $CacheNow } return $Results } diff --git a/Modules/CIPPCore/Public/Get-CippDbRole.ps1 b/Modules/CIPPCore/Public/Get-CippDbRole.ps1 index 02313a9a96ad..5c38f0eb63a4 100644 --- a/Modules/CIPPCore/Public/Get-CippDbRole.ps1 +++ b/Modules/CIPPCore/Public/Get-CippDbRole.ps1 @@ -11,7 +11,7 @@ function Get-CippDbRole { [switch]$CisaHighlyPrivilegedRoles ) - $Roles = New-CIPPDbRequest -TenantFilter $TenantFilter -Type 'Roles' + $Roles = Get-CIPPTestData -TenantFilter $TenantFilter -Type 'Roles' if ($IncludePrivilegedRoles) { $PrivilegedRoleTemplateIds = @( diff --git a/Modules/CIPPCore/Public/Get-CippDbRoleMembers.ps1 b/Modules/CIPPCore/Public/Get-CippDbRoleMembers.ps1 index d51612448da5..0b5b026df61a 100644 --- a/Modules/CIPPCore/Public/Get-CippDbRoleMembers.ps1 +++ b/Modules/CIPPCore/Public/Get-CippDbRoleMembers.ps1 @@ -8,9 +8,9 @@ function Get-CippDbRoleMembers { [string]$RoleTemplateId ) - $RoleAssignments = New-CIPPDbRequest -TenantFilter $TenantFilter -Type 'RoleAssignmentScheduleInstances' - $RoleEligibilities = New-CIPPDbRequest -TenantFilter $TenantFilter -Type 'RoleEligibilitySchedules' - $DirectRoleAssignments = New-CIPPDbRequest -TenantFilter $TenantFilter -Type 'Roles' | Where-Object { $_.roleTemplateId -eq $RoleTemplateId } | Select-Object -ExpandProperty members + $RoleAssignments = Get-CIPPTestData -TenantFilter $TenantFilter -Type 'RoleAssignmentScheduleInstances' + $RoleEligibilities = Get-CIPPTestData -TenantFilter $TenantFilter -Type 'RoleEligibilitySchedules' + $DirectRoleAssignments = Get-CIPPTestData -TenantFilter $TenantFilter -Type 'Roles' | Where-Object { $_.roleTemplateId -eq $RoleTemplateId } | Select-Object -ExpandProperty members $ActiveMembers = $RoleAssignments | Where-Object { $_.roleDefinitionId -eq $RoleTemplateId -and $_.assignmentType -eq 'Assigned' diff --git a/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 index c4af3e75d077..2538fc8493fb 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 @@ -181,13 +181,13 @@ function Invoke-CIPPDBCacheCollection { Write-Information " [$CollectionType] Collecting $CacheType for $TenantFilter" & $FullFunctionName @Params $ItemStopwatch.Stop() - $ElapsedSeconds = [math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3) + $ElapsedSeconds = '{0:N3}' -f $ItemStopwatch.Elapsed.TotalSeconds $Timings.Add("$CacheType : ${ElapsedSeconds}s") Write-Information " [$CollectionType] Completed $CacheType for $TenantFilter - Took ${ElapsedSeconds} seconds" $SuccessCount++ } catch { $ItemStopwatch.Stop() - $ElapsedSeconds = [math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3) + $ElapsedSeconds = '{0:N3}' -f $ItemStopwatch.Elapsed.TotalSeconds $FailedCount++ $Errors.Add("$CacheType : $($_.Exception.Message)") $Timings.Add("$CacheType : ${ElapsedSeconds}s (FAILED)") @@ -196,7 +196,7 @@ function Invoke-CIPPDBCacheCollection { } $CollectionStopwatch.Stop() - $TotalElapsed = [math]::Round($CollectionStopwatch.Elapsed.TotalSeconds, 3) + $TotalElapsed = '{0:N3}' -f $CollectionStopwatch.Elapsed.TotalSeconds $Summary = "$CollectionType collection for $TenantFilter completed in ${TotalElapsed} seconds - $SuccessCount succeeded, $FailedCount failed out of $($CacheTypes.Count)" Write-Information $Summary Write-Information " Timings: $($Timings -join ' | ')" diff --git a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 index b386e1e4413b..052242225f8d 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 @@ -53,6 +53,21 @@ function Invoke-CIPPTestCollection { GenericTests = 'Invoke-CippTestGenericTest*' } + # Process-scoped cache of Get-Command lookups so each (tenant × suite) invocation + # doesn't pay the module command-table walk again. The CIPPTests module is loaded + # once per worker process and its exported function set does not change at runtime. + if (-not $script:CIPPTestSuiteFunctionCache) { + $script:CIPPTestSuiteFunctionCache = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) + Write-Information "[CacheInit] CIPPTestSuiteFunctionCache initialized in PID $PID" + } + if (-not $script:CIPPTestCustomFunctionResolved) { + Write-Information "[CacheMiss] CIPPTestCustomFunction PID=$PID - resolving Invoke-CippTestCustomScripts via Get-Command" + $script:CIPPTestCustomFunction = Get-Command -Name 'Invoke-CippTestCustomScripts' -ErrorAction SilentlyContinue + $script:CIPPTestCustomFunctionResolved = $true + } else { + Write-Information "[CacheHit] CIPPTestCustomFunction PID=$PID Resolved=$([bool]$script:CIPPTestCustomFunction)" + } + $SuiteStopwatch = [System.Diagnostics.Stopwatch]::StartNew() $SuccessCount = 0 $FailedCount = 0 @@ -62,8 +77,7 @@ function Invoke-CIPPTestCollection { # Custom suite: Invoke-CippTestCustomScripts now requires a ScriptGuid parameter. # Enumerate distinct enabled script guids from the DB and call once per guid. if ($SuiteName -eq 'Custom') { - $CustomFunction = Get-Command -Name 'Invoke-CippTestCustomScripts' -ErrorAction SilentlyContinue - if (-not $CustomFunction) { + if (-not $script:CIPPTestCustomFunction) { Write-Information 'Invoke-CippTestCustomScripts not found — skipping Custom suite' return @{ SuiteName = $SuiteName; TenantFilter = $TenantFilter; Success = 0; Failed = 0; Total = 0; TotalSeconds = 0; Timings = @(); Errors = @() } } @@ -71,12 +85,30 @@ function Invoke-CIPPTestCollection { $Table = Get-CippTable -TableName 'CustomPowershellScripts' $AllScripts = @(Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'CustomScript'") - # Get the latest version of each script guid, filter to enabled only - $EnabledGuids = $AllScripts | Group-Object -Property ScriptGuid | ForEach-Object { - $_.Group | Sort-Object -Property Version -Descending | Select-Object -First 1 - } | Where-Object { - -not $_.PSObject.Properties['Enabled'] -or [bool]$_.Enabled - } | Select-Object -ExpandProperty ScriptGuid + # Single-pass "latest enabled version per ScriptGuid". + # The previous Group-Object | ForEach-Object { Sort-Object | Select -First 1 } + # pipeline allocated a Group container per guid and ran an O(n log n) sort per group; + # this hashtable walk is O(n) total and avoids the pipeline overhead entirely. + $LatestByGuid = @{} + foreach ($Script in $AllScripts) { + $Guid = $Script.ScriptGuid + if (-not $Guid) { continue } + $Existing = $LatestByGuid[$Guid] + if (-not $Existing -or [int]$Script.Version -gt [int]$Existing.Version) { + $LatestByGuid[$Guid] = $Script + } + } + + $EnabledGuidsList = [System.Collections.Generic.List[string]]::new() + foreach ($Latest in $LatestByGuid.Values) { + # Cache the property lookup — calling .PSObject.Properties[''] reflects through + # the PSObject member set on every invocation in the original code. + $EnabledProp = $Latest.PSObject.Properties['Enabled'] + if (-not $EnabledProp -or [bool]$EnabledProp.Value) { + $EnabledGuidsList.Add($Latest.ScriptGuid) + } + } + $EnabledGuids = $EnabledGuidsList.ToArray() if ($EnabledGuids.Count -eq 0) { Write-Information 'No enabled custom scripts found — skipping Custom suite' @@ -104,13 +136,13 @@ function Invoke-CIPPTestCollection { $ResultBatch.Clear() } $ItemStopwatch.Stop() - $ElapsedSeconds = [math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3) + $ElapsedSeconds = '{0:N3}' -f $ItemStopwatch.Elapsed.TotalSeconds $Timings.Add("CustomScript-$Guid : ${ElapsedSeconds}s") Write-Information " [Custom] Completed CustomScript-$Guid - ${ElapsedSeconds}s" $SuccessCount++ } catch { $ItemStopwatch.Stop() - $ElapsedSeconds = [math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3) + $ElapsedSeconds = '{0:N3}' -f $ItemStopwatch.Elapsed.TotalSeconds $FailedCount++ $Errors.Add("CustomScript-$Guid : $($_.Exception.Message)") $Timings.Add("CustomScript-$Guid : ${ElapsedSeconds}s (FAILED)") @@ -125,7 +157,7 @@ function Invoke-CIPPTestCollection { } $SuiteStopwatch.Stop() - $TotalElapsed = [math]::Round($SuiteStopwatch.Elapsed.TotalSeconds, 3) + $TotalElapsed = '{0:N3}' -f $SuiteStopwatch.Elapsed.TotalSeconds $Summary = "Custom suite for $TenantFilter completed in ${TotalElapsed}s — $SuccessCount/$($EnabledGuids.Count) ran, $FailedCount errored" Write-Information $Summary Write-Information " Timings: $($Timings -join ' | ')" @@ -144,9 +176,17 @@ function Invoke-CIPPTestCollection { } } - # Standard suites: discover functions by name pattern via Get-Command + # Standard suites: discover functions by name pattern via Get-Command. + # Cache the function list per suite so repeated activity invocations in the same + # process don't pay the module command-table walk again (item 7). $Pattern = $SuitePatterns[$SuiteName] - $TestFunctions = @(Get-Command -Name $Pattern -Module CIPPTests -ErrorAction SilentlyContinue) + if ($script:CIPPTestSuiteFunctionCache.ContainsKey($SuiteName)) { + Write-Information "[CacheHit] CIPPTestSuiteFunctionCache PID=$PID Suite=$SuiteName Size=$($script:CIPPTestSuiteFunctionCache.Count)" + } else { + Write-Information "[CacheMiss] CIPPTestSuiteFunctionCache PID=$PID Suite=$SuiteName Size=$($script:CIPPTestSuiteFunctionCache.Count) - resolving pattern '$Pattern' via Get-Command" + $script:CIPPTestSuiteFunctionCache[$SuiteName] = @(Get-Command -Name $Pattern -Module CIPPTests -ErrorAction SilentlyContinue) + } + $TestFunctions = $script:CIPPTestSuiteFunctionCache[$SuiteName] if ($TestFunctions.Count -eq 0) { Write-Information "No test functions found for suite $SuiteName (pattern: $Pattern) — skipping" return @{ @@ -180,13 +220,13 @@ function Invoke-CIPPTestCollection { $ResultBatch.Clear() } $ItemStopwatch.Stop() - $Timings.Add("$($TestFunction.Name) : $([math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3))s") + $Timings.Add(('{0} : {1:N3}s' -f $TestFunction.Name, $ItemStopwatch.Elapsed.TotalSeconds)) $SuccessCount++ } catch { $ItemStopwatch.Stop() $FailedCount++ $Errors.Add("$($TestFunction.Name) : $($_.Exception.Message)") - $Timings.Add("$($TestFunction.Name) : $([math]::Round($ItemStopwatch.Elapsed.TotalSeconds, 3))s (FAILED)") + $Timings.Add(('{0} : {1:N3}s (FAILED)' -f $TestFunction.Name, $ItemStopwatch.Elapsed.TotalSeconds)) } } @@ -197,7 +237,7 @@ function Invoke-CIPPTestCollection { } $SuiteStopwatch.Stop() - $TotalElapsed = [math]::Round($SuiteStopwatch.Elapsed.TotalSeconds, 3) + $TotalElapsed = '{0:N3}' -f $SuiteStopwatch.Elapsed.TotalSeconds $TestCount = $TestFunctions.Count $Summary = "$SuiteName suite for $TenantFilter completed in ${TotalElapsed}s — $SuccessCount/$TestCount ran, $FailedCount errored" Write-Information $Summary diff --git a/Modules/CIPPCore/Public/New-CIPPDbRequest.ps1 b/Modules/CIPPCore/Public/New-CIPPDbRequest.ps1 index 3fa02a5998cf..0a3345e5cbac 100644 --- a/Modules/CIPPCore/Public/New-CIPPDbRequest.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPDbRequest.ps1 @@ -39,7 +39,19 @@ function New-CIPPDbRequest { $Table = Get-CippTable -tablename 'CippReportingDB' - $Tenant = Get-Tenants -TenantFilter $TenantFilter | Select-Object -ExpandProperty defaultDomainName + if (-not $script:CIPPDbRequestTenantCache) { + $script:CIPPDbRequestTenantCache = @{} + } + $CacheNow = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $CachedTenant = $script:CIPPDbRequestTenantCache[$TenantFilter] + if ($CachedTenant -and ($CacheNow - $CachedTenant.Timestamp) -lt 300) { + $Tenant = $CachedTenant.DefaultDomain + } else { + $Tenant = (Get-Tenants -TenantFilter $TenantFilter).defaultDomainName + if ($Tenant) { + $script:CIPPDbRequestTenantCache[$TenantFilter] = @{ DefaultDomain = $Tenant; Timestamp = $CacheNow } + } + } if (-not $Tenant) { if ($TenantFilter -eq $env:TenantID) { return $false diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDbCacheTestData.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDbCacheTestData.ps1 index 10302dd69d17..66fbf545949e 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDbCacheTestData.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDbCacheTestData.ps1 @@ -36,50 +36,62 @@ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. '@ * 10 # Repeat to get ~3KB - $startTime = Get-Date + # Stopwatch is much cheaper than two Get-Date calls (Get-Date is documented in + # the PowerShell 7.4 performance guidance as a hot path to avoid). + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() Write-Information "[Set-CIPPDbCacheTestData] Starting generation of $Count test objects for tenant $TenantFilter" - # Stream test objects directly to batch processor - 1..$Count | ForEach-Object { - [PSCustomObject]@{ - id = [guid]::NewGuid().ToString() - displayName = "Test User $_" - userPrincipalName = "testuser$_@$TenantFilter" - mail = "testuser$_@$TenantFilter" - givenName = 'Test' - surname = "User $_" - jobTitle = 'Test Engineer' - department = 'Testing Department' - officeLocation = "Test Office $_" - mobilePhone = "+1-555-000-$($_.ToString().PadLeft(4, '0'))" - businessPhones = @("+1-555-001-$($_.ToString().PadLeft(4, '0'))") - accountEnabled = $true - createdDateTime = (Get-Date).ToString('o') - lastSignInDateTime = (Get-Date).AddDays(-1).ToString('o') - description = $sampleText - companyName = 'Test Company' - country = 'United States' - city = 'Test City' - state = 'Test State' - postalCode = '12345' - streetAddress = '123 Test Street' - proxyAddresses = @("SMTP:testuser$_@$TenantFilter", "smtp:alias$_@$TenantFilter") - assignedLicenses = @( - @{ skuId = [guid]::NewGuid().ToString(); disabledPlans = @() } - ) - customAttribute1 = 'Custom Value 1' - customAttribute2 = 'Custom Value 2' - customAttribute3 = 'Custom Value 3' - additionalData = $sampleText + + # Stream test objects through the pipeline using `foreach` inside an inline + # scriptblock — preserves the original streaming behaviour (so Add-CIPPDbItem + # can flush in 500-row batches without holding all 50k rows in memory) while + # avoiding the per-iteration ForEach-Object scriptblock dispatch and + # PSCustomObject NoteProperty bag construction. Ordered hashtables serialize + # identically through ConvertTo-Json downstream. + & { + foreach ($i in 1..$Count) { + $padded = $i.ToString().PadLeft(4, '0') + [ordered]@{ + id = [guid]::NewGuid().ToString() + displayName = "Test User $i" + userPrincipalName = "testuser$i@$TenantFilter" + mail = "testuser$i@$TenantFilter" + givenName = 'Test' + surname = "User $i" + jobTitle = 'Test Engineer' + department = 'Testing Department' + officeLocation = "Test Office $i" + mobilePhone = "+1-555-000-$padded" + businessPhones = @("+1-555-001-$padded") + accountEnabled = $true + createdDateTime = (Get-Date).ToString('o') + lastSignInDateTime = (Get-Date).AddDays(-1).ToString('o') + description = $sampleText + companyName = 'Test Company' + country = 'United States' + city = 'Test City' + state = 'Test State' + postalCode = '12345' + streetAddress = '123 Test Street' + proxyAddresses = @("SMTP:testuser$i@$TenantFilter", "smtp:alias$i@$TenantFilter") + assignedLicenses = @( + @{ skuId = [guid]::NewGuid().ToString(); disabledPlans = @() } + ) + customAttribute1 = 'Custom Value 1' + customAttribute2 = 'Custom Value 2' + customAttribute3 = 'Custom Value 3' + additionalData = $sampleText + } } } | Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'TestData' -AddCount - $endTime = Get-Date - $duration = ($endTime - $startTime).TotalSeconds - $objectsPerSecond = [math]::Round($Count / $duration, 2) + $stopwatch.Stop() + $duration = $stopwatch.Elapsed.TotalSeconds + $objectsPerSecond = '{0:N2}' -f ($Count / $duration) + $durationFmt = '{0:N3}' -f $duration Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter ` - -message "Generated $Count test objects in $duration seconds ($objectsPerSecond objects/sec)" -sev Debug + -message "Generated $Count test objects in $durationFmt seconds ($objectsPerSecond objects/sec)" -sev Debug } catch { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter ` diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_1.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_1.ps1 index 8fb0530f76f1..c5d1e600002c 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_1.ps1 @@ -20,10 +20,9 @@ function Invoke-CippTestCIS_1_1_1 { return } - $PrivilegedRoleIds = ($Roles | Where-Object { $_.isPrivileged -eq $true }).id - $PrivilegedAssignments = $RoleAssignments | Where-Object { $_.roleDefinitionId -in $PrivilegedRoleIds } - $PrivilegedUserIds = $PrivilegedAssignments.principalId | Select-Object -Unique - $PrivilegedUsers = $Users | Where-Object { $_.id -in $PrivilegedUserIds } + $PrivilegedRoleIds = [System.Collections.Generic.HashSet[string]]::new([string[]]$Roles.Where({ $_.isPrivileged -eq $true }).id) + $PrivilegedUserIds = [System.Collections.Generic.HashSet[string]]::new([string[]]($RoleAssignments.Where({ $PrivilegedRoleIds.Contains($_.roleDefinitionId) }).principalId | Select-Object -Unique)) + $PrivilegedUsers = $Users.Where({ $PrivilegedUserIds.Contains($_.id) }) if (-not $PrivilegedUsers) { Add-CippTestResult -TenantFilter $Tenant -TestId 'CIS_1_1_1' -TestType 'Identity' -Status 'Passed' -ResultMarkdown 'No privileged users found.' -Risk 'High' -Name 'Administrative accounts are cloud-only' -UserImpact 'Medium' -ImplementationEffort 'Medium' -Category 'Privileged Access' @@ -38,13 +37,13 @@ function Invoke-CippTestCIS_1_1_1 { if ($NonCompliant.Count -eq 0) { $Status = 'Passed' - $Result = "All $($PrivilegedUsers.Count) privileged users are cloud-only and unlicensed." + $Result = [System.Text.StringBuilder]::new("All $($PrivilegedUsers.Count) privileged users are cloud-only and unlicensed.") } else { $Status = 'Failed' - $Result = "$($NonCompliant.Count) of $($PrivilegedUsers.Count) privileged user(s) are not cloud-only or are licensed:`n`n" - $Result += "| UPN | Synced | Licensed |`n| :-- | :----- | :------- |`n" + $Result = [System.Text.StringBuilder]::new("$($NonCompliant.Count) of $($PrivilegedUsers.Count) privileged user(s) are not cloud-only or are licensed:`n`n") + $null = $Result.Append("| UPN | Synced | Licensed |`n| :-- | :----- | :------- |`n") foreach ($U in ($NonCompliant | Select-Object -First 25)) { - $Result += "| $($U.userPrincipalName) | $([bool]$U.onPremisesSyncEnabled) | $([bool]($U.assignedLicenses.Count -gt 0)) |`n" + $null = $Result.Append("| $($U.userPrincipalName) | $([bool]$U.onPremisesSyncEnabled) | $([bool]($U.assignedLicenses.Count -gt 0)) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_2.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_2.ps1 index 81849198254d..910a698a7063 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_2.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_2.ps1 @@ -19,16 +19,16 @@ function Invoke-CippTestCIS_1_1_2 { return } - $GA = $Roles | Where-Object { $_.displayName -eq 'Global Administrator' } | Select-Object -First 1 + $GA = $Roles.Where({ $_.displayName -eq 'Global Administrator' }, 'First', 1)[0] if (-not $GA) { Add-CippTestResult -TenantFilter $Tenant -TestId 'CIS_1_1_2' -TestType 'Identity' -Status 'Failed' -ResultMarkdown 'Global Administrator role not found in tenant role definitions.' -Risk 'High' -Name 'Two emergency access accounts have been defined' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Privileged Access' return } - $GAUserIds = ($RoleAssignments | Where-Object { $_.roleDefinitionId -eq $GA.id }).principalId - $GAUsers = $Users | Where-Object { $_.id -in $GAUserIds } + $GAUserIds = [System.Collections.Generic.HashSet[string]]::new([string[]]$RoleAssignments.Where({ $_.roleDefinitionId -eq $GA.id }).principalId) + $GAUsers = $Users.Where({ $GAUserIds.Contains($_.id) }) $BreakGlassPattern = 'breakglass|break-glass|emergency|cipp-bg|bg-admin' - $LikelyBG = $GAUsers | Where-Object { $_.userPrincipalName -match $BreakGlassPattern -and $_.onPremisesSyncEnabled -ne $true } + $LikelyBG = $GAUsers.Where({ $_.userPrincipalName -match $BreakGlassPattern -and $_.onPremisesSyncEnabled -ne $true }) if ($LikelyBG.Count -ge 2) { $Status = 'Passed' diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_4.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_4.ps1 index a0e446fbbef4..e33d80beba91 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_4.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_1_4.ps1 @@ -18,19 +18,19 @@ function Invoke-CippTestCIS_1_1_4 { # SkuPartNumbers that are acceptable for admin accounts: Entra ID P1/P2 only $AcceptableSkus = @('AAD_PREMIUM', 'AAD_PREMIUM_P2', 'EMS', 'EMSPREMIUM') - $PrivilegedRoleIds = ($Roles | Where-Object { $_.isPrivileged -eq $true }).id - $PrivilegedUserIds = ($RoleAssignments | Where-Object { $_.roleDefinitionId -in $PrivilegedRoleIds }).principalId | Select-Object -Unique - $PrivilegedUsers = $Users | Where-Object { $_.id -in $PrivilegedUserIds } + $PrivilegedRoleIds = [System.Collections.Generic.HashSet[string]]::new([string[]]$Roles.Where({ $_.isPrivileged -eq $true }).id) + $PrivilegedUserIds = [System.Collections.Generic.HashSet[string]]::new([string[]]($RoleAssignments.Where({ $PrivilegedRoleIds.Contains($_.roleDefinitionId) }).principalId | Select-Object -Unique)) + $PrivilegedUsers = $Users.Where({ $PrivilegedUserIds.Contains($_.id) }) - $LicensedAdmins = $PrivilegedUsers | Where-Object { + $LicensedAdmins = $PrivilegedUsers.Where({ $_.assignedLicenses -and $_.assignedLicenses.Count -gt 0 - } + }) - $NonCompliant = $LicensedAdmins | Where-Object { - $skus = ($_.assignedPlans | ForEach-Object { $_.servicePlanId }) -join ',' - $hasProductivity = $_.assignedPlans | Where-Object { $_.service -in @('exchange', 'SharePoint', 'MicrosoftCommunicationsOnline', 'TeamspaceAPI') -and $_.capabilityStatus -eq 'Enabled' } - [bool]$hasProductivity - } + $ProductivityServices = [System.Collections.Generic.HashSet[string]]::new([string[]]@('exchange', 'SharePoint', 'MicrosoftCommunicationsOnline', 'TeamspaceAPI')) + $NonCompliant = $LicensedAdmins.Where({ + $hasProductivity = $_.assignedPlans.Where({ $ProductivityServices.Contains($_.service) -and $_.capabilityStatus -eq 'Enabled' }, 'First', 1) + [bool]$hasProductivity.Count + }) if (-not $LicensedAdmins) { $Status = 'Passed' diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_1.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_1.ps1 index d35d3a2e9d8a..bc3b4679fbad 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_1.ps1 @@ -17,13 +17,13 @@ function Invoke-CippTestCIS_1_2_1 { if (-not $PublicGroups -or $PublicGroups.Count -eq 0) { $Status = 'Passed' - $Result = 'No public Microsoft 365 (Unified) groups found in the tenant.' + $Result = [System.Text.StringBuilder]::new('No public Microsoft 365 (Unified) groups found in the tenant.') } else { $Status = 'Failed' - $Result = "Found $($PublicGroups.Count) public Microsoft 365 group(s). Each public group's contents are visible to every user in the tenant — convert them to Private unless explicitly approved.`n`n" - $Result += "| Display Name | Mail |`n| :----------- | :--- |`n" + $Result = [System.Text.StringBuilder]::new("Found $($PublicGroups.Count) public Microsoft 365 group(s). Each public group's contents are visible to every user in the tenant — convert them to Private unless explicitly approved.`n`n") + $null = $Result.Append("| Display Name | Mail |`n| :----------- | :--- |`n") foreach ($G in ($PublicGroups | Select-Object -First 25)) { - $Result += "| $($G.displayName) | $($G.mail) |`n" + $null = $Result.Append("| $($G.displayName) | $($G.mail) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_3_1.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_3_1.ps1 index 3b62354ad116..a18099e1a9c7 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_3_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_3_1.ps1 @@ -17,12 +17,12 @@ function Invoke-CippTestCIS_1_3_1 { if (-not $Failing -or $Failing.Count -eq 0) { $Status = 'Passed' - $Result = "All $($Domains.Count) domain(s) have password expiration disabled (passwordValidityPeriodInDays = 2147483647)." + $Result = [System.Text.StringBuilder]::new("All $($Domains.Count) domain(s) have password expiration disabled (passwordValidityPeriodInDays = 2147483647).") } else { $Status = 'Failed' - $Result = "$($Failing.Count) domain(s) still expire passwords:`n`n| Domain | Validity (days) |`n| :----- | :-------------- |`n" + $Result = [System.Text.StringBuilder]::new("$($Failing.Count) domain(s) still expire passwords:`n`n| Domain | Validity (days) |`n| :----- | :-------------- |`n") foreach ($D in $Failing) { - $Result += "| $($D.id) | $($D.passwordValidityPeriodInDays) |`n" + $null = $Result.Append("| $($D.id) | $($D.passwordValidityPeriodInDays) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_10.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_10.ps1 index 4e0c321a5f93..3ad101ed07a1 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_10.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_10.ps1 @@ -19,12 +19,12 @@ function Invoke-CippTestCIS_2_1_10 { if (-not $Failing -or $Failing.Count -eq 0) { $Status = 'Passed' - $Result = "All $($Results.Count) domain(s) have a DMARC record with p=quarantine or p=reject." + $Result = [System.Text.StringBuilder]::new("All $($Results.Count) domain(s) have a DMARC record with p=quarantine or p=reject.") } else { $Status = 'Failed' - $Result = "$($Failing.Count) of $($Results.Count) domain(s) are missing a compliant DMARC record:`n`n| Domain | DMARCPresent | DMARCActionPolicy |`n| :----- | :----------- | :---------------- |`n" + $Result = [System.Text.StringBuilder]::new("$($Failing.Count) of $($Results.Count) domain(s) are missing a compliant DMARC record:`n`n| Domain | DMARCPresent | DMARCActionPolicy |`n| :----- | :----------- | :---------------- |`n") foreach ($D in ($Failing | Select-Object -First 25)) { - $Result += "| $($D.Domain) | $($D.DMARCPresent) | $($D.DMARCActionPolicy) |`n" + $null = $Result.Append("| $($D.Domain) | $($D.DMARCPresent) | $($D.DMARCActionPolicy) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_14.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_14.ps1 index 6906ce5a406a..5a0ae74afa6d 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_14.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_14.ps1 @@ -17,12 +17,12 @@ function Invoke-CippTestCIS_2_1_14 { if (-not $Offending) { $Status = 'Passed' - $Result = "All $($Inbound.Count) inbound anti-spam policy/policies have no allowed sender domains." + $Result = [System.Text.StringBuilder]::new("All $($Inbound.Count) inbound anti-spam policy/policies have no allowed sender domains.") } else { $Status = 'Failed' - $Result = "$($Offending.Count) inbound anti-spam policy/policies have allowed sender domains configured:`n`n" + $Result = [System.Text.StringBuilder]::new("$($Offending.Count) inbound anti-spam policy/policies have allowed sender domains configured:`n`n") foreach ($P in $Offending) { - $Result += "- **$($P.Identity)**: $($P.AllowedSenderDomains -join ', ')`n" + $null = $Result.Append("- **$($P.Identity)**: $($P.AllowedSenderDomains -join ', ')`n") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_8.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_8.ps1 index 82d0881cbe1a..b58ffa9664e7 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_8.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_8.ps1 @@ -17,12 +17,12 @@ function Invoke-CippTestCIS_2_1_8 { if (-not $Failing -or $Failing.Count -eq 0) { $Status = 'Passed' - $Result = "All $($Results.Count) domain(s) have an SPF record published." + $Result = [System.Text.StringBuilder]::new("All $($Results.Count) domain(s) have an SPF record published.") } else { $Status = 'Failed' - $Result = "$($Failing.Count) of $($Results.Count) domain(s) are missing a valid SPF record:`n`n| Domain | SPF Record |`n| :----- | :--------- |`n" + $Result = [System.Text.StringBuilder]::new("$($Failing.Count) of $($Results.Count) domain(s) are missing a valid SPF record:`n`n| Domain | SPF Record |`n| :----- | :--------- |`n") foreach ($D in ($Failing | Select-Object -First 25)) { - $Result += "| $($D.Domain) | $($D.ActualSPFRecord) |`n" + $null = $Result.Append("| $($D.Domain) | $($D.ActualSPFRecord) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 index 3547c7403431..7cb90eabd3c9 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 @@ -25,11 +25,11 @@ function Invoke-CippTestCIS_2_1_9 { if ($Failed.Count -eq 0) { $Status = 'Passed' - $Result = "DKIM is enabled for all $($Sending.Count) sending domain(s)." + $Result = [System.Text.StringBuilder]::new("DKIM is enabled for all $($Sending.Count) sending domain(s).") } else { $Status = 'Failed' - $Result = "DKIM is not enabled for $($Failed.Count) sending domain(s):`n`n| Domain | DKIM Enabled |`n| :----- | :----------- |`n" - foreach ($F in $Failed) { $Result += "| $($F.Domain) | $($F.Enabled) |`n" } + $Result = [System.Text.StringBuilder]::new("DKIM is not enabled for $($Failed.Count) sending domain(s):`n`n| Domain | DKIM Enabled |`n| :----- | :----------- |`n") + foreach ($F in $Failed) { $null = $Result.Append("| $($F.Domain) | $($F.Enabled) |`n") } } Add-CippTestResult -TenantFilter $Tenant -TestId 'CIS_2_1_9' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'DKIM is enabled for all Exchange Online Domains' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Email Authentication' diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_1.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_1.ps1 index 7ea446f7a56f..730b29f55a04 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_1.ps1 @@ -14,15 +14,15 @@ function Invoke-CippTestCIS_5_2_2_1 { return } - $PrivRoleIds = ($Roles | Where-Object { $_.isPrivileged -eq $true }).id + $PrivRoleIds = [System.Collections.Generic.HashSet[string]]::new([string[]]$Roles.Where({ $_.isPrivileged -eq $true }).id) - $Matching = $CA | Where-Object { + $Matching = $CA.Where({ $_.state -eq 'enabled' -and $_.grantControls -and ($_.grantControls.builtInControls -contains 'mfa' -or $_.grantControls.authenticationStrength) -and $_.conditions.users.includeRoles -and - (@($_.conditions.users.includeRoles) | Where-Object { $_ -in $PrivRoleIds }).Count -gt 0 - } + ([string[]]$_.conditions.users.includeRoles).Where({ $PrivRoleIds.Contains($_) }, 'First', 1).Count -gt 0 + }) if ($Matching) { $Status = 'Passed' diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_4.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_4.ps1 index 5725d23ddd79..ea2462c3b457 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_4.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_2_4.ps1 @@ -14,18 +14,18 @@ function Invoke-CippTestCIS_5_2_2_4 { return } - $PrivRoleIds = ($Roles | Where-Object { $_.isPrivileged -eq $true }).id + $PrivRoleIds = [System.Collections.Generic.HashSet[string]]::new([string[]]$Roles.Where({ $_.isPrivileged -eq $true }).id) - $Matching = $CA | Where-Object { + $Matching = $CA.Where({ $_.state -eq 'enabled' -and $_.conditions.users.includeRoles -and - (@($_.conditions.users.includeRoles) | Where-Object { $_ -in $PrivRoleIds }).Count -gt 0 -and + ([string[]]$_.conditions.users.includeRoles).Where({ $PrivRoleIds.Contains($_) }, 'First', 1).Count -gt 0 -and $_.sessionControls -and $_.sessionControls.signInFrequency -and $_.sessionControls.signInFrequency.isEnabled -eq $true -and $_.sessionControls.persistentBrowser -and $_.sessionControls.persistentBrowser.mode -eq 'never' - } + }) if ($Matching) { $Status = 'Passed' diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_3_1.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_3_1.ps1 index cd591efbb4aa..935bbf9d7c76 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_3_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_3_1.ps1 @@ -15,8 +15,8 @@ function Invoke-CippTestCIS_5_3_1 { return } - $PrivRoleIds = ($Roles | Where-Object { $_.isPrivileged -eq $true }).id - $EligibleAssignmentsForPriv = $Eligibility | Where-Object { $_.roleDefinitionId -in $PrivRoleIds } + $PrivRoleIds = [System.Collections.Generic.HashSet[string]]::new([string[]]$Roles.Where({ $_.isPrivileged -eq $true }).id) + $EligibleAssignmentsForPriv = $Eligibility.Where({ $PrivRoleIds.Contains($_.roleDefinitionId) }) if ($EligibleAssignmentsForPriv -and $EligibleAssignmentsForPriv.Count -gt 0) { $Status = 'Passed' diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.ps1 index 3280623b0a6a..3ad60d1ad77f 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO101.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO101 { $FailedPolicies = $MalwarePolicies | Where-Object { -not $_.EnableFileFilter } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($MalwarePolicies.Count) malware filter policy/policies have file filtering enabled." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($MalwarePolicies.Count) malware filter policy/policies have file filtering enabled.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($MalwarePolicies.Count) malware filter policy/policies do not have file filtering enabled:`n`n" - $Result += "| Policy Name | File Filter Enabled |`n" - $Result += "| :---------- | :------------------ |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($MalwarePolicies.Count) malware filter policy/policies do not have file filtering enabled:`n`n") + $null = $Result.Append("| Policy Name | File Filter Enabled |`n") + $null = $Result.Append("| :---------- | :------------------ |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Name) | $($Policy.EnableFileFilter) |`n" + $null = $Result.Append("| $($Policy.Name) | $($Policy.EnableFileFilter) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.ps1 index ec9f77655698..e1ab88df20d4 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO102.ps1 @@ -37,14 +37,14 @@ function Invoke-CippTestCISAMSEXO102 { } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($MalwarePolicies.Count) malware filter policy/policies quarantine or delete emails with malware." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($MalwarePolicies.Count) malware filter policy/policies quarantine or delete emails with malware.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($MalwarePolicies.Count) malware filter policy/policies do not quarantine or delete malware:`n`n" - $Result += "| Policy Name | Current Action | Expected |`n" - $Result += "| :---------- | :------------- | :------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($MalwarePolicies.Count) malware filter policy/policies do not quarantine or delete malware:`n`n") + $null = $Result.Append("| Policy Name | Current Action | Expected |`n") + $null = $Result.Append("| :---------- | :------------- | :------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.'Policy Name') | $($Policy.'Current Action') | $($Policy.Expected) |`n" + $null = $Result.Append("| $($Policy.'Policy Name') | $($Policy.'Current Action') | $($Policy.Expected) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.ps1 index 0362875758df..0ced07fd39aa 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO103.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO103 { $FailedPolicies = $MalwarePolicies | Where-Object { -not $_.ZapEnabled } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($MalwarePolicies.Count) malware filter policy/policies have ZAP (Zero-hour Auto Purge) enabled." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($MalwarePolicies.Count) malware filter policy/policies have ZAP (Zero-hour Auto Purge) enabled.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($MalwarePolicies.Count) malware filter policy/policies do not have ZAP enabled:`n`n" - $Result += "| Policy Name | ZAP Enabled |`n" - $Result += "| :---------- | :---------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($MalwarePolicies.Count) malware filter policy/policies do not have ZAP enabled:`n`n") + $null = $Result.Append("| Policy Name | ZAP Enabled |`n") + $null = $Result.Append("| :---------- | :---------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Name) | $($Policy.ZapEnabled) |`n" + $null = $Result.Append("| $($Policy.Name) | $($Policy.ZapEnabled) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.ps1 index 5519f0e2b9bd..c400638593a3 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO11.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO11 { $ForwardingEnabledDomains = $RemoteDomains | Where-Object { $_.AutoForwardEnabled -eq $true } if (($ForwardingEnabledDomains | Measure-Object).Count -eq 0) { - $Result = '✅ **Pass**: Automatic forwarding to external domains is disabled for all remote domains.' + $Result = [System.Text.StringBuilder]::new('✅ **Pass**: Automatic forwarding to external domains is disabled for all remote domains.') $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($ForwardingEnabledDomains.Count) domain(s) have automatic forwarding enabled:`n`n" - $Result += "| Domain Name | Auto Forward |`n" - $Result += "| :---------- | :----------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($ForwardingEnabledDomains.Count) domain(s) have automatic forwarding enabled:`n`n") + $null = $Result.Append("| Domain Name | Auto Forward |`n") + $null = $Result.Append("| :---------- | :----------- |`n") foreach ($Domain in $ForwardingEnabledDomains) { - $Result += "| $($Domain.DomainName) | $($Domain.AutoForwardEnabled) |`n" + $null = $Result.Append("| $($Domain.DomainName) | $($Domain.AutoForwardEnabled) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.ps1 index ebc6b5f8e3ee..60f74ec92b92 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO112.ps1 @@ -30,16 +30,16 @@ function Invoke-CippTestCISAMSEXO112 { } if ($PoliciesWithTips.Count -gt 0) { - $Result = "✅ **Pass**: $($PoliciesWithTips.Count) policy/policies have impersonation safety tips enabled:`n`n" - $Result += "| Policy | Similar Users Tips | Similar Domains Tips | Unusual Characters Tips |`n" - $Result += "| :----- | :----------------- | :------------------- | :---------------------- |`n" + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: $($PoliciesWithTips.Count) policy/policies have impersonation safety tips enabled:`n`n") + $null = $Result.Append("| Policy | Similar Users Tips | Similar Domains Tips | Unusual Characters Tips |`n") + $null = $Result.Append("| :----- | :----------------- | :------------------- | :---------------------- |`n") foreach ($Policy in $PoliciesWithTips) { - $Result += "| $($Policy.Identity) | $($Policy.EnableSimilarUsersSafetyTips) | $($Policy.EnableSimilarDomainsSafetyTips) | $($Policy.EnableUnusualCharactersSafetyTips) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableSimilarUsersSafetyTips) | $($Policy.EnableSimilarDomainsSafetyTips) | $($Policy.EnableUnusualCharactersSafetyTips) |`n") } $Status = 'Passed' } else { - $Result = "❌ **Fail**: No policies found with impersonation safety tips enabled.`n`n" - $Result += "Enable safety tips in preset security policies to warn users about potential impersonation." + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: No policies found with impersonation safety tips enabled.`n`n") + $null = $Result.Append("Enable safety tips in preset security policies to warn users about potential impersonation.") $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.ps1 index 68d40d45bd03..555983192cda 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO113.ps1 @@ -29,16 +29,16 @@ function Invoke-CippTestCISAMSEXO113 { } if ($PoliciesWithIntelligence.Count -gt 0) { - $Result = "✅ **Pass**: $($PoliciesWithIntelligence.Count) policy/policies have mailbox intelligence enabled:`n`n" - $Result += "| Policy | Mailbox Intelligence | Intelligence Protection | State |`n" - $Result += "| :----- | :------------------- | :---------------------- | :---- |`n" + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: $($PoliciesWithIntelligence.Count) policy/policies have mailbox intelligence enabled:`n`n") + $null = $Result.Append("| Policy | Mailbox Intelligence | Intelligence Protection | State |`n") + $null = $Result.Append("| :----- | :------------------- | :---------------------- | :---- |`n") foreach ($Policy in $PoliciesWithIntelligence) { - $Result += "| $($Policy.Identity) | $($Policy.EnableMailboxIntelligence) | $($Policy.EnableMailboxIntelligenceProtection) | $($Policy.State) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableMailboxIntelligence) | $($Policy.EnableMailboxIntelligenceProtection) | $($Policy.State) |`n") } $Status = 'Passed' } else { - $Result = "❌ **Fail**: No policies found with mailbox intelligence enabled.`n`n" - $Result += 'Enable mailbox intelligence in preset security policies for AI-powered impersonation protection.' + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: No policies found with mailbox intelligence enabled.`n`n") + $null = $Result.Append('Enable mailbox intelligence in preset security policies for AI-powered impersonation protection.') $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.ps1 index dd6333788ebd..076946c66a31 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO121.ps1 @@ -26,18 +26,18 @@ function Invoke-CippTestCISAMSEXO121 { $AllowedSenders = $AllowBlockList | Where-Object { $_.Action -eq 'Allow' -and $_.ListType -eq 'Sender' } if ($AllowedSenders.Count -eq 0) { - $Result = '✅ **Pass**: No allowed senders configured in tenant allow/block list.' + $Result = [System.Text.StringBuilder]::new('✅ **Pass**: No allowed senders configured in tenant allow/block list.') $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($AllowedSenders.Count) allowed sender(s) configured in tenant allow/block list" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($AllowedSenders.Count) allowed sender(s) configured in tenant allow/block list") if ($AllowedSenders.Count -gt 10) { - $Result += ' (showing first 10)' + $null = $Result.Append(' (showing first 10)') } - $Result += ":`n`n" - $Result += "| Value | Action | List Type |`n" - $Result += "| :---- | :----- | :-------- |`n" + $null = $Result.Append(":`n`n") + $null = $Result.Append("| Value | Action | List Type |`n") + $null = $Result.Append("| :---- | :----- | :-------- |`n") foreach ($Sender in ($AllowedSenders | Select-Object -First 10)) { - $Result += "| $($Sender.Value) | $($Sender.Action) | $($Sender.ListType) |`n" + $null = $Result.Append("| $($Sender.Value) | $($Sender.Action) | $($Sender.ListType) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.ps1 index ea0c5c5e020d..f84e33a94501 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO122.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO122 { $FailedPolicies = $SpamPolicies | Where-Object { $_.EnableSafeList -eq $true } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies have safe lists disabled." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies have safe lists disabled.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies have safe lists enabled:`n`n" - $Result += "| Policy Name | Safe List Enabled |`n" - $Result += "| :---------- | :---------------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies have safe lists enabled:`n`n") + $null = $Result.Append("| Policy Name | Safe List Enabled |`n") + $null = $Result.Append("| :---------- | :---------------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Name) | $($Policy.EnableSafeList) |`n" + $null = $Result.Append("| $($Policy.Name) | $($Policy.EnableSafeList) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.ps1 index 0a80171888ad..167153bfefd2 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO131.ps1 @@ -26,12 +26,12 @@ function Invoke-CippTestCISAMSEXO131 { $OrgConfigObject = $OrgConfig | Select-Object -First 1 if ($OrgConfigObject.AuditDisabled -eq $false) { - $Result = '✅ **Pass**: Mailbox auditing is enabled for the organization.' + $Result = [System.Text.StringBuilder]::new('✅ **Pass**: Mailbox auditing is enabled for the organization.') $Status = 'Passed' } else { - $Result = "❌ **Fail**: Mailbox auditing is disabled for the organization.`n`n" - $Result += "**Current Setting:**`n" - $Result += "- AuditDisabled: $($OrgConfigObject.AuditDisabled)" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: Mailbox auditing is disabled for the organization.`n`n") + $null = $Result.Append("**Current Setting:**`n") + $null = $Result.Append("- AuditDisabled: $($OrgConfigObject.AuditDisabled)") $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.ps1 index 2eca3acff798..b4968c25a793 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO141.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO141 { $FailedPolicies = $SpamPolicies | Where-Object { $_.HighConfidenceSpamAction -ne 'Quarantine' } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies quarantine high confidence spam." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies quarantine high confidence spam.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies do not quarantine high confidence spam:`n`n" - $Result += "| Policy Name | Current Action | Expected |`n" - $Result += "| :---------- | :------------- | :------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies do not quarantine high confidence spam:`n`n") + $null = $Result.Append("| Policy Name | Current Action | Expected |`n") + $null = $Result.Append("| :---------- | :------------- | :------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.'Policy Name') | $($Policy.'Current Action') | $($Policy.Expected) |`n" + $null = $Result.Append("| $($Policy.'Policy Name') | $($Policy.'Current Action') | $($Policy.Expected) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.ps1 index 3d6477aabc04..79104ff941f7 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO142.ps1 @@ -37,14 +37,14 @@ function Invoke-CippTestCISAMSEXO142 { } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies move spam to junk folder or quarantine." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies move spam to junk folder or quarantine.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies do not properly handle spam:`n`n" - $Result += "| Policy Name | Current Action | Expected |`n" - $Result += "| :---------- | :------------- | :------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies do not properly handle spam:`n`n") + $null = $Result.Append("| Policy Name | Current Action | Expected |`n") + $null = $Result.Append("| :---------- | :------------- | :------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.'Policy Name') | $($Policy.'Current Action') | $($Policy.Expected) |`n" + $null = $Result.Append("| $($Policy.'Policy Name') | $($Policy.'Current Action') | $($Policy.Expected) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.ps1 index 0605186a6e7b..21af91add0af 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO143.ps1 @@ -40,14 +40,14 @@ function Invoke-CippTestCISAMSEXO143 { } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies have no spam filter bypasses configured." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($SpamPolicies.Count) anti-spam policy/policies have no spam filter bypasses configured.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies have spam filter bypasses configured:`n`n" - $Result += "| Policy Name | Allowed Senders | Allowed Domains | Issue |`n" - $Result += "| :---------- | :-------------- | :-------------- | :---- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($SpamPolicies.Count) anti-spam policy/policies have spam filter bypasses configured:`n`n") + $null = $Result.Append("| Policy Name | Allowed Senders | Allowed Domains | Issue |`n") + $null = $Result.Append("| :---------- | :-------------- | :-------------- | :---- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.'Policy Name') | $($Policy.'Allowed Senders') | $($Policy.'Allowed Domains') | $($Policy.Issue) |`n" + $null = $Result.Append("| $($Policy.'Policy Name') | $($Policy.'Allowed Senders') | $($Policy.'Allowed Domains') | $($Policy.Issue) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.ps1 index cbc4f460951c..7a487dfcdc41 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO151.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO151 { $FailedPolicies = $SafeLinksPolicies | Where-Object { -not $_.EnableSafeLinksForEmail } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($SafeLinksPolicies.Count) Safe Links policy/policies have URL comparison with block-list enabled." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($SafeLinksPolicies.Count) Safe Links policy/policies have URL comparison with block-list enabled.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($SafeLinksPolicies.Count) Safe Links policy/policies do not have URL scanning enabled:`n`n" - $Result += "| Policy Name | Safe Links for Email |`n" - $Result += "| :---------- | :------------------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($SafeLinksPolicies.Count) Safe Links policy/policies do not have URL scanning enabled:`n`n") + $null = $Result.Append("| Policy Name | Safe Links for Email |`n") + $null = $Result.Append("| :---------- | :------------------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Name) | $($Policy.EnableSafeLinksForEmail) |`n" + $null = $Result.Append("| $($Policy.Name) | $($Policy.EnableSafeLinksForEmail) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.ps1 index 88a246cf1374..e051dcb7f6a6 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO152.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO152 { $FailedPolicies = $SafeLinksPolicies | Where-Object { -not $_.ScanUrls } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($SafeLinksPolicies.Count) Safe Links policy/policies have real-time URL scanning enabled." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($SafeLinksPolicies.Count) Safe Links policy/policies have real-time URL scanning enabled.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($SafeLinksPolicies.Count) Safe Links policy/policies do not have real-time URL scanning enabled:`n`n" - $Result += "| Policy Name | Scan URLs |`n" - $Result += "| :---------- | :-------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($SafeLinksPolicies.Count) Safe Links policy/policies do not have real-time URL scanning enabled:`n`n") + $null = $Result.Append("| Policy Name | Scan URLs |`n") + $null = $Result.Append("| :---------- | :-------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Name) | $($Policy.ScanUrls) |`n" + $null = $Result.Append("| $($Policy.Name) | $($Policy.ScanUrls) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.ps1 index 5e413ca22c00..ed50fec8a0f0 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO153.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO153 { $FailedPolicies = $SafeLinksPolicies | Where-Object { $_.TrackUserClicks -eq $true } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: All $($SafeLinksPolicies.Count) Safe Links policy/policies have click tracking disabled." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: All $($SafeLinksPolicies.Count) Safe Links policy/policies have click tracking disabled.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) of $($SafeLinksPolicies.Count) Safe Links policy/policies have click tracking enabled:`n`n" - $Result += "| Policy Name | Track User Clicks |`n" - $Result += "| :---------- | :---------------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) of $($SafeLinksPolicies.Count) Safe Links policy/policies have click tracking enabled:`n`n") + $null = $Result.Append("| Policy Name | Track User Clicks |`n") + $null = $Result.Append("| :---------- | :---------------- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Name) | $($Policy.TrackUserClicks) |`n" + $null = $Result.Append("| $($Policy.Name) | $($Policy.TrackUserClicks) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.ps1 index 9ec61ec96a6f..06fe3ca4c264 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO171.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO171 { $AuditConfigObject = $AuditConfig | Select-Object -First 1 if ($AuditConfigObject.UnifiedAuditLogIngestionEnabled -eq $true) { - $Result = "✅ **Pass**: Microsoft Purview Audit (Standard) logging is enabled.`n`n" - $Result += "**Current Settings:**`n" - $Result += "- UnifiedAuditLogIngestionEnabled: $($AuditConfigObject.UnifiedAuditLogIngestionEnabled)" + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: Microsoft Purview Audit (Standard) logging is enabled.`n`n") + $null = $Result.Append("**Current Settings:**`n") + $null = $Result.Append("- UnifiedAuditLogIngestionEnabled: $($AuditConfigObject.UnifiedAuditLogIngestionEnabled)") $Status = 'Passed' } else { - $Result = "❌ **Fail**: Microsoft Purview Audit (Standard) logging is not enabled.`n`n" - $Result += "**Current Settings:**`n" - $Result += "- UnifiedAuditLogIngestionEnabled: $($AuditConfigObject.UnifiedAuditLogIngestionEnabled)" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: Microsoft Purview Audit (Standard) logging is not enabled.`n`n") + $null = $Result.Append("**Current Settings:**`n") + $null = $Result.Append("- UnifiedAuditLogIngestionEnabled: $($AuditConfigObject.UnifiedAuditLogIngestionEnabled)") $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.ps1 index c7bdfae5f0dc..ea4780621b3a 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO173.ps1 @@ -26,14 +26,14 @@ function Invoke-CippTestCISAMSEXO173 { $AuditConfigObject = $AuditConfig | Select-Object -First 1 if ($AuditConfigObject.AdminAuditLogEnabled -eq $true) { - $Result = "✅ **Pass**: Admin audit log is enabled (provides 1 year retention).`n`n" - $Result += "**Current Settings:**`n" - $Result += "- AdminAuditLogEnabled: $($AuditConfigObject.AdminAuditLogEnabled)" + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: Admin audit log is enabled (provides 1 year retention).`n`n") + $null = $Result.Append("**Current Settings:**`n") + $null = $Result.Append("- AdminAuditLogEnabled: $($AuditConfigObject.AdminAuditLogEnabled)") $Status = 'Passed' } else { - $Result = "❌ **Fail**: Admin audit log is not enabled.`n`n" - $Result += "**Current Settings:**`n" - $Result += "- AdminAuditLogEnabled: $($AuditConfigObject.AdminAuditLogEnabled)" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: Admin audit log is not enabled.`n`n") + $null = $Result.Append("**Current Settings:**`n") + $null = $Result.Append("- AdminAuditLogEnabled: $($AuditConfigObject.AdminAuditLogEnabled)") $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.ps1 index f1ddaff3ef16..a547857bd91c 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO31.ps1 @@ -47,14 +47,14 @@ function Invoke-CippTestCISAMSEXO31 { } if ($FailedDomains.Count -eq 0) { - $Result = "✅ **Pass**: DKIM is enabled for all $($SendingDomains.Count) sending domain(s)." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: DKIM is enabled for all $($SendingDomains.Count) sending domain(s).") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedDomains.Count) of $($SendingDomains.Count) domain(s) do not have DKIM properly enabled:`n`n" - $Result += "| Domain | DKIM Enabled | Status |`n" - $Result += "| :----- | :----------- | :----- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedDomains.Count) of $($SendingDomains.Count) domain(s) do not have DKIM properly enabled:`n`n") + $null = $Result.Append("| Domain | DKIM Enabled | Status |`n") + $null = $Result.Append("| :----- | :----------- | :----- |`n") foreach ($Domain in $FailedDomains) { - $Result += "| $($Domain.Domain) | $($Domain.'DKIM Enabled') | $($Domain.Status) |`n" + $null = $Result.Append("| $($Domain.Domain) | $($Domain.'DKIM Enabled') | $($Domain.Status) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.ps1 index 9061f5b258e7..3c513b43d8bc 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO51.ps1 @@ -26,18 +26,18 @@ function Invoke-CippTestCISAMSEXO51 { $FailedMailboxes = $CASMailboxes | Where-Object { $_.SmtpClientAuthenticationDisabled -eq $false } if ($FailedMailboxes.Count -eq 0) { - $Result = "✅ **Pass**: SMTP authentication is disabled for all $($CASMailboxes.Count) mailbox(es)." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: SMTP authentication is disabled for all $($CASMailboxes.Count) mailbox(es).") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedMailboxes.Count) of $($CASMailboxes.Count) mailbox(es) have SMTP authentication enabled" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedMailboxes.Count) of $($CASMailboxes.Count) mailbox(es) have SMTP authentication enabled") if ($FailedMailboxes.Count -gt 10) { - $Result += ' (showing first 10)' + $null = $Result.Append(' (showing first 10)') } - $Result += ":`n`n" - $Result += "| Display Name | Identity | SMTP Auth Disabled |`n" - $Result += "| :----------- | :------- | :----------------- |`n" + $null = $Result.Append(":`n`n") + $null = $Result.Append("| Display Name | Identity | SMTP Auth Disabled |`n") + $null = $Result.Append("| :----------- | :------- | :----------------- |`n") foreach ($Mailbox in ($FailedMailboxes | Select-Object -First 10)) { - $Result += "| $($Mailbox.DisplayName) | $($Mailbox.Identity) | $($Mailbox.SmtpClientAuthenticationDisabled) |`n" + $null = $Result.Append("| $($Mailbox.DisplayName) | $($Mailbox.Identity) | $($Mailbox.SmtpClientAuthenticationDisabled) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.ps1 index 3a42e55c563c..6614818ec038 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO61.ps1 @@ -40,14 +40,14 @@ function Invoke-CippTestCISAMSEXO61 { } if ($FailedPolicies.Count -eq 0) { - $Result = '✅ **Pass**: No sharing policies allow contact folder sharing with external domains.' + $Result = [System.Text.StringBuilder]::new('✅ **Pass**: No sharing policies allow contact folder sharing with external domains.') $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) sharing policy/policies allow contact folder sharing:`n`n" - $Result += "| Policy Name | Enabled | Issue |`n" - $Result += "| :---------- | :------ | :---- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) sharing policy/policies allow contact folder sharing:`n`n") + $null = $Result.Append("| Policy Name | Enabled | Issue |`n") + $null = $Result.Append("| :---------- | :------ | :---- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.'Policy Name') | $($Policy.Enabled) | $($Policy.Issue) |`n" + $null = $Result.Append("| $($Policy.'Policy Name') | $($Policy.Enabled) | $($Policy.Issue) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.ps1 index 021fd0c9680b..12f304da818d 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO62.ps1 @@ -41,14 +41,14 @@ function Invoke-CippTestCISAMSEXO62 { } if ($FailedPolicies.Count -eq 0) { - $Result = "✅ **Pass**: No sharing policies allow detailed calendar sharing with all domains." + $Result = [System.Text.StringBuilder]::new("✅ **Pass**: No sharing policies allow detailed calendar sharing with all domains.") $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) sharing policy/policies allow detailed calendar sharing with all domains:`n`n" - $Result += "| Policy Name | Enabled | Issue |`n" - $Result += "| :---------- | :------ | :---- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) sharing policy/policies allow detailed calendar sharing with all domains:`n`n") + $null = $Result.Append("| Policy Name | Enabled | Issue |`n") + $null = $Result.Append("| :---------- | :------ | :---- |`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.'Policy Name') | $($Policy.Enabled) | $($Policy.Issue) |`n" + $null = $Result.Append("| $($Policy.'Policy Name') | $($Policy.Enabled) | $($Policy.Issue) |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.ps1 index 17880ef45290..6951858c3e5b 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO71.ps1 @@ -26,12 +26,12 @@ function Invoke-CippTestCISAMSEXO71 { $OrgConfigObject = $OrgConfig | Select-Object -First 1 if ($OrgConfigObject.ExternalInOutlook -eq $true) { - $Result = '✅ **Pass**: External sender warnings are enabled in Outlook.' + $Result = [System.Text.StringBuilder]::new('✅ **Pass**: External sender warnings are enabled in Outlook.') $Status = 'Passed' } else { - $Result = "❌ **Fail**: External sender warnings are not enabled in Outlook.`n`n" - $Result += "**Current Setting:**`n" - $Result += "- ExternalInOutlook: $($OrgConfigObject.ExternalInOutlook)" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: External sender warnings are not enabled in Outlook.`n`n") + $null = $Result.Append("**Current Setting:**`n") + $null = $Result.Append("- ExternalInOutlook: $($OrgConfigObject.ExternalInOutlook)") $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.ps1 index 5b7433097bdc..00acb7805e6c 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO95.ps1 @@ -51,16 +51,16 @@ function Invoke-CippTestCISAMSEXO95 { } if ($FailedPolicies.Count -eq 0) { - $Result = '✅ **Pass**: All malware filter policies block click-to-run files (.exe, .cmd, .vbe).' + $Result = [System.Text.StringBuilder]::new('✅ **Pass**: All malware filter policies block click-to-run files (.exe, .cmd, .vbe).') $Status = 'Passed' } else { - $Result = "❌ **Fail**: $($FailedPolicies.Count) malware filter policy/policies do not properly block click-to-run executables:`n`n" - $Result += "| Policy Name | File Filter Enabled | Missing Blocked Types |`n" - $Result += "| :---------- | :------------------ | :-------------------- |`n" + $Result = [System.Text.StringBuilder]::new("❌ **Fail**: $($FailedPolicies.Count) malware filter policy/policies do not properly block click-to-run executables:`n`n") + $null = $Result.Append("| Policy Name | File Filter Enabled | Missing Blocked Types |`n") + $null = $Result.Append("| :---------- | :------------------ | :-------------------- |`n") foreach ($Policy in $FailedPolicies) { $fileFilterValue = if ($Policy.'File Filter Enabled') { $Policy.'File Filter Enabled' } else { $Policy.'Issue' } $missingTypes = if ($Policy.'Missing Blocked Types') { $Policy.'Missing Blocked Types' } else { 'N/A' } - $Result += "| $($Policy.'Policy Name') | $fileFilterValue | $missingTypes |`n" + $null = $Result.Append("| $($Policy.'Policy Name') | $fileFilterValue | $missingTypes |`n") } $Status = 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady001.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady001.ps1 index fb7dd130ff2d..983269e10c8d 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady001.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady001.ps1 @@ -33,17 +33,17 @@ function Invoke-CippTestCopilotReady001 { if ($EligibleSkus.Count -gt 0) { $Status = 'Passed' - $Result = "Tenant has **$($EligibleSkus.Count)** eligible prerequisite license plan(s) covering **$AssignableCount** seats that qualify for Microsoft 365 Copilot.`n`n" - $Result += "| License | Total Seats | Assigned |`n" - $Result += "|---------|------------|---------|`n" + $Result = [System.Text.StringBuilder]::new("Tenant has **$($EligibleSkus.Count)** eligible prerequisite license plan(s) covering **$AssignableCount** seats that qualify for Microsoft 365 Copilot.`n`n") + $null = $Result.Append("| License | Total Seats | Assigned |`n") + $null = $Result.Append("|---------|------------|---------|`n") foreach ($Sku in $EligibleSkus) { - $Result += "| $($Sku.License) | $($Sku.TotalLicenses) | $($Sku.CountUsed) |`n" + $null = $Result.Append("| $($Sku.License) | $($Sku.TotalLicenses) | $($Sku.CountUsed) |`n") } } else { $Status = 'Failed' - $Result = "No Microsoft 365 Copilot prerequisite licenses were found in this tenant.`n`n" - $Result += 'Users must have an eligible M365 plan before a Copilot add-on license can be assigned. ' - $Result += 'See [Microsoft licensing requirements](https://learn.microsoft.com/en-us/copilot/microsoft-365/microsoft-365-copilot-licensing) for the full list.' + $Result = [System.Text.StringBuilder]::new("No Microsoft 365 Copilot prerequisite licenses were found in this tenant.`n`n") + $null = $Result.Append('Users must have an eligible M365 plan before a Copilot add-on license can be assigned. ') + $null = $Result.Append('See [Microsoft licensing requirements](https://learn.microsoft.com/en-us/copilot/microsoft-365/microsoft-365-copilot-licensing) for the full list.') } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady001' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'High' -Name 'Tenant has M365 Copilot prerequisite licenses' -UserImpact 'High' -ImplementationEffort 'Medium' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady002.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady002.ps1 index 2be9ef3320ad..01542ec1dc53 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady002.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady002.ps1 @@ -38,28 +38,28 @@ function Invoke-CippTestCopilotReady002 { if ($CopilotLicenses.Count -eq 0) { $Status = 'Failed' - $Result = "No Microsoft 365 Copilot add-on licenses were found in this tenant.`n`n" - $Result += 'Purchase Microsoft 365 Copilot licenses and assign them to eligible users to enable Copilot features.' + $Result = [System.Text.StringBuilder]::new("No Microsoft 365 Copilot add-on licenses were found in this tenant.`n`n") + $null = $Result.Append('Purchase Microsoft 365 Copilot licenses and assign them to eligible users to enable Copilot features.') } elseif ($TotalConsumed -eq 0) { $Status = 'Failed' - $Result = "Microsoft 365 Copilot licenses exist (**$TotalEnabled** seats) but **none are assigned** to any users.`n`n" - $Result += "| License | Total Seats | Assigned | Available |`n" - $Result += "|---------|------------|----------|-----------|`n" + $Result = [System.Text.StringBuilder]::new("Microsoft 365 Copilot licenses exist (**$TotalEnabled** seats) but **none are assigned** to any users.`n`n") + $null = $Result.Append("| License | Total Seats | Assigned | Available |`n") + $null = $Result.Append("|---------|------------|----------|-----------|`n") foreach ($Sku in $CopilotLicenses) { $Available = [int]$Sku.TotalLicenses - [int]$Sku.CountUsed - $Result += "| $($Sku.License) | $($Sku.TotalLicenses) | $($Sku.CountUsed) | $Available |`n" + $null = $Result.Append("| $($Sku.License) | $($Sku.TotalLicenses) | $($Sku.CountUsed) | $Available |`n") } } else { $Status = 'Passed' - $Result = "Microsoft 365 Copilot licenses are purchased and assigned.`n`n" - $Result += "| License | Total Seats | Assigned | Available |`n" - $Result += "|---------|------------|----------|-----------|`n" + $Result = [System.Text.StringBuilder]::new("Microsoft 365 Copilot licenses are purchased and assigned.`n`n") + $null = $Result.Append("| License | Total Seats | Assigned | Available |`n") + $null = $Result.Append("|---------|------------|----------|-----------|`n") foreach ($Sku in $CopilotLicenses) { $Available = [int]$Sku.TotalLicenses - [int]$Sku.CountUsed - $Result += "| $($Sku.License) | $($Sku.TotalLicenses) | $($Sku.CountUsed) | $Available |`n" + $null = $Result.Append("| $($Sku.License) | $($Sku.TotalLicenses) | $($Sku.CountUsed) | $Available |`n") } if ($TotalAvailable -gt 0) { - $Result += "`n**$TotalAvailable unassigned seat(s)** are available to assign to additional users." + $null = $Result.Append("`n**$TotalAvailable unassigned seat(s)** are available to assign to additional users.") } } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 index aa2fc5f723fa..a73b499233e0 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady003.ps1 @@ -80,15 +80,15 @@ function Invoke-CippTestCopilotReady003 { if ($DesktopPercent -ge $DesktopThresholdPercent) { $Status = 'Passed' - $Result = "**$DesktopCount of $TotalUsers licensed users ($DesktopPercent%)** have Microsoft 365 Apps activated on a desktop platform (Windows or Mac) — above the $DesktopThresholdPercent% threshold.`n`n" - $Result += "These users can access Copilot features in desktop Word, Excel, PowerPoint, and Outlook." + $Result = [System.Text.StringBuilder]::new("**$DesktopCount of $TotalUsers licensed users ($DesktopPercent%)** have Microsoft 365 Apps activated on a desktop platform (Windows or Mac) — above the $DesktopThresholdPercent% threshold.`n`n") + $null = $Result.Append("These users can access Copilot features in desktop Word, Excel, PowerPoint, and Outlook.") } else { $Status = 'Failed' - $Result = "Only **$DesktopCount of $TotalUsers licensed users ($DesktopPercent%)** have Microsoft 365 Apps activated on a desktop platform — below the $DesktopThresholdPercent% threshold.`n`n" - $Result += "Copilot in Word, Excel, PowerPoint, and Outlook requires the M365 desktop application. " - $Result += "Users with only web or mobile activations, or who have never activated at all, cannot use Copilot's in-document features.`n`n" + $Result = [System.Text.StringBuilder]::new("Only **$DesktopCount of $TotalUsers licensed users ($DesktopPercent%)** have Microsoft 365 Apps activated on a desktop platform — below the $DesktopThresholdPercent% threshold.`n`n") + $null = $Result.Append("Copilot in Word, Excel, PowerPoint, and Outlook requires the M365 desktop application. ") + $null = $Result.Append("Users with only web or mobile activations, or who have never activated at all, cannot use Copilot's in-document features.`n`n") if ($NoDesktopUsers.Count -gt 0 -and $NoDesktopUsers.Count -le 20) { - $Result += "**Users without desktop activation:**`n" + $null = $Result.Append("**Users without desktop activation:**`n") foreach ($User in $NoDesktopUsers) { if ($User.neverActivated) { $PlatformStr = ' (never activated)' @@ -97,13 +97,13 @@ function Invoke-CippTestCopilotReady003 { if ([int]($User.android ?? 0) -gt 0 -or [int]($User.ios ?? 0) -gt 0) { $Platforms += 'Mobile' } $PlatformStr = if ($Platforms) { " ($(($Platforms -join ', ')) only)" } else { ' (no activations)' } } - $Result += "- $($User.displayName) ($($User.userPrincipalName))$PlatformStr`n" + $null = $Result.Append("- $($User.displayName) ($($User.userPrincipalName))$PlatformStr`n") } } elseif ($NoDesktopUsers.Count -gt 20) { $NeverActivated = @($NoDesktopUsers | Where-Object { $_.neverActivated }).Count - $Result += "**$($NoDesktopUsers.Count) users** have no desktop M365 Apps activation" - if ($NeverActivated -gt 0) { $Result += " ($NeverActivated have never activated on any platform)" } - $Result += ".`n" + $null = $Result.Append("**$($NoDesktopUsers.Count) users** have no desktop M365 Apps activation") + if ($NeverActivated -gt 0) { $null = $Result.Append(" ($NeverActivated have never activated on any platform)") } + $null = $Result.Append(".`n") } } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady004.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady004.ps1 index 721bb9058bd8..519029d6747d 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady004.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady004.ps1 @@ -56,18 +56,18 @@ function Invoke-CippTestCopilotReady004 { if ($ActivityPercent -ge $ActivityThresholdPercent) { $Status = 'Passed' - $Result = "**$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** sent email in the past 30 days — above the $ActivityThresholdPercent% threshold.`n`n" - $Result += 'These users are good candidates for Copilot in Outlook, which provides AI-assisted drafting, summarization, and email coaching.' + $Result = [System.Text.StringBuilder]::new("**$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** sent email in the past 30 days — above the $ActivityThresholdPercent% threshold.`n`n") + $null = $Result.Append('These users are good candidates for Copilot in Outlook, which provides AI-assisted drafting, summarization, and email coaching.') } else { $Status = 'Failed' - $Result = "Only **$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** sent email in the past 30 days — below the $ActivityThresholdPercent% threshold.`n`n" - $Result += 'Copilot for Outlook delivers the most value to active email users. ' - $Result += "Consider reviewing Exchange Online license assignment and adoption before rolling out Copilot.`n`n" + $Result = [System.Text.StringBuilder]::new("Only **$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** sent email in the past 30 days — below the $ActivityThresholdPercent% threshold.`n`n") + $null = $Result.Append('Copilot for Outlook delivers the most value to active email users. ') + $null = $Result.Append("Consider reviewing Exchange Online license assignment and adoption before rolling out Copilot.`n`n") if ($InactiveUsers.Count -gt 0 -and $InactiveUsers.Count -le 20) { - $Result += "**Inactive users (no Outlook email in 30 days):**`n" - foreach ($Upn in $InactiveUsers) { $Result += "- $Upn`n" } + $null = $Result.Append("**Inactive users (no Outlook email in 30 days):**`n") + foreach ($Upn in $InactiveUsers) { $null = $Result.Append("- $Upn`n") } } elseif ($InactiveUsers.Count -gt 20) { - $Result += "**$($InactiveUsers.Count) users** had no Outlook email activity in the past 30 days." + $null = $Result.Append("**$($InactiveUsers.Count) users** had no Outlook email activity in the past 30 days.") } } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady005.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady005.ps1 index c8ffe38a60f9..a91dfc76d8ee 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady005.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady005.ps1 @@ -56,18 +56,18 @@ function Invoke-CippTestCopilotReady005 { if ($ActivityPercent -ge $ActivityThresholdPercent) { $Status = 'Passed' - $Result = "**$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** used Teams meetings or chat in the past 30 days — above the $ActivityThresholdPercent% threshold.`n`n" - $Result += 'These users are strong candidates for Copilot in Teams, which provides meeting summaries, chat recaps, and real-time meeting assistance.' + $Result = [System.Text.StringBuilder]::new("**$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** used Teams meetings or chat in the past 30 days — above the $ActivityThresholdPercent% threshold.`n`n") + $null = $Result.Append('These users are strong candidates for Copilot in Teams, which provides meeting summaries, chat recaps, and real-time meeting assistance.') } else { $Status = 'Failed' - $Result = "Only **$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** used Teams meetings or chat in the past 30 days — below the $ActivityThresholdPercent% threshold.`n`n" - $Result += 'Copilot for Teams delivers the most value to users who regularly use chat and meetings. ' - $Result += "Consider driving Teams adoption before or alongside a Copilot rollout.`n`n" + $Result = [System.Text.StringBuilder]::new("Only **$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** used Teams meetings or chat in the past 30 days — below the $ActivityThresholdPercent% threshold.`n`n") + $null = $Result.Append('Copilot for Teams delivers the most value to users who regularly use chat and meetings. ') + $null = $Result.Append("Consider driving Teams adoption before or alongside a Copilot rollout.`n`n") if ($InactiveUsers.Count -gt 0 -and $InactiveUsers.Count -le 20) { - $Result += "**Inactive users (no Teams activity in 30 days):**`n" - foreach ($Upn in $InactiveUsers) { $Result += "- $Upn`n" } + $null = $Result.Append("**Inactive users (no Teams activity in 30 days):**`n") + foreach ($Upn in $InactiveUsers) { $null = $Result.Append("- $Upn`n") } } elseif ($InactiveUsers.Count -gt 20) { - $Result += "**$($InactiveUsers.Count) users** had no Teams meetings or chat activity in the past 30 days." + $null = $Result.Append("**$($InactiveUsers.Count) users** had no Teams meetings or chat activity in the past 30 days.") } } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady006.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady006.ps1 index be6c118d3328..fd6e5a4616e5 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady006.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady006.ps1 @@ -56,18 +56,18 @@ function Invoke-CippTestCopilotReady006 { if ($ActivityPercent -ge $ActivityThresholdPercent) { $Status = 'Passed' - $Result = "**$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** worked on OneDrive or SharePoint files in the past 30 days \u2014 above the $ActivityThresholdPercent% threshold.`n`n" - $Result += 'These users are strong candidates for Copilot, which provides the most value when users actively collaborate on files in Microsoft 365.' + $Result = [System.Text.StringBuilder]::new("**$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** worked on OneDrive or SharePoint files in the past 30 days \u2014 above the $ActivityThresholdPercent% threshold.`n`n") + $null = $Result.Append('These users are strong candidates for Copilot, which provides the most value when users actively collaborate on files in Microsoft 365.') } else { $Status = 'Failed' - $Result = "Only **$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** worked on OneDrive or SharePoint files in the past 30 days \u2014 below the $ActivityThresholdPercent% threshold.`n`n" - $Result += 'Copilot delivers the most value when users regularly store and collaborate on files in OneDrive and SharePoint. ' - $Result += "Consider driving file collaboration adoption before or alongside a Copilot rollout.`n`n" + $Result = [System.Text.StringBuilder]::new("Only **$ActiveCount of $TotalUsers licensed users ($ActivityPercent%)** worked on OneDrive or SharePoint files in the past 30 days \u2014 below the $ActivityThresholdPercent% threshold.`n`n") + $null = $Result.Append('Copilot delivers the most value when users regularly store and collaborate on files in OneDrive and SharePoint. ') + $null = $Result.Append("Consider driving file collaboration adoption before or alongside a Copilot rollout.`n`n") if ($InactiveUsers.Count -gt 0 -and $InactiveUsers.Count -le 20) { - $Result += "**Inactive users (no Office doc activity in 30 days):**`n" - foreach ($Upn in $InactiveUsers) { $Result += "- $Upn`n" } + $null = $Result.Append("**Inactive users (no Office doc activity in 30 days):**`n") + foreach ($Upn in $InactiveUsers) { $null = $Result.Append("- $Upn`n") } } elseif ($InactiveUsers.Count -gt 20) { - $Result += "**$($InactiveUsers.Count) users** had no OneDrive or SharePoint file activity in the past 30 days." + $null = $Result.Append("**$($InactiveUsers.Count) users** had no OneDrive or SharePoint file activity in the past 30 days.") } } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady007.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady007.ps1 index 5e67952c4017..a3dff2755827 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady007.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady007.ps1 @@ -59,19 +59,19 @@ function Invoke-CippTestCopilotReady007 { if ($ChannelPercent -ge $ChannelThresholdPercent) { $Status = 'Passed' - $Result = "**$QualifiedCount of $TotalUsers M365 Apps licensed users ($ChannelPercent%)** are on Current Channel or Monthly Enterprise Channel — above the $ChannelThresholdPercent% threshold.`n`n" - $Result += 'These users will receive Copilot feature updates for desktop Word, Excel, PowerPoint, Outlook, and OneNote.' + $Result = [System.Text.StringBuilder]::new("**$QualifiedCount of $TotalUsers M365 Apps licensed users ($ChannelPercent%)** are on Current Channel or Monthly Enterprise Channel — above the $ChannelThresholdPercent% threshold.`n`n") + $null = $Result.Append('These users will receive Copilot feature updates for desktop Word, Excel, PowerPoint, Outlook, and OneNote.') } else { $Status = 'Failed' - $Result = "Only **$QualifiedCount of $TotalUsers M365 Apps licensed users ($ChannelPercent%)** are on a qualified update channel — below the $ChannelThresholdPercent% threshold.`n`n" - $Result += 'Copilot in M365 desktop apps requires **Current Channel** or **Monthly Enterprise Channel**. ' - $Result += "Users on Semi-Annual Enterprise Channel or other update rings will not receive Copilot features.`n`n" - $Result += "To remediate, update the Microsoft 365 Apps update channel via Microsoft Intune, Microsoft 365 admin center, or Group Policy.`n`n" + $Result = [System.Text.StringBuilder]::new("Only **$QualifiedCount of $TotalUsers M365 Apps licensed users ($ChannelPercent%)** are on a qualified update channel — below the $ChannelThresholdPercent% threshold.`n`n") + $null = $Result.Append('Copilot in M365 desktop apps requires **Current Channel** or **Monthly Enterprise Channel**. ') + $null = $Result.Append("Users on Semi-Annual Enterprise Channel or other update rings will not receive Copilot features.`n`n") + $null = $Result.Append("To remediate, update the Microsoft 365 Apps update channel via Microsoft Intune, Microsoft 365 admin center, or Group Policy.`n`n") if ($NotQualifiedUsers.Count -gt 0 -and $NotQualifiedUsers.Count -le 20) { - $Result += "**Users not on a qualified update channel:**`n" - foreach ($Upn in $NotQualifiedUsers) { $Result += "- $Upn`n" } + $null = $Result.Append("**Users not on a qualified update channel:**`n") + foreach ($Upn in $NotQualifiedUsers) { $null = $Result.Append("- $Upn`n") } } elseif ($NotQualifiedUsers.Count -gt 20) { - $Result += "**$($NotQualifiedUsers.Count) users** are not on a qualified update channel." + $null = $Result.Append("**$($NotQualifiedUsers.Count) users** are not on a qualified update channel.") } } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady008.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady008.ps1 index fd4254a173cf..79cf9ee11344 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady008.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady008.ps1 @@ -69,20 +69,20 @@ function Invoke-CippTestCopilotReady008 { $MedPct = [math]::Round(($MediumTier.Count / $Total) * 100, 1) $LowPct = [math]::Round(($LowTier.Count / $Total) * 100, 1) - $Result = "## Copilot Candidate Tier Breakdown`n`n" - $Result += "Scoring is based on 6 readiness signals from the Microsoft 365 Copilot Readiness report (30-day window).`n`n" - $Result += "| Tier | Users | % of Tenant | Description |`n" - $Result += "|------|-------|-------------|-------------|`n" - $Result += "| **High** (≥4 signals) | $($HighTier.Count) | $HighPct% | Power M365 users — strongest Copilot ROI |`n" - $Result += "| **Medium** (3 signals) | $($MediumTier.Count) | $MedPct% | Engaged users — good Copilot candidates |`n" - $Result += "| **Low** (≤2 signals) | $($LowTier.Count) | $LowPct% | Low engagement — adopt M365 basics first |`n" - $Result += "`n**Signals scored:** Copilot license assigned, qualified update channel, Teams meetings, Teams chat, Outlook email, Office documents (each = 1 point)`n" + $Result = [System.Text.StringBuilder]::new("## Copilot Candidate Tier Breakdown`n`n") + $null = $Result.Append("Scoring is based on 6 readiness signals from the Microsoft 365 Copilot Readiness report (30-day window).`n`n") + $null = $Result.Append("| Tier | Users | % of Tenant | Description |`n") + $null = $Result.Append("|------|-------|-------------|-------------|`n") + $null = $Result.Append("| **High** (≥4 signals) | $($HighTier.Count) | $HighPct% | Power M365 users — strongest Copilot ROI |`n") + $null = $Result.Append("| **Medium** (3 signals) | $($MediumTier.Count) | $MedPct% | Engaged users — good Copilot candidates |`n") + $null = $Result.Append("| **Low** (≤2 signals) | $($LowTier.Count) | $LowPct% | Low engagement — adopt M365 basics first |`n") + $null = $Result.Append("`n**Signals scored:** Copilot license assigned, qualified update channel, Teams meetings, Teams chat, Outlook email, Office documents (each = 1 point)`n") if ($HighTier.Count -gt 0 -and $HighTier.Count -le 20) { - $Result += "`n**High tier users:**`n" - foreach ($Upn in $HighTier) { $Result += "- $Upn`n" } + $null = $Result.Append("`n**High tier users:**`n") + foreach ($Upn in $HighTier) { $null = $Result.Append("- $Upn`n") } } elseif ($HighTier.Count -gt 20) { - $Result += "`n*$($HighTier.Count) users are in the high tier — use the Microsoft 365 Copilot Readiness report in the admin center for the full list.*`n" + $null = $Result.Append("`n*$($HighTier.Count) users are in the high tier — use the Microsoft 365 Copilot Readiness report in the admin center for the full list.*`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady008' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Copilot candidate tier breakdown' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady009.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady009.ps1 index d98182851d1f..c02db723fd5d 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady009.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady009.ps1 @@ -57,13 +57,13 @@ function Invoke-CippTestCopilotReady009 { if ($ReadyPercent -ge $AdoptionThresholdPercent) { $Status = 'Passed' - $Result = "**$MediumOrAbove of $Total licensed users ($ReadyPercent%)** score Medium or above on Copilot readiness signals — above the $AdoptionThresholdPercent% threshold.`n`n" - $Result += 'This tenant has strong M365 engagement across the user base and is well-positioned for a Copilot rollout.' + $Result = [System.Text.StringBuilder]::new("**$MediumOrAbove of $Total licensed users ($ReadyPercent%)** score Medium or above on Copilot readiness signals — above the $AdoptionThresholdPercent% threshold.`n`n") + $null = $Result.Append('This tenant has strong M365 engagement across the user base and is well-positioned for a Copilot rollout.') } else { $Status = 'Failed' - $Result = "Only **$MediumOrAbove of $Total licensed users ($ReadyPercent%)** score Medium or above on Copilot readiness signals — below the $AdoptionThresholdPercent% threshold.`n`n" - $Result += "**$LowCount users** have low M365 engagement (≤2 of 6 signals). Copilot delivers the most value where users are already active across Teams, Outlook, and Office apps.`n`n" - $Result += "Consider running an M365 adoption campaign — focused on Teams meetings, Teams chat, Outlook, and OneDrive/SharePoint file usage — before or alongside a Copilot rollout.`n`n" + $Result = [System.Text.StringBuilder]::new("Only **$MediumOrAbove of $Total licensed users ($ReadyPercent%)** score Medium or above on Copilot readiness signals — below the $AdoptionThresholdPercent% threshold.`n`n") + $null = $Result.Append("**$LowCount users** have low M365 engagement (≤2 of 6 signals). Copilot delivers the most value where users are already active across Teams, Outlook, and Office apps.`n`n") + $null = $Result.Append("Consider running an M365 adoption campaign — focused on Teams meetings, Teams chat, Outlook, and OneDrive/SharePoint file usage — before or alongside a Copilot rollout.`n`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady009' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'High' -Name 'Majority of users are Copilot-ready (Medium or above)' -UserImpact 'High' -ImplementationEffort 'Medium' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady010.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady010.ps1 index b12a6eadb155..86989d192ce0 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady010.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady010.ps1 @@ -52,17 +52,17 @@ function Invoke-CippTestCopilotReady010 { if ($NotRegistered.Count -eq 0) { $Status = 'Passed' - $Result = "All **$Total licensed users** have MFA registered — the tenant meets the MFA security baseline for Copilot deployment." + $Result = [System.Text.StringBuilder]::new("All **$Total licensed users** have MFA registered — the tenant meets the MFA security baseline for Copilot deployment.") } else { $Status = 'Failed' - $Result = "**$($NotRegistered.Count) of $Total licensed users ($([math]::Round(($NotRegistered.Count / $Total) * 100, 1))%)** do not have MFA registered.`n`n" - $Result += 'MFA is a security baseline requirement before deploying Copilot. Accounts without MFA present elevated risk when Copilot has access to tenant data.`n`n' - $Result += "Remediate by enforcing MFA via Conditional Access or per-user MFA, and requiring users to register via [aka.ms/mfasetup](https://aka.ms/mfasetup).`n`n" + $Result = [System.Text.StringBuilder]::new("**$($NotRegistered.Count) of $Total licensed users ($([math]::Round(($NotRegistered.Count / $Total) * 100, 1))%)** do not have MFA registered.`n`n") + $null = $Result.Append('MFA is a security baseline requirement before deploying Copilot. Accounts without MFA present elevated risk when Copilot has access to tenant data.`n`n') + $null = $Result.Append("Remediate by enforcing MFA via Conditional Access or per-user MFA, and requiring users to register via [aka.ms/mfasetup](https://aka.ms/mfasetup).`n`n") if ($NotRegistered.Count -le 20) { - $Result += "**Users without MFA registered:**`n" - foreach ($Upn in $NotRegistered) { $Result += "- $Upn`n" } + $null = $Result.Append("**Users without MFA registered:**`n") + foreach ($Upn in $NotRegistered) { $null = $Result.Append("- $Upn`n") } } else { - $Result += "**$($NotRegistered.Count) users** do not have MFA registered." + $null = $Result.Append("**$($NotRegistered.Count) users** do not have MFA registered.") } } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady011.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady011.ps1 index 064ead6b549c..8348dd2f1ae8 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady011.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady011.ps1 @@ -23,24 +23,24 @@ function Invoke-CippTestCopilotReady011 { if ($EnabledPolicies.Count -gt 0) { $Status = 'Passed' - $Result = "**$($EnabledPolicies.Count) enabled Conditional Access polic$(if ($EnabledPolicies.Count -eq 1) { 'y' } else { 'ies' })** found in the tenant.`n`n" - $Result += "| Policy Name | State |`n" - $Result += "|-------------|-------|`n" + $Result = [System.Text.StringBuilder]::new("**$($EnabledPolicies.Count) enabled Conditional Access polic$(if ($EnabledPolicies.Count -eq 1) { 'y' } else { 'ies' })** found in the tenant.`n`n") + $null = $Result.Append("| Policy Name | State |`n") + $null = $Result.Append("|-------------|-------|`n") foreach ($Policy in ($EnabledPolicies | Sort-Object displayName)) { - $Result += "| $($Policy.displayName) | Enabled |`n" + $null = $Result.Append("| $($Policy.displayName) | Enabled |`n") } if ($ReportOnlyPolicies.Count -gt 0) { - $Result += "`n*$($ReportOnlyPolicies.Count) additional polic$(if ($ReportOnlyPolicies.Count -eq 1) { 'y is' } else { 'ies are' }) in report-only mode and not enforcing access controls.*" + $null = $Result.Append("`n*$($ReportOnlyPolicies.Count) additional polic$(if ($ReportOnlyPolicies.Count -eq 1) { 'y is' } else { 'ies are' }) in report-only mode and not enforcing access controls.*") } } else { $Status = 'Failed' - $Result = "No enabled Conditional Access policies were found in this tenant.`n`n" + $Result = [System.Text.StringBuilder]::new("No enabled Conditional Access policies were found in this tenant.`n`n") if ($ReportOnlyPolicies.Count -gt 0) { - $Result += "**$($ReportOnlyPolicies.Count) polic$(if ($ReportOnlyPolicies.Count -eq 1) { 'y is' } else { 'ies are' }) in report-only mode** but not enforcing.`n`n" + $null = $Result.Append("**$($ReportOnlyPolicies.Count) polic$(if ($ReportOnlyPolicies.Count -eq 1) { 'y is' } else { 'ies are' }) in report-only mode** but not enforcing.`n`n") } - $Result += 'Conditional Access is the primary mechanism for enforcing MFA, device compliance, and access controls in Entra ID. ' - $Result += 'Before deploying Copilot, establish at least a baseline CA policy requiring MFA for all users. ' - $Result += 'See [Microsoft CA policy templates](https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-policy-common) to get started.' + $null = $Result.Append('Conditional Access is the primary mechanism for enforcing MFA, device compliance, and access controls in Entra ID. ') + $null = $Result.Append('Before deploying Copilot, establish at least a baseline CA policy requiring MFA for all users. ') + $null = $Result.Append('See [Microsoft CA policy templates](https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-policy-common) to get started.') } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady011' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'High' -Name 'Tenant has enabled Conditional Access policies' -UserImpact 'High' -ImplementationEffort 'Medium' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady012.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady012.ps1 index 6e3a2e5023d0..ea6a590526c3 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady012.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady012.ps1 @@ -47,21 +47,21 @@ function Invoke-CippTestCopilotReady012 { if ($Issues.Count -eq 0) { $Status = 'Passed' - $Result = "All user self-service creation permissions are restricted — users cannot create groups, tenants, or register applications without admin involvement.`n`n" - $Result += 'This reduces shadow IT risk and ensures governance controls apply to new M365 resources before Copilot can interact with them.' + $Result = [System.Text.StringBuilder]::new("All user self-service creation permissions are restricted — users cannot create groups, tenants, or register applications without admin involvement.`n`n") + $null = $Result.Append('This reduces shadow IT risk and ensures governance controls apply to new M365 resources before Copilot can interact with them.') } else { $Status = 'Failed' - $Result = "**$($Issues.Count) user permission$(if ($Issues.Count -eq 1) { '' } else { 's' })** allow unrestricted self-service creation.`n`n" - $Result += "| Permission | Status |`n" - $Result += "|------------|--------|`n" + $Result = [System.Text.StringBuilder]::new("**$($Issues.Count) user permission$(if ($Issues.Count -eq 1) { '' } else { 's' })** allow unrestricted self-service creation.`n`n") + $null = $Result.Append("| Permission | Status |`n") + $null = $Result.Append("|------------|--------|`n") foreach ($Issue in $Issues) { - $Result += "| $Issue | ⚠️ Unrestricted |`n" + $null = $Result.Append("| $Issue | ⚠️ Unrestricted |`n") } foreach ($Ok in $Restricted) { - $Result += "| $Ok | ✅ Restricted |`n" + $null = $Result.Append("| $Ok | ✅ Restricted |`n") } - $Result += "`nWith Copilot deployed, unrestricted group and app creation increases the risk of uncontrolled data exposure. " - $Result += 'Restrict these permissions via **Entra ID → User settings** and **Group settings** to ensure new resources go through a governed process.' + $null = $Result.Append("`nWith Copilot deployed, unrestricted group and app creation increases the risk of uncontrolled data exposure. ") + $null = $Result.Append('Restrict these permissions via **Entra ID → User settings** and **Group settings** to ensure new resources go through a governed process.') } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady012' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'User self-service creation is restricted (groups, tenants, apps)' -UserImpact 'Medium' -ImplementationEffort 'Low' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady013.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady013.ps1 index 95ccd53d9e2f..e72e1dfa6618 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady013.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady013.ps1 @@ -22,20 +22,20 @@ function Invoke-CippTestCopilotReady013 { if ($ActiveLabels.Count -gt 0) { $Status = 'Passed' - $Result = "**$($ActiveLabels.Count) active sensitivity label$(if ($ActiveLabels.Count -eq 1) { '' } else { 's' })** found in the tenant.`n`n" - $Result += "| Label | Parent | Has Protection |`n" - $Result += "|-------|--------|---------------|`n" + $Result = [System.Text.StringBuilder]::new("**$($ActiveLabels.Count) active sensitivity label$(if ($ActiveLabels.Count -eq 1) { '' } else { 's' })** found in the tenant.`n`n") + $null = $Result.Append("| Label | Parent | Has Protection |`n") + $null = $Result.Append("|-------|--------|---------------|`n") foreach ($Label in ($ActiveLabels | Sort-Object sensitivity)) { $ParentName = if ($Label.parent -and $Label.parent.name) { $Label.parent.name } else { '—' } $HasProtection = if ($Label.hasProtection -eq $true) { '✅ Yes' } else { 'No' } - $Result += "| $($Label.name) | $ParentName | $HasProtection |`n" + $null = $Result.Append("| $($Label.name) | $ParentName | $HasProtection |`n") } - $Result += "`nCopilot can respect and apply these labels when creating or summarizing content." + $null = $Result.Append("`nCopilot can respect and apply these labels when creating or summarizing content.") } else { $Status = 'Failed' - $Result = "No active sensitivity labels were found in this tenant.`n`n" - $Result += 'Sensitivity labels classify and protect organizational data — helping ensure Copilot-generated content is appropriately marked. ' - $Result += 'Configure labels in the [Microsoft Purview compliance portal](https://compliance.microsoft.com/informationprotection) before deploying Copilot.' + $Result = [System.Text.StringBuilder]::new("No active sensitivity labels were found in this tenant.`n`n") + $null = $Result.Append('Sensitivity labels classify and protect organizational data — helping ensure Copilot-generated content is appropriately marked. ') + $null = $Result.Append('Configure labels in the [Microsoft Purview compliance portal](https://compliance.microsoft.com/informationprotection) before deploying Copilot.') } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady013' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Tenant has sensitivity labels configured in Purview' -UserImpact 'Medium' -ImplementationEffort 'Medium' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady014.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady014.ps1 index d57a807a3747..0d5160c1a985 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady014.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady014.ps1 @@ -23,22 +23,22 @@ function Invoke-CippTestCopilotReady014 { if ($EnabledPolicies.Count -gt 0) { $Status = 'Passed' - $Result = "**$($EnabledPolicies.Count) enabled DLP polic$(if ($EnabledPolicies.Count -eq 1) { 'y' } else { 'ies' })** found in the tenant.`n`n" - $Result += "| Policy | Workload | Enabled |`n" - $Result += "|--------|----------|---------|`n" + $Result = [System.Text.StringBuilder]::new("**$($EnabledPolicies.Count) enabled DLP polic$(if ($EnabledPolicies.Count -eq 1) { 'y' } else { 'ies' })** found in the tenant.`n`n") + $null = $Result.Append("| Policy | Workload | Enabled |`n") + $null = $Result.Append("|--------|----------|---------|`n") foreach ($Policy in ($AllPolicies | Sort-Object DisplayName)) { $IsEnabled = if ($Policy.Mode -eq 'Enable' -and $Policy.Enabled -eq $true) { '✅ Yes' } else { 'No' } $Workload = if ($Policy.Workload) { $Policy.Workload } else { '—' } - $Result += "| $($Policy.DisplayName) | $Workload | $IsEnabled |`n" + $null = $Result.Append("| $($Policy.DisplayName) | $Workload | $IsEnabled |`n") } } else { $Status = 'Failed' - $Result = "No enabled DLP policies were found in this tenant.`n`n" + $Result = [System.Text.StringBuilder]::new("No enabled DLP policies were found in this tenant.`n`n") if ($AllPolicies.Count -gt 0) { - $Result += "**$($AllPolicies.Count) polic$(if ($AllPolicies.Count -eq 1) { 'y exists' } else { 'ies exist' })** but none are enabled.`n`n" + $null = $Result.Append("**$($AllPolicies.Count) polic$(if ($AllPolicies.Count -eq 1) { 'y exists' } else { 'ies exist' })** but none are enabled.`n`n") } - $Result += 'Data Loss Prevention policies help protect sensitive information from being shared inappropriately — a critical control when Copilot can surface content broadly. ' - $Result += 'Enable or create DLP policies in the [Microsoft Purview compliance portal](https://compliance.microsoft.com/datalossprevention) before deploying Copilot.' + $null = $Result.Append('Data Loss Prevention policies help protect sensitive information from being shared inappropriately — a critical control when Copilot can surface content broadly. ') + $null = $Result.Append('Enable or create DLP policies in the [Microsoft Purview compliance portal](https://compliance.microsoft.com/datalossprevention) before deploying Copilot.') } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady014' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Tenant has enabled DLP policies configured' -UserImpact 'Medium' -ImplementationEffort 'Medium' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 index d59731ab2114..3bb45ac64ef9 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 @@ -43,10 +43,10 @@ function Invoke-CippTestCopilotReady015 { $DisplayUsers = $ActiveUsers | Sort-Object lastActivityDate -Descending | Select-Object -First 50 foreach ($User in $DisplayUsers) { $LastActive = if ($User.lastActivityDate) { $User.lastActivityDate } else { 'N/A' } - $Row = "| $($User.userPrincipalName) | $LastActive |" + $Row = [System.Text.StringBuilder]::new("| $($User.userPrincipalName) | $LastActive |") foreach ($Col in $AppColumns) { $Val = $User.$Col - $Row += " $Val |" + $null = $Row.Append(" $Val |") } $Result += "$Row`n" } diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady016.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady016.ps1 index 9ee4f3779981..5fca7f31fb2a 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady016.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady016.ps1 @@ -30,25 +30,25 @@ function Invoke-CippTestCopilotReady016 { $TotalAppCount = ($AppCounts | Measure-Object -Property Value -Sum).Sum ?? 0 if (-not $AppCounts -or $TotalAppCount -eq 0) { - $Result = "No Microsoft 365 Copilot usage was detected in the past 30 days.`n`n" - $Result += 'This tenant either has no Copilot licenses assigned or users have not yet started using Copilot features.' + $Result = [System.Text.StringBuilder]::new("No Microsoft 365 Copilot usage was detected in the past 30 days.`n`n") + $null = $Result.Append('This tenant either has no Copilot licenses assigned or users have not yet started using Copilot features.') Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady016' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Copilot active user count by app' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Copilot Readiness' return } - $Result = "## Copilot Active Users by App (Last 30 Days)`n`n" - $Result += "| App | Active Users |`n" - $Result += "|-----|-------------|`n" + $Result = [System.Text.StringBuilder]::new("## Copilot Active Users by App (Last 30 Days)`n`n") + $null = $Result.Append("| App | Active Users |`n") + $null = $Result.Append("|-----|-------------|`n") foreach ($App in ($AppCounts | Sort-Object Value -Descending)) { # Format the property name to be more readable — insert space before each capital # that follows a lowercase letter to avoid double-spacing sequences like 'AI' -> 'A I' $AppName = $App.Name -replace '([a-z])([A-Z])', '$1 $2' -replace 'Active Users', '' - $Result += "| $($AppName.Trim()) | $($App.Value) |`n" + $null = $Result.Append("| $($AppName.Trim()) | $($App.Value) |`n") } if ($Summary.reportRefreshDate) { - $Result += "`n*Data as of $($Summary.reportRefreshDate).*" + $null = $Result.Append("`n*Data as of $($Summary.reportRefreshDate).*") } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady016' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Copilot active user count by app' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady017.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady017.ps1 index 374696adc407..10b13fb99ed6 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady017.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady017.ps1 @@ -21,8 +21,8 @@ function Invoke-CippTestCopilotReady017 { $TrendPoints = @($TrendData | Where-Object { $_.reportDate } | Sort-Object reportDate) if ($TrendPoints.Count -eq 0) { - $Result = "No Microsoft 365 Copilot usage trend data was found for the past 7 days.`n`n" - $Result += 'This tenant either has no Copilot licenses assigned or users have not yet started using Copilot features.' + $Result = [System.Text.StringBuilder]::new("No Microsoft 365 Copilot usage trend data was found for the past 7 days.`n`n") + $null = $Result.Append('This tenant either has no Copilot licenses assigned or users have not yet started using Copilot features.') Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady017' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Copilot adoption trend (7-day)' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Copilot Readiness' return } @@ -33,13 +33,13 @@ function Invoke-CippTestCopilotReady017 { } | Select-Object -First 1 # Build trend table - $Result = "## Copilot Active User Trend (Last 7 Days)`n`n" - $Result += "| Date | Active Users |`n" - $Result += "|------|-------------|`n" + $Result = [System.Text.StringBuilder]::new("## Copilot Active User Trend (Last 7 Days)`n`n") + $null = $Result.Append("| Date | Active Users |`n") + $null = $Result.Append("|------|-------------|`n") foreach ($Point in $TrendPoints) { $Count = if ($CountField) { $Point.$CountField } else { 'N/A' } - $Result += "| $($Point.reportDate) | $Count |`n" + $null = $Result.Append("| $($Point.reportDate) | $Count |`n") } # Determine trend direction if we have a count field and at least 2 data points @@ -60,7 +60,7 @@ function Invoke-CippTestCopilotReady017 { $TrendText = "**Trending down** — active Copilot users decreased by $([math]::Abs($Delta)) over the 7-day window. Consider reviewing adoption activities to re-engage users." } - $Result += "`n$TrendIcon $TrendText" + $null = $Result.Append("`n$TrendIcon $TrendText") } Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady017' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Informational' -Name 'Copilot adoption trend (7-day)' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/Custom/Invoke-CippTestCustomScripts.ps1 b/Modules/CIPPTests/Public/Tests/Custom/Invoke-CippTestCustomScripts.ps1 index c9ab3230c8c3..c64a9b1d03ce 100644 --- a/Modules/CIPPTests/Public/Tests/Custom/Invoke-CippTestCustomScripts.ps1 +++ b/Modules/CIPPTests/Public/Tests/Custom/Invoke-CippTestCustomScripts.ps1 @@ -16,9 +16,19 @@ function Invoke-CippTestCustomScripts { return } - $LatestScripts = $Scripts | Group-Object -Property ScriptGuid | ForEach-Object { - $_.Group | Sort-Object -Property Version -Descending | Select-Object -First 1 + # Pick the latest version per ScriptGuid in a single pass instead of the + # original Group-Object | ForEach-Object { Sort-Object | Select -First 1 } + # pipeline (item 8). + $LatestByGuid = @{} + foreach ($S in $Scripts) { + $Guid = $S.ScriptGuid + if (-not $Guid) { continue } + $Existing = $LatestByGuid[$Guid] + if (-not $Existing -or [int]$S.Version -gt [int]$Existing.Version) { + $LatestByGuid[$Guid] = $S + } } + $LatestScripts = @($LatestByGuid.Values) if (-not [string]::IsNullOrWhiteSpace($ScriptGuid) -and $LatestScripts.Count -eq 0) { Write-Information "No latest custom script found for ScriptGuid: $ScriptGuid" @@ -26,25 +36,29 @@ function Invoke-CippTestCustomScripts { } foreach ($Script in $LatestScripts) { + # Cache PSObject property lookups once per script so we don't pay the + # member-resolution cost repeatedly inside the hot loop (item 13). + $Props = $Script.PSObject.Properties + $EnabledProp = $Props['Enabled'] + $AlertProp = $Props['AlertOnFailure'] + $ResultModeProp = $Props['ResultMode'] + $AlertStatusesProp = $Props['AlertStatuses'] + # We can't prefilter this on table lookup as each script version has its own Enabled property, so we need to check here if the latest version is enabled - $IsEnabled = if ($Script.PSObject.Properties['Enabled']) { [bool]$Script.Enabled } else { $true } + $IsEnabled = if ($EnabledProp) { [bool]$EnabledProp.Value } else { $true } if (-not $IsEnabled) { continue } - $ShouldAlert = $false - if ($Script.PSObject.Properties['AlertOnFailure']) { - $ShouldAlert = [bool]$Script.AlertOnFailure - } + $ShouldAlert = if ($AlertProp) { [bool]$AlertProp.Value } else { $false } - $ResultMode = if ($Script.PSObject.Properties['ResultMode'] -and -not [string]::IsNullOrWhiteSpace($Script.ResultMode)) { $Script.ResultMode } else { 'Auto' } + $ResultMode = if ($ResultModeProp -and -not [string]::IsNullOrWhiteSpace($ResultModeProp.Value)) { $ResultModeProp.Value } else { 'Auto' } $TestId = "CustomScript-$($Script.ScriptGuid)" $ScriptName = if ([string]::IsNullOrWhiteSpace($Script.ScriptName)) { $TestId } else { $Script.ScriptName } $AlertStatuses = @('Failed') - if ($Script.PSObject.Properties['AlertStatuses'] -and - -not [string]::IsNullOrWhiteSpace($Script.AlertStatuses)) { - $AlertStatuses = $Script.AlertStatuses | ConvertFrom-Json + if ($AlertStatusesProp -and -not [string]::IsNullOrWhiteSpace($AlertStatusesProp.Value)) { + $AlertStatuses = $AlertStatusesProp.Value | ConvertFrom-Json } try { diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest001.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest001.ps1 index 2ae2479ef32b..3569629c5dd1 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest001.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest001.ps1 @@ -18,10 +18,10 @@ function Invoke-CippTestGenericTest001 { $TotalUsed = ($Licenses | ForEach-Object { [int]$_.CountUsed } | Measure-Object -Sum).Sum $OverallUtilization = if ($TotalLicenses -gt 0) { [math]::Round(($TotalUsed / $TotalLicenses) * 100, 1) } else { 0 } - $Result = "**Total Licenses:** $TotalLicenses | **In Use:** $TotalUsed | **Overall Utilization:** $OverallUtilization%`n`n" + $Result = [System.Text.StringBuilder]::new("**Total Licenses:** $TotalLicenses | **In Use:** $TotalUsed | **Overall Utilization:** $OverallUtilization%`n`n") - $Result += "| License | In Use | Total | Available | Utilization |`n" - $Result += "|---------|--------|-------|-----------|-------------|`n" + $null = $Result.Append("| License | In Use | Total | Available | Utilization |`n") + $null = $Result.Append("|---------|--------|-------|-----------|-------------|`n") foreach ($License in ($Licenses | Sort-Object { [int]$_.TotalLicenses } -Descending)) { $LicName = $License.License @@ -30,7 +30,7 @@ function Invoke-CippTestGenericTest001 { $Available = $Total - $Used $Util = if ($Total -gt 0) { [math]::Round(($Used / $Total) * 100, 0) } else { 0 } $UtilIcon = if ($Util -ge 90) { "🟢 $Util%" } elseif ($Util -ge 70) { "🟡 $Util%" } else { "🟢 $Util%" } - $Result += "| $LicName | $Used | $Total | $Available | $UtilIcon |`n" + $null = $Result.Append("| $LicName | $Used | $Total | $Available | $UtilIcon |`n") } diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest002.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest002.ps1 index ffeb3fab4d4a..77314747c732 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest002.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest002.ps1 @@ -37,28 +37,28 @@ function Invoke-CippTestGenericTest002 { } if ($UserLicenseMap.Count -eq 0) { - $Result = "No users with assigned licenses were found in the cached data.`n`nThis may indicate that the license data has not been synced recently, or no licenses have been assigned to individual users." + $Result = [System.Text.StringBuilder]::new("No users with assigned licenses were found in the cached data.`n`nThis may indicate that the license data has not been synced recently, or no licenses have been assigned to individual users.") Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest002' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'User License Overview' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' return } - $Result = "**Total Licensed Users:** $($UserLicenseMap.Count)`n`n" + $Result = [System.Text.StringBuilder]::new("**Total Licensed Users:** $($UserLicenseMap.Count)`n`n") - $Result += "| User | Licenses |`n" - $Result += "|------|----------|`n" + $null = $Result.Append("| User | Licenses |`n") + $null = $Result.Append("|------|----------|`n") $SortedUsers = $UserLicenseMap.GetEnumerator() | Sort-Object { $_.Value.DisplayName } $DisplayCount = 0 foreach ($Entry in $SortedUsers) { $DisplayName = $Entry.Value.DisplayName $LicList = ($Entry.Value.Licenses | Sort-Object) -join ', ' - $Result += "| $DisplayName | $LicList |`n" + $null = $Result.Append("| $DisplayName | $LicList |`n") $DisplayCount++ if ($DisplayCount -ge 100) { break } } if ($UserLicenseMap.Count -gt 100) { - $Result += "`n*Showing 100 of $($UserLicenseMap.Count) licensed users.*`n" + $null = $Result.Append("`n*Showing 100 of $($UserLicenseMap.Count) licensed users.*`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest002' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'User License Overview' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest003.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest003.ps1 index e6dfc31ec0ce..bc6de5e0d904 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest003.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest003.ps1 @@ -15,7 +15,7 @@ function Invoke-CippTestGenericTest003 { $Licenses = @($LicenseData) - $Result = "" + $Result = [System.Text.StringBuilder]::new() $HasRenewals = $false $TrialCount = 0 @@ -44,24 +44,24 @@ function Invoke-CippTestGenericTest003 { } if (-not $HasRenewals) { - $Result += 'No subscription renewal information is available. This may indicate non-standard licensing or the data has not been synced recently.' + $null = $Result.Append('No subscription renewal information is available. This may indicate non-standard licensing or the data has not been synced recently.') Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest003' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'License Renewal Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' return } if ($TrialCount -gt 0) { - $Result += "**⚠️ $TrialCount trial subscription(s) detected.** Trial licenses will expire and may cause users to lose access if not converted to paid subscriptions.`n`n" + $null = $Result.Append("**⚠️ $TrialCount trial subscription(s) detected.** Trial licenses will expire and may cause users to lose access if not converted to paid subscriptions.`n`n") } $UrgentRenewals = @($UpcomingRenewals | Where-Object { $_.DaysUntilRenew -le 30 -and $_.DaysUntilRenew -ge 0 }) if ($UrgentRenewals.Count -gt 0) { - $Result += "**🔴 $($UrgentRenewals.Count) subscription(s) renewing within 30 days** — review these to ensure billing and seat counts are correct.`n`n" + $null = $Result.Append("**🔴 $($UrgentRenewals.Count) subscription(s) renewing within 30 days** — review these to ensure billing and seat counts are correct.`n`n") } $Sorted = $UpcomingRenewals | Sort-Object DaysUntilRenew - $Result += "| License | Status | Billing Term | Seats | Renews In | Renewal Date | Trial |`n" - $Result += "|---------|--------|--------------|-------|-----------|--------------|-------|`n" + $null = $Result.Append("| License | Status | Billing Term | Seats | Renews In | Renewal Date | Trial |`n") + $null = $Result.Append("|---------|--------|--------------|-------|-----------|--------------|-------|`n") foreach ($Renewal in $Sorted) { $DaysLabel = if ($null -eq $Renewal.DaysUntilRenew) { 'Unknown' } @@ -76,7 +76,7 @@ function Invoke-CippTestGenericTest003 { 'Deleted' { "❌ $($Renewal.Status)" } default { $Renewal.Status } } - $Result += "| $($Renewal.License) | $StatusIcon | $($Renewal.Term) | $($Renewal.Seats) | $DaysLabel | $($Renewal.NextRenewal) | $TrialLabel |`n" + $null = $Result.Append("| $($Renewal.License) | $StatusIcon | $($Renewal.Term) | $($Renewal.Seats) | $DaysLabel | $($Renewal.NextRenewal) | $TrialLabel |`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest003' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'License Renewal Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 index 47bf6671a71d..4d6b19eaeee2 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 @@ -29,25 +29,25 @@ function Invoke-CippTestGenericTest004 { $AdminCount = @($Users | Where-Object { $_.IsAdmin -eq $true }).Count $MFARegPct = if ($TotalUsers -gt 0) { [math]::Round(($MFARegistered / $TotalUsers) * 100, 1) } else { 0 } - $Result = "### Summary`n`n" - $Result += "| Metric | Count |`n" - $Result += "|--------|-------|`n" - $Result += "| Total Accounts | $TotalUsers |`n" - $Result += "| Admin Accounts | $AdminCount |`n" - $Result += "| Registered for MFA | $MFARegistered ($MFARegPct%) |`n" - $Result += "| MFA Capable | $MFACapable |`n" - $Result += "| Protected by Conditional Access | $CoveredByCA |`n" - $Result += "| Protected by Security Defaults | $CoveredBySD |`n" - $Result += "| Using Per-User MFA (Legacy) | $PerUserMFA |`n" - $Result += "| **Not Protected by Any MFA Policy** | **$NotProtected** |`n`n" + $Result = [System.Text.StringBuilder]::new("### Summary`n`n") + $null = $Result.Append("| Metric | Count |`n") + $null = $Result.Append("|--------|-------|`n") + $null = $Result.Append("| Total Accounts | $TotalUsers |`n") + $null = $Result.Append("| Admin Accounts | $AdminCount |`n") + $null = $Result.Append("| Registered for MFA | $MFARegistered ($MFARegPct%) |`n") + $null = $Result.Append("| MFA Capable | $MFACapable |`n") + $null = $Result.Append("| Protected by Conditional Access | $CoveredByCA |`n") + $null = $Result.Append("| Protected by Security Defaults | $CoveredBySD |`n") + $null = $Result.Append("| Using Per-User MFA (Legacy) | $PerUserMFA |`n") + $null = $Result.Append("| **Not Protected by Any MFA Policy** | **$NotProtected** |`n`n") if ($NotProtected -gt 0) { - $Result += "**⚠️ $NotProtected account(s) have no MFA enforcement.** These accounts are at significantly higher risk of compromise. Consider enabling Conditional Access policies to require MFA for all users.`n`n" + $null = $Result.Append("**⚠️ $NotProtected account(s) have no MFA enforcement.** These accounts are at significantly higher risk of compromise. Consider enabling Conditional Access policies to require MFA for all users.`n`n") } - $Result += "### All Accounts`n`n" - $Result += "| Display Name | MFA Registered | MFA Method | Protected By | Account Type |`n" - $Result += "|-------------|----------------|------------|--------------|--------------|`n" + $null = $Result.Append("### All Accounts`n`n") + $null = $Result.Append("| Display Name | MFA Registered | MFA Method | Protected By | Account Type |`n") + $null = $Result.Append("|-------------|----------------|------------|--------------|--------------|`n") $DisplayUsers = $Users | Sort-Object DisplayName | Select-Object -First 100 foreach ($User in $DisplayUsers) { @@ -64,11 +64,11 @@ function Invoke-CippTestGenericTest004 { elseif ($User.PerUser -in @('Enforced', 'Enabled')) { "Per-User MFA ($($User.PerUser))" } else { '❌ None' } $AcctType = if ($User.IsAdmin -eq $true) { '🔑 Admin' } else { 'User' } - $Result += "| $Name | $Registered | $Methods | $Protection | $AcctType |`n" + $null = $Result.Append("| $Name | $Registered | $Methods | $Protection | $AcctType |`n") } if ($Users.Count -gt 100) { - $Result += "`n*Showing 100 of $($Users.Count) accounts.*`n" + $null = $Result.Append("`n*Showing 100 of $($Users.Count) accounts.*`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest004' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Tenant MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 index 9e4e6943ba34..6f25700f1eba 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 @@ -16,7 +16,7 @@ function Invoke-CippTestGenericTest005 { $Admins = @($MFAData | Where-Object { $_.UPN -and $_.IsAdmin -eq $true }) if ($Admins.Count -eq 0) { - $Result = "No administrator accounts were found in the MFA state data. This is unusual and may indicate the data needs to be re-synced." + $Result = [System.Text.StringBuilder]::new("No administrator accounts were found in the MFA state data. This is unusual and may indicate the data needs to be re-synced.") Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest005' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Admin MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' return } @@ -26,20 +26,20 @@ function Invoke-CippTestGenericTest005 { $NotProtected = @($Admins | Where-Object { $_.CoveredByCA -notlike 'Enforced*' -and $_.CoveredBySD -ne $true -and $_.PerUser -notin @('Enforced', 'Enabled') }).Count $MFARegPct = if ($TotalAdmins -gt 0) { [math]::Round(($MFARegistered / $TotalAdmins) * 100, 1) } else { 0 } - $Result = "**Total Admins:** $TotalAdmins | **MFA Registered:** $MFARegistered ($MFARegPct%)" + $Result = [System.Text.StringBuilder]::new("**Total Admins:** $TotalAdmins | **MFA Registered:** $MFARegistered ($MFARegPct%)") if ($NotProtected -gt 0) { - $Result += " | **⚠️ Unprotected: $NotProtected**" + $null = $Result.Append(" | **⚠️ Unprotected: $NotProtected**") } - $Result += "`n`n" + $null = $Result.Append("`n`n") if ($NotProtected -gt 0) { - $Result += "**🔴 Critical: $NotProtected admin account(s) have no MFA enforcement.** Admin accounts without MFA are the #1 target for attackers. This should be addressed immediately.`n`n" + $null = $Result.Append("**🔴 Critical: $NotProtected admin account(s) have no MFA enforcement.** Admin accounts without MFA are the #1 target for attackers. This should be addressed immediately.`n`n") } elseif ($MFARegistered -eq $TotalAdmins) { - $Result += "**✅ All admin accounts have MFA registered and enforced.** Great job keeping your most privileged accounts secured.`n`n" + $null = $Result.Append("**✅ All admin accounts have MFA registered and enforced.** Great job keeping your most privileged accounts secured.`n`n") } - $Result += "| Display Name | MFA Registered | MFA Method | Protected By | Account Enabled |`n" - $Result += "|-------------|----------------|------------|--------------|-----------------|`n" + $null = $Result.Append("| Display Name | MFA Registered | MFA Method | Protected By | Account Enabled |`n") + $null = $Result.Append("|-------------|----------------|------------|--------------|-----------------|`n") foreach ($Admin in ($Admins | Sort-Object DisplayName)) { $Name = $Admin.DisplayName @@ -55,7 +55,7 @@ function Invoke-CippTestGenericTest005 { elseif ($Admin.PerUser -in @('Enforced', 'Enabled')) { "Per-User MFA ($($Admin.PerUser))" } else { '❌ None' } $Enabled = if ($Admin.AccountEnabled -eq $true) { 'Yes' } else { 'Disabled' } - $Result += "| $Name | $Registered | $Methods | $Protection | $Enabled |`n" + $null = $Result.Append("| $Name | $Registered | $Methods | $Protection | $Enabled |`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest005' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Admin MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 index 5c07c9c19a69..dee9fb7994df 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 @@ -16,7 +16,7 @@ function Invoke-CippTestGenericTest006 { $StandardUsers = @($MFAData | Where-Object { $_.UPN -and $_.IsAdmin -ne $true }) if ($StandardUsers.Count -eq 0) { - $Result = "No standard (non-admin) user accounts were found in the MFA state data." + $Result = [System.Text.StringBuilder]::new("No standard (non-admin) user accounts were found in the MFA state data.") Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest006' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'User MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' return } @@ -26,18 +26,18 @@ function Invoke-CippTestGenericTest006 { $NotProtected = @($StandardUsers | Where-Object { $_.CoveredByCA -notlike 'Enforced*' -and $_.CoveredBySD -ne $true -and $_.PerUser -notin @('Enforced', 'Enabled') }).Count $MFARegPct = if ($TotalUsers -gt 0) { [math]::Round(($MFARegistered / $TotalUsers) * 100, 1) } else { 0 } - $Result = "**Total Users:** $TotalUsers | **MFA Registered:** $MFARegistered ($MFARegPct%)" + $Result = [System.Text.StringBuilder]::new("**Total Users:** $TotalUsers | **MFA Registered:** $MFARegistered ($MFARegPct%)") if ($NotProtected -gt 0) { - $Result += " | **Unprotected: $NotProtected**" + $null = $Result.Append(" | **Unprotected: $NotProtected**") } - $Result += "`n`n" + $null = $Result.Append("`n`n") if ($NotProtected -gt 0) { - $Result += "**⚠️ $NotProtected user account(s) have no MFA enforcement.** Consider enabling a Conditional Access policy that requires MFA for all users.`n`n" + $null = $Result.Append("**⚠️ $NotProtected user account(s) have no MFA enforcement.** Consider enabling a Conditional Access policy that requires MFA for all users.`n`n") } - $Result += "| Display Name | MFA Registered | MFA Method | Protected By | User Type |`n" - $Result += "|-------------|----------------|------------|--------------|-----------|`n" + $null = $Result.Append("| Display Name | MFA Registered | MFA Method | Protected By | User Type |`n") + $null = $Result.Append("|-------------|----------------|------------|--------------|-----------|`n") $DisplayUsers = $StandardUsers | Sort-Object DisplayName | Select-Object -First 100 foreach ($User in $DisplayUsers) { @@ -54,11 +54,11 @@ function Invoke-CippTestGenericTest006 { elseif ($User.PerUser -in @('Enforced', 'Enabled')) { "Per-User MFA ($($User.PerUser))" } else { '❌ None' } $UserType = if ($User.UserType -eq 'Guest') { 'Guest' } else { 'Member' } - $Result += "| $Name | $Registered | $Methods | $Protection | $UserType |`n" + $null = $Result.Append("| $Name | $Registered | $Methods | $Protection | $UserType |`n") } if ($StandardUsers.Count -gt 100) { - $Result += "`n*Showing 100 of $($StandardUsers.Count) user accounts.*`n" + $null = $Result.Append("`n*Showing 100 of $($StandardUsers.Count) user accounts.*`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest006' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'User MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 index 15df3d07aa71..3d4d71b98fd3 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 @@ -16,7 +16,7 @@ function Invoke-CippTestGenericTest007 { $LicensedUsers = @($MFAData | Where-Object { $_.UPN -and $_.isLicensed -eq $true }) if ($LicensedUsers.Count -eq 0) { - $Result = "No licensed user accounts were found in the MFA state data. This may indicate no licenses have been assigned or the data needs to be re-synced." + $Result = [System.Text.StringBuilder]::new("No licensed user accounts were found in the MFA state data. This may indicate no licenses have been assigned or the data needs to be re-synced.") Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest007' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Licensed User MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' return } @@ -27,18 +27,18 @@ function Invoke-CippTestGenericTest007 { $Admins = @($LicensedUsers | Where-Object { $_.IsAdmin -eq $true }).Count $MFARegPct = if ($TotalUsers -gt 0) { [math]::Round(($MFARegistered / $TotalUsers) * 100, 1) } else { 0 } - $Result = "**Licensed Users:** $TotalUsers | **Admins among them:** $Admins | **MFA Registered:** $MFARegistered ($MFARegPct%)" + $Result = [System.Text.StringBuilder]::new("**Licensed Users:** $TotalUsers | **Admins among them:** $Admins | **MFA Registered:** $MFARegistered ($MFARegPct%)") if ($NotProtected -gt 0) { - $Result += " | **Unprotected: $NotProtected**" + $null = $Result.Append(" | **Unprotected: $NotProtected**") } - $Result += "`n`n" + $null = $Result.Append("`n`n") if ($NotProtected -gt 0) { - $Result += "**⚠️ $NotProtected licensed user(s) have no MFA enforcement.** These accounts have access to company data and email but are not protected by any MFA policy.`n`n" + $null = $Result.Append("**⚠️ $NotProtected licensed user(s) have no MFA enforcement.** These accounts have access to company data and email but are not protected by any MFA policy.`n`n") } - $Result += "| Display Name | Role | MFA Registered | MFA Method | Protected By |`n" - $Result += "|-------------|------|----------------|------------|--------------|`n" + $null = $Result.Append("| Display Name | Role | MFA Registered | MFA Method | Protected By |`n") + $null = $Result.Append("|-------------|------|----------------|------------|--------------|`n") $DisplayUsers = $LicensedUsers | Sort-Object DisplayName | Select-Object -First 100 foreach ($User in $DisplayUsers) { @@ -55,11 +55,11 @@ function Invoke-CippTestGenericTest007 { elseif ($User.CoveredBySD -eq $true) { 'Security Defaults' } elseif ($User.PerUser -in @('Enforced', 'Enabled')) { "Per-User MFA ($($User.PerUser))" } else { '❌ None' } - $Result += "| $Name | $Role | $Registered | $Methods | $Protection |`n" + $null = $Result.Append("| $Name | $Role | $Registered | $Methods | $Protection |`n") } if ($LicensedUsers.Count -gt 100) { - $Result += "`n*Showing 100 of $($LicensedUsers.Count) licensed user accounts.*`n" + $null = $Result.Append("`n*Showing 100 of $($LicensedUsers.Count) licensed user accounts.*`n") } Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest007' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Licensed User MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 index c4f401f8fc5f..f082405ac151 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 @@ -16,11 +16,11 @@ function Invoke-CippTestGenericTest008 { $AllUsers = @($MFAData | Where-Object { $_.UPN }) $PerUserMFAUsers = @($AllUsers | Where-Object { $_.PerUser -in @('Enforced', 'Enabled') }) - $Result = "" + $Result = [System.Text.StringBuilder]::new() if ($PerUserMFAUsers.Count -eq 0) { - $Result += "**✅ No accounts are using legacy Per-User MFA.** Your tenant is not relying on the deprecated per-user MFA enforcement method.`n`n" - $Result += "Make sure your accounts are protected by Conditional Access policies or Security Defaults instead." + $null = $Result.Append("**✅ No accounts are using legacy Per-User MFA.** Your tenant is not relying on the deprecated per-user MFA enforcement method.`n`n") + $null = $Result.Append("Make sure your accounts are protected by Conditional Access policies or Security Defaults instead.") Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest008' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Legacy Per-User MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' return } @@ -29,21 +29,21 @@ function Invoke-CippTestGenericTest008 { $EnabledCount = @($PerUserMFAUsers | Where-Object { $_.PerUser -eq 'Enabled' }).Count $AdminsAffected = @($PerUserMFAUsers | Where-Object { $_.IsAdmin -eq $true }).Count - $Result += "### Current Status`n`n" - $Result += "**⚠️ $($PerUserMFAUsers.Count) account(s) are still using legacy Per-User MFA.**`n`n" - $Result += "| Status | Count |`n" - $Result += "|--------|-------|`n" - $Result += "| Per-User MFA Enforced | $EnforcedCount |`n" - $Result += "| Per-User MFA Enabled | $EnabledCount |`n" + $null = $Result.Append("### Current Status`n`n") + $null = $Result.Append("**⚠️ $($PerUserMFAUsers.Count) account(s) are still using legacy Per-User MFA.**`n`n") + $null = $Result.Append("| Status | Count |`n") + $null = $Result.Append("|--------|-------|`n") + $null = $Result.Append("| Per-User MFA Enforced | $EnforcedCount |`n") + $null = $Result.Append("| Per-User MFA Enabled | $EnabledCount |`n") if ($AdminsAffected -gt 0) { - $Result += "| Admin Accounts Affected | $AdminsAffected |`n" + $null = $Result.Append("| Admin Accounts Affected | $AdminsAffected |`n") } - $Result += "`n" + $null = $Result.Append("`n") - $Result += "### Accounts Using Per-User MFA`n`n" - $Result += "The following accounts should be migrated to Conditional Access policies:`n`n" - $Result += "| Display Name | Per-User MFA Status | Also Covered by CA | Account Type | Licensed |`n" - $Result += "|-------------|--------------------|--------------------|--------------|----------|`n" + $null = $Result.Append("### Accounts Using Per-User MFA`n`n") + $null = $Result.Append("The following accounts should be migrated to Conditional Access policies:`n`n") + $null = $Result.Append("| Display Name | Per-User MFA Status | Also Covered by CA | Account Type | Licensed |`n") + $null = $Result.Append("|-------------|--------------------|--------------------|--------------|----------|`n") foreach ($User in ($PerUserMFAUsers | Sort-Object DisplayName)) { $Name = $User.DisplayName @@ -51,14 +51,14 @@ function Invoke-CippTestGenericTest008 { $CAProtected = if ($User.CoveredByCA -like 'Enforced*') { '✅ Yes' } else { '❌ No' } $AcctType = if ($User.IsAdmin -eq $true) { '🔑 Admin' } else { 'User' } $Licensed = if ($User.isLicensed -eq $true) { 'Yes' } else { 'No' } - $Result += "| $Name | $PerUserStatus | $CAProtected | $AcctType | $Licensed |`n" + $null = $Result.Append("| $Name | $PerUserStatus | $CAProtected | $AcctType | $Licensed |`n") } - $Result += "`n### Recommended Migration Steps`n`n" - $Result += "1. **Create a Conditional Access policy** that requires MFA for all users (or start with admins)`n" - $Result += "2. **Verify** the Conditional Access policy is working correctly for affected users`n" - $Result += "3. **Disable Per-User MFA** for each account listed above once confirmed`n" - $Result += "4. **Test sign-in** to confirm users can still authenticate properly`n" + $null = $Result.Append("`n### Recommended Migration Steps`n`n") + $null = $Result.Append("1. **Create a Conditional Access policy** that requires MFA for all users (or start with admins)`n") + $null = $Result.Append("2. **Verify** the Conditional Access policy is working correctly for affected users`n") + $null = $Result.Append("3. **Disable Per-User MFA** for each account listed above once confirmed`n") + $null = $Result.Append("4. **Test sign-in** to confirm users can still authenticate properly`n") Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest008' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Legacy Per-User MFA Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest009.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest009.ps1 index f59f5be507c5..fc7054d88b82 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest009.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest009.ps1 @@ -32,31 +32,31 @@ function Invoke-CippTestGenericTest009 { $ScoreChange = [math]::Round($CurrentScore - $OldestScore, 1) $TrendIcon = if ($ScoreChange -gt 0) { "📈 +$ScoreChange" } elseif ($ScoreChange -lt 0) { "📉 $ScoreChange" } else { '➡️ No change' } - $Result = "### Current Score`n`n" - $Result += "| Metric | Value |`n" - $Result += "|--------|-------|`n" - $Result += "| Current Score | **$CurrentScore** out of $MaxScore ($ScorePct%) |`n" - $Result += "| 14-Day Trend | $TrendIcon |`n" - $Result += "| Data Points | $($SortedScores.Count) days |`n`n" + $Result = [System.Text.StringBuilder]::new("### Current Score`n`n") + $null = $Result.Append("| Metric | Value |`n") + $null = $Result.Append("|--------|-------|`n") + $null = $Result.Append("| Current Score | **$CurrentScore** out of $MaxScore ($ScorePct%) |`n") + $null = $Result.Append("| 14-Day Trend | $TrendIcon |`n") + $null = $Result.Append("| Data Points | $($SortedScores.Count) days |`n`n") if ($ScorePct -ge 80) { - $Result += "**✅ Strong security posture.** Your score is in the top tier. Keep monitoring to maintain this level.`n`n" + $null = $Result.Append("**✅ Strong security posture.** Your score is in the top tier. Keep monitoring to maintain this level.`n`n") } elseif ($ScorePct -ge 50) { - $Result += "**🟡 Moderate security posture.** There's room for improvement. Review the recommended actions in your Microsoft 365 Security portal.`n`n" + $null = $Result.Append("**🟡 Moderate security posture.** There's room for improvement. Review the recommended actions in your Microsoft 365 Security portal.`n`n") } else { - $Result += "**🔴 Low security posture.** Significant improvements are recommended. Focus on the high-impact actions first.`n`n" + $null = $Result.Append("**🔴 Low security posture.** Significant improvements are recommended. Focus on the high-impact actions first.`n`n") } - $Result += "### 14-Day Score Trend`n`n" - $Result += "| Date | Score | Max Score | Percentage |`n" - $Result += "|------|-------|-----------|------------|`n" + $null = $Result.Append("### 14-Day Score Trend`n`n") + $null = $Result.Append("| Date | Score | Max Score | Percentage |`n") + $null = $Result.Append("|------|-------|-----------|------------|`n") foreach ($Score in $SortedScores) { $DateStr = if ($Score.createdDateTime) { ([datetime]$Score.createdDateTime).ToString('yyyy-MM-dd') } else { 'Unknown' } $DayScore = [math]::Round([double]$Score.currentScore, 1) $DayMax = [math]::Round([double]$Score.maxScore, 1) $DayPct = if ($DayMax -gt 0) { [math]::Round(($DayScore / $DayMax) * 100, 1) } else { 0 } - $Result += "| $DateStr | $DayScore | $DayMax | $DayPct% |`n" + $null = $Result.Append("| $DateStr | $DayScore | $DayMax | $DayPct% |`n") } # Show top improvable controls if available @@ -79,12 +79,12 @@ function Invoke-CippTestGenericTest009 { } | Where-Object { $_.Gap -gt 0 } | Sort-Object Gap -Descending | Select-Object -First 10) if ($ImprovableControls.Count -gt 0) { - $Result += "`n### Top Improvement Opportunities`n`n" - $Result += "| Control | Current | Max | Points Available |`n" - $Result += "|---------|---------|-----|-----------------|`n" + $null = $Result.Append("`n### Top Improvement Opportunities`n`n") + $null = $Result.Append("| Control | Current | Max | Points Available |`n") + $null = $Result.Append("|---------|---------|-----|-----------------|`n") foreach ($Control in $ImprovableControls) { - $Result += "| $($Control.Name) | $($Control.Score) | $($Control.MaxScore) | +$($Control.Gap) |`n" + $null = $Result.Append("| $($Control.Name) | $($Control.Score) | $($Control.MaxScore) | +$($Control.Gap) |`n") } } } diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest010.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest010.ps1 index 0fb523f95270..4191ac46f5f9 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest010.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest010.ps1 @@ -17,7 +17,7 @@ function Invoke-CippTestGenericTest010 { $CapabilityProperties = $Capabilities.PSObject.Properties | Where-Object { $_.Value -eq $true } if (-not $CapabilityProperties -or $CapabilityProperties.Count -eq 0) { - $Result = "No active service plans were found for this tenant. This is unusual and may indicate a licensing issue." + $Result = [System.Text.StringBuilder]::new("No active service plans were found for this tenant. This is unusual and may indicate a licensing issue.") Add-CippTestResult -TenantFilter $Tenant -TestId 'GenericTest010' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Tenant Capabilities Report' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Tenant Overview' return } @@ -33,7 +33,7 @@ function Invoke-CippTestGenericTest010 { } } - $Result = "**Total Active Capabilities:** $($CapabilityProperties.Count)`n`n" + $Result = [System.Text.StringBuilder]::new("**Total Active Capabilities:** $($CapabilityProperties.Count)`n`n") # Categorize capabilities into logical groups $Categories = [ordered]@{ @@ -54,9 +54,9 @@ function Invoke-CippTestGenericTest010 { $CategoryPlans = @($AllPlanNames | Where-Object { $_ -in $Categories[$CategoryName] }) if ($CategoryPlans.Count -eq 0) { continue } - $Result += "### $CategoryName`n`n" - $Result += "| Capability | Service Plan |`n" - $Result += "|------------|-------------|`n" + $null = $Result.Append("### $CategoryName`n`n") + $null = $Result.Append("| Capability | Service Plan |`n") + $null = $Result.Append("|------------|-------------|`n") foreach ($Plan in ($CategoryPlans | Sort-Object)) { $FriendlyName = if ($FriendlyNameMap.ContainsKey($Plan)) { @@ -65,17 +65,17 @@ function Invoke-CippTestGenericTest010 { # Convert raw plan name to readable format $Plan -replace '_', ' ' -replace '([a-z])([A-Z])', '$1 $2' -replace ' S ', ' ' -replace ' O365 ', ' ' -replace ' P\d$', '' -replace ' ENTERPRISE', '' -replace ' PREMIUM', ' Premium' -replace ' STANDARD', '' } - $Result += "| $FriendlyName | $Plan |`n" + $null = $Result.Append("| $FriendlyName | $Plan |`n") } - $Result += "`n" + $null = $Result.Append("`n") } # Any uncategorized plans $Uncategorized = @($AllPlanNames | Where-Object { $_ -notin $CategorizedPlanNames }) if ($Uncategorized.Count -gt 0) { - $Result += "### Other Capabilities`n`n" - $Result += "| Capability | Service Plan |`n" - $Result += "|------------|-------------|`n" + $null = $Result.Append("### Other Capabilities`n`n") + $null = $Result.Append("| Capability | Service Plan |`n") + $null = $Result.Append("|------------|-------------|`n") foreach ($Plan in ($Uncategorized | Sort-Object)) { $FriendlyName = if ($FriendlyNameMap.ContainsKey($Plan)) { @@ -83,7 +83,7 @@ function Invoke-CippTestGenericTest010 { } else { $Plan -replace '_', ' ' -replace '([a-z])([A-Z])', '$1 $2' -replace ' S ', ' ' -replace ' O365 ', ' ' -replace ' P\d$', '' -replace ' ENTERPRISE', '' -replace ' PREMIUM', ' Premium' -replace ' STANDARD', '' } - $Result += "| $FriendlyName | $Plan |`n" + $null = $Result.Append("| $FriendlyName | $Plan |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 index f8b41220a0e9..ac3e9105eea4 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 @@ -51,7 +51,7 @@ function Invoke-CippTestGenericTest011 { } catch { $AllCATemplates = @() } $AlignmentItems = @($AlignmentData) - $Result = '' + $Result = [System.Text.StringBuilder]::new() # Helper: resolve a standard name to a friendly display name # Mirrors the resolution chain from Get-CIPPDrift.ps1 and the frontend drift.js @@ -139,19 +139,19 @@ function Invoke-CippTestGenericTest011 { $ScoreIcon = if ($Score -ge 80) { '✅' } elseif ($Score -ge 50) { '🟡' } else { '🔴' } - $Result += "### $TemplateName`n`n" - $Result += "**Alignment Score:** $ScoreIcon $Score% | **Compliant:** $Compliant / $Total" - if ($LicenseMissing -gt 0) { $Result += " | **License Missing:** $LicenseMissing" } - if ($ReportingDisabled -gt 0) { $Result += " | **Reporting Disabled:** $ReportingDisabled" } + $null = $Result.Append("### $TemplateName`n`n") + $null = $Result.Append("**Alignment Score:** $ScoreIcon $Score% | **Compliant:** $Compliant / $Total") + if ($LicenseMissing -gt 0) { $null = $Result.Append(" | **License Missing:** $LicenseMissing") } + if ($ReportingDisabled -gt 0) { $null = $Result.Append(" | **Reporting Disabled:** $ReportingDisabled") } if ($Template.LatestDataCollection) { $CollectionDate = ([datetime]$Template.LatestDataCollection).ToString('yyyy-MM-dd HH:mm') - $Result += " | **Last Checked:** $CollectionDate" + $null = $Result.Append(" | **Last Checked:** $CollectionDate") } - $Result += "`n`n" + $null = $Result.Append("`n`n") $Details = $Template.ComparisonDetails if (-not $Details) { - $Result += "No comparison details available for this template.`n`n" + $null = $Result.Append("No comparison details available for this template.`n`n") continue } @@ -166,53 +166,53 @@ function Invoke-CippTestGenericTest011 { # Compliant items if ($CompliantItems.Count -gt 0) { - $Result += "| Standard | Status |`n" - $Result += "|----------|--------|`n" + $null = $Result.Append("| Standard | Status |`n") + $null = $Result.Append("|----------|--------|`n") foreach ($Item in $CompliantItems) { $FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings if (-not $FriendlyName) { continue } - $Result += "| $FriendlyName | ✅ Compliant |`n" + $null = $Result.Append("| $FriendlyName | ✅ Compliant |`n") } - $Result += "`n" + $null = $Result.Append("`n") } # Non-compliant items if ($NonCompliantItems.Count -gt 0) { - $Result += "| Standard | Status |`n" - $Result += "|----------|--------|`n" + $null = $Result.Append("| Standard | Status |`n") + $null = $Result.Append("|----------|--------|`n") foreach ($Item in $NonCompliantItems) { $FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings if (-not $FriendlyName) { continue } - $Result += "| $FriendlyName | ❌ Non-Compliant |`n" + $null = $Result.Append("| $FriendlyName | ❌ Non-Compliant |`n") } - $Result += "`n" + $null = $Result.Append("`n") } # License missing items if ($LicenseMissingItems.Count -gt 0) { - $Result += "#### Standards Not Applied Due to Missing Licenses`n`n" - $Result += "These items are part of this baseline, but your environment does not meet the minimum required licenses for them to be applied.`n`n" - $Result += "| Standard | Status |`n" - $Result += "|----------|--------|`n" + $null = $Result.Append("#### Standards Not Applied Due to Missing Licenses`n`n") + $null = $Result.Append("These items are part of this baseline, but your environment does not meet the minimum required licenses for them to be applied.`n`n") + $null = $Result.Append("| Standard | Status |`n") + $null = $Result.Append("|----------|--------|`n") foreach ($Item in $LicenseMissingItems) { $FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings if (-not $FriendlyName) { continue } - $Result += "| $FriendlyName | ⚠️ License Missing |`n" + $null = $Result.Append("| $FriendlyName | ⚠️ License Missing |`n") } - $Result += "`n" + $null = $Result.Append("`n") } # Reporting disabled items if ($ReportingDisabledItems.Count -gt 0) { - $Result += "#### Standards With Reporting Disabled`n`n" - $Result += "| Standard | Status |`n" - $Result += "|----------|--------|`n" + $null = $Result.Append("#### Standards With Reporting Disabled`n`n") + $null = $Result.Append("| Standard | Status |`n") + $null = $Result.Append("|----------|--------|`n") foreach ($Item in $ReportingDisabledItems) { $FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings if (-not $FriendlyName) { continue } - $Result += "| $FriendlyName | ⏸️ Reporting Disabled |`n" + $null = $Result.Append("| $FriendlyName | ⏸️ Reporting Disabled |`n") } - $Result += "`n" + $null = $Result.Append("`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA100.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA100.ps1 index bc145b4e233e..9c62fa6ec28c 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA100.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA100.ps1 @@ -27,16 +27,16 @@ function Invoke-CippTestORCA100 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have appropriate Bulk Complaint Level (BCL) thresholds set between 4 and 6.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have appropriate Bulk Complaint Level (BCL) thresholds set between 4 and 6.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies have BCL thresholds outside the recommended range (4-6).`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Current BCL Threshold |`n" - $Result += "|------------|----------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies have BCL thresholds outside the recommended range (4-6).`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Current BCL Threshold |`n") + $null = $Result.Append("|------------|----------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.BulkThreshold) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.BulkThreshold) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA101.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA101.ps1 index cd51a41a228e..0e9d3ec3b7a5 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA101.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA101.ps1 @@ -26,23 +26,23 @@ function Invoke-CippTestORCA101 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies are configured to mark bulk mail as spam.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)`n`n" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies are configured to mark bulk mail as spam.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)`n`n") if ($PassedPolicies.Count -gt 0) { - $Result += "| Policy Name | Mark As Spam Bulk Mail |`n" - $Result += "|------------|------------------------|`n" + $null = $Result.Append("| Policy Name | Mark As Spam Bulk Mail |`n") + $null = $Result.Append("|------------|------------------------|`n") foreach ($Policy in $PassedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.MarkAsSpamBulkMail) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.MarkAsSpamBulkMail) |`n") } } } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies are not configured to mark bulk mail as spam.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Mark As Spam Bulk Mail |`n" - $Result += "|------------|------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies are not configured to mark bulk mail as spam.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Mark As Spam Bulk Mail |`n") + $null = $Result.Append("|------------|------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.MarkAsSpamBulkMail) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.MarkAsSpamBulkMail) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA102.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA102.ps1 index 8d91be619f29..5d4459f46995 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA102.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA102.ps1 @@ -46,21 +46,21 @@ function Invoke-CippTestORCA102 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have Advanced Spam Filter (ASF) options turned off.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have Advanced Spam Filter (ASF) options turned off.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies have Advanced Spam Filter (ASF) options enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enabled ASF Options |`n" - $Result += "|------------|---------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies have Advanced Spam Filter (ASF) options enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enabled ASF Options |`n") + $null = $Result.Append("|------------|---------------------|`n") foreach ($Policy in $FailedPolicies) { $EnabledOptions = [System.Collections.Generic.List[string]]::new() if ($Policy.IncreaseScoreWithImageLinks -eq 'On') { $EnabledOptions.Add('ImageLinks') | Out-Null } if ($Policy.IncreaseScoreWithNumericIps -eq 'On') { $EnabledOptions.Add('NumericIPs') | Out-Null } if ($Policy.MarkAsSpamEmptyMessages -eq 'On') { $EnabledOptions.Add('EmptyMessages') | Out-Null } if ($Policy.MarkAsSpamJavaScriptInHtml -eq 'On') { $EnabledOptions.Add('JavaScript') | Out-Null } - $Result += "| $($Policy.Identity) | $($EnabledOptions -join ', ') |`n" + $null = $Result.Append("| $($Policy.Identity) | $($EnabledOptions -join ', ') |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA103.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA103.ps1 index 636048dbf752..8ee002e8f83e 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA103.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA103.ps1 @@ -45,16 +45,16 @@ function Invoke-CippTestORCA103 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All outbound spam filter policies are configured correctly.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All outbound spam filter policies are configured correctly.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) outbound spam filter policies are not configured correctly.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Issues |`n" - $Result += "|------------|--------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) outbound spam filter policies are not configured correctly.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Issues |`n") + $null = $Result.Append("|------------|--------|`n") foreach ($Failed in $FailedPolicies) { - $Result += "| $($Failed.Policy.Identity) | $($Failed.Issues -join '
') |`n" + $null = $Result.Append("| $($Failed.Policy.Identity) | $($Failed.Issues -join '
') |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 index 53aabdf3dbc5..da2cd06bfc06 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 @@ -27,26 +27,26 @@ function Invoke-CippTestORCA104 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have High Confidence Phish action set to Quarantine.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)`n`n" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have High Confidence Phish action set to Quarantine.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)`n`n") if ($PassedPolicies.Count -gt 0) { - $Result += "| Policy Name | Action |`n" - $Result += "|------------|--------|`n" + $null = $Result.Append("| Policy Name | Action |`n") + $null = $Result.Append("|------------|--------|`n") foreach ($Policy in $PassedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.HighConfidencePhishAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.HighConfidencePhishAction) |`n") } } } else { $Status = 'Failed' - $Result = "Some anti-phishing policies do not have High Confidence Phish action set to Quarantine.`n`n" - $Result += "**Failed Policies:** $($FailedPolicies.Count) | **Passed Policies:** $($PassedPolicies.Count)`n`n" - $Result += "### Non-Compliant Policies`n`n" - $Result += "| Policy Name | Current Action | Recommended Action |`n" - $Result += "|------------|----------------|-------------------|`n" + $Result = [System.Text.StringBuilder]::new("Some anti-phishing policies do not have High Confidence Phish action set to Quarantine.`n`n") + $null = $Result.Append("**Failed Policies:** $($FailedPolicies.Count) | **Passed Policies:** $($PassedPolicies.Count)`n`n") + $null = $Result.Append("### Non-Compliant Policies`n`n") + $null = $Result.Append("| Policy Name | Current Action | Recommended Action |`n") + $null = $Result.Append("|------------|----------------|-------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.HighConfidencePhishAction) | Quarantine |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.HighConfidencePhishAction) | Quarantine |`n") } - $Result += "`n**Remediation:** Update the HighConfidencePhishAction to 'Quarantine' for enhanced security." + $null = $Result.Append("`n**Remediation:** Update the HighConfidencePhishAction to 'Quarantine' for enhanced security.") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA104' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'High' -Name 'High Confidence Phish action set to Quarantine message' -UserImpact 'High' -ImplementationEffort 'Low' -Category 'Anti-Phish' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA105.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA105.ps1 index 58c6a253b0f6..4a89546a6022 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA105.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA105.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA105 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Links policies have synchronous URL detonation enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All Safe Links policies have synchronous URL detonation enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) Safe Links policies do not have synchronous URL detonation enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Deliver Message After Scan |`n" - $Result += "|------------|---------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) Safe Links policies do not have synchronous URL detonation enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Deliver Message After Scan |`n") + $null = $Result.Append("|------------|---------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.DeliverMessageAfterScan) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.DeliverMessageAfterScan) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA106.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA106.ps1 index aca84b48492c..ae9d99104142 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA106.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA106.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA106 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have quarantine retention period set to 30 days.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have quarantine retention period set to 30 days.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have quarantine retention period set to 30 days.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Quarantine Retention Period |`n" - $Result += "|------------|----------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have quarantine retention period set to 30 days.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Quarantine Retention Period |`n") + $null = $Result.Append("|------------|----------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.QuarantineRetentionPeriod) days |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.QuarantineRetentionPeriod) days |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA107.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA107.ps1 index d7acf2e2f0b7..90655ffcf9c1 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA107.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA107.ps1 @@ -26,24 +26,24 @@ function Invoke-CippTestORCA107 { if ($FailedPolicies.Count -eq 0 -and $PassedPolicies.Count -gt 0) { $Status = 'Passed' - $Result = "All quarantine policies have end-user spam notifications enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)`n`n" - $Result += "| Policy Name | Notification Frequency (days) |`n" - $Result += "|------------|-------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("All quarantine policies have end-user spam notifications enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Notification Frequency (days) |`n") + $null = $Result.Append("|------------|-------------------------------|`n") foreach ($Policy in $PassedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EndUserSpamNotificationFrequency) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EndUserSpamNotificationFrequency) |`n") } } elseif ($PassedPolicies.Count -eq 0) { $Status = 'Failed' - $Result = "No quarantine policies have end-user spam notifications enabled.`n`n" + $Result = [System.Text.StringBuilder]::new("No quarantine policies have end-user spam notifications enabled.`n`n") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) quarantine policies do not have end-user spam notifications enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Notification Frequency |`n" - $Result += "|------------|----------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) quarantine policies do not have end-user spam notifications enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Notification Frequency |`n") + $null = $Result.Append("|------------|----------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | Disabled |`n" + $null = $Result.Append("| $($Policy.Identity) | Disabled |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108_1.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108_1.ps1 index 62abde9f0eb7..b447b27296cc 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108_1.ps1 @@ -30,16 +30,16 @@ function Invoke-CippTestORCA108_1 { if ($FailedDomains.Count -eq 0) { $Status = 'Passed' - $Result = "All custom domains have DKIM DNS records configured.`n`n" - $Result += "**Compliant Domains:** $($PassedDomains.Count)" + $Result = [System.Text.StringBuilder]::new("All custom domains have DKIM DNS records configured.`n`n") + $null = $Result.Append("**Compliant Domains:** $($PassedDomains.Count)") } else { $Status = 'Failed' - $Result = "$($FailedDomains.Count) custom domains do not have DKIM DNS records configured.`n`n" - $Result += "**Non-Compliant Domains:** $($FailedDomains.Count)`n`n" - $Result += "| Domain Name |`n" - $Result += "|------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedDomains.Count) custom domains do not have DKIM DNS records configured.`n`n") + $null = $Result.Append("**Non-Compliant Domains:** $($FailedDomains.Count)`n`n") + $null = $Result.Append("| Domain Name |`n") + $null = $Result.Append("|------------|`n") foreach ($Domain in $FailedDomains) { - $Result += "| $($Domain.DomainName) |`n" + $null = $Result.Append("| $($Domain.DomainName) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA109.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA109.ps1 index a5446cece14c..20aed3932e3d 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA109.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA109.ps1 @@ -29,18 +29,18 @@ function Invoke-CippTestORCA109 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "No anti-spam policies have sender allow lists configured.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("No anti-spam policies have sender allow lists configured.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies have sender allow lists configured.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Allowed Senders | Allowed Sender Domains |`n" - $Result += "|------------|----------------|----------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies have sender allow lists configured.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Allowed Senders | Allowed Sender Domains |`n") + $null = $Result.Append("|------------|----------------|----------------------|`n") foreach ($Policy in $FailedPolicies) { $SenderCount = if ($Policy.AllowedSenders) { $Policy.AllowedSenders.Count } else { 0 } $DomainCount = if ($Policy.AllowedSenderDomains) { $Policy.AllowedSenderDomains.Count } else { 0 } - $Result += "| $($Policy.Identity) | $SenderCount | $DomainCount |`n" + $null = $Result.Append("| $($Policy.Identity) | $SenderCount | $DomainCount |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA110.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA110.ps1 index d37e046d43d2..2f3ed57eeffc 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA110.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA110.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA110 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have internal sender notifications disabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have internal sender notifications disabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies have internal sender notifications enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Inline Safety Tips Enabled |`n" - $Result += "|------------|---------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies have internal sender notifications enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Inline Safety Tips Enabled |`n") + $null = $Result.Append("|------------|---------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.InlineSafetyTipsEnabled) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.InlineSafetyTipsEnabled) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA111.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA111.ps1 index c1fa95853c93..5f19c83b3827 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA111.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA111.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA111 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have unauthenticated sender tagging enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have unauthenticated sender tagging enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have unauthenticated sender tagging enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Unauthenticated Sender |`n" - $Result += "|------------|------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have unauthenticated sender tagging enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Unauthenticated Sender |`n") + $null = $Result.Append("|------------|------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableUnauthenticatedSender) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableUnauthenticatedSender) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA112.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA112.ps1 index b1312a225698..98b53e4e193b 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA112.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA112.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA112 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have anti-spoofing action set to Move to Junk Email folder.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have anti-spoofing action set to Move to Junk Email folder.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have anti-spoofing action set to Move to Junk Email folder.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Authentication Fail Action |`n" - $Result += "|------------|---------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have anti-spoofing action set to Move to Junk Email folder.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Authentication Fail Action |`n") + $null = $Result.Append("|------------|---------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.AuthenticationFailAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.AuthenticationFailAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 index 1f19a2c52a49..a6f5d30b681b 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 @@ -27,26 +27,26 @@ function Invoke-CippTestORCA113 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Links policies have click-through disabled (DoNotAllowClickThrough = true).`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)`n`n" + $Result = [System.Text.StringBuilder]::new("All Safe Links policies have click-through disabled (DoNotAllowClickThrough = true).`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)`n`n") if ($PassedPolicies.Count -gt 0) { - $Result += "| Policy Name | DoNotAllowClickThrough |`n" - $Result += "|------------|----------------------|`n" + $null = $Result.Append("| Policy Name | DoNotAllowClickThrough |`n") + $null = $Result.Append("|------------|----------------------|`n") foreach ($Policy in $PassedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.DoNotAllowClickThrough) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.DoNotAllowClickThrough) |`n") } } } else { $Status = 'Failed' - $Result = "Some Safe Links policies allow click-through, which reduces protection.`n`n" - $Result += "**Failed Policies:** $($FailedPolicies.Count) | **Passed Policies:** $($PassedPolicies.Count)`n`n" - $Result += "### Non-Compliant Policies`n`n" - $Result += "| Policy Name | DoNotAllowClickThrough | Recommended |`n" - $Result += "|------------|----------------------|-------------|`n" + $Result = [System.Text.StringBuilder]::new("Some Safe Links policies allow click-through, which reduces protection.`n`n") + $null = $Result.Append("**Failed Policies:** $($FailedPolicies.Count) | **Passed Policies:** $($PassedPolicies.Count)`n`n") + $null = $Result.Append("### Non-Compliant Policies`n`n") + $null = $Result.Append("| Policy Name | DoNotAllowClickThrough | Recommended |`n") + $null = $Result.Append("|------------|----------------------|-------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.DoNotAllowClickThrough) | true |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.DoNotAllowClickThrough) | true |`n") } - $Result += "`n**Remediation:** Disable click-through (set DoNotAllowClickThrough to true) to prevent users from bypassing Safe Links protection." + $null = $Result.Append("`n**Remediation:** Disable click-through (set DoNotAllowClickThrough to true) to prevent users from bypassing Safe Links protection.") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA113' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'High' -Name 'AllowClickThrough is disabled in Safe Links policies' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Safe Links' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA114.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA114.ps1 index 9fd8629c74d0..921ba1dad27b 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA114.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA114.ps1 @@ -28,17 +28,17 @@ $FailedPolicies = [System.Collections.Generic.List[object]]::new() if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "No anti-spam policies have IP allow lists configured.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("No anti-spam policies have IP allow lists configured.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies have IP allow lists configured.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | IP Allow List Count |`n" - $Result += "|------------|-------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies have IP allow lists configured.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | IP Allow List Count |`n") + $null = $Result.Append("|------------|-------------------|`n") foreach ($Policy in $FailedPolicies) { $IPCount = if ($Policy.IPAllowList) { $Policy.IPAllowList.Count } else { 0 } - $Result += "| $($Policy.Identity) | $IPCount |`n" + $null = $Result.Append("| $($Policy.Identity) | $IPCount |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA115.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA115.ps1 index 589876fb598e..520459434633 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA115.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA115.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA115 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have mailbox intelligence based impersonation protection enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have mailbox intelligence based impersonation protection enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have mailbox intelligence based impersonation protection enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Mailbox Intelligence Protection |`n" - $Result += "|------------|---------------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have mailbox intelligence based impersonation protection enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Mailbox Intelligence Protection |`n") + $null = $Result.Append("|------------|---------------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableMailboxIntelligenceProtection) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableMailboxIntelligenceProtection) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA116.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA116.ps1 index acea46f26a58..a34da227c484 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA116.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA116.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA116 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have mailbox intelligence impersonation protection action set to Move to Junk Email folder.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have mailbox intelligence impersonation protection action set to Move to Junk Email folder.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have mailbox intelligence impersonation protection action set to Move to Junk Email folder.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Mailbox Intelligence Protection Action |`n" - $Result += "|------------|---------------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have mailbox intelligence impersonation protection action set to Move to Junk Email folder.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Mailbox Intelligence Protection Action |`n") + $null = $Result.Append("|------------|---------------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.MailboxIntelligenceProtectionAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.MailboxIntelligenceProtectionAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_1.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_1.ps1 index 3d83b616463b..f4fa2d75c382 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_1.ps1 @@ -28,17 +28,17 @@ function Invoke-CippTestORCA118_1 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "No anti-spam policies have allowed sender domains configured.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("No anti-spam policies have allowed sender domains configured.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies have allowed sender domains configured.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Allowed Sender Domains Count |`n" - $Result += "|------------|------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies have allowed sender domains configured.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Allowed Sender Domains Count |`n") + $null = $Result.Append("|------------|------------------------------|`n") foreach ($Policy in $FailedPolicies) { $Count = if ($Policy.AllowedSenderDomains) { $Policy.AllowedSenderDomains.Count } else { 0 } - $Result += "| $($Policy.Identity) | $Count |`n" + $null = $Result.Append("| $($Policy.Identity) | $Count |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_2.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_2.ps1 index 64c079f40f9a..76b8db06094c 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_2.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_2.ps1 @@ -24,17 +24,17 @@ function Invoke-CippTestORCA118_2 { if ($FailedRules.Count -eq 0) { $Status = 'Passed' - $Result = "No transport rules allow list domains by setting SCL to -1.`n`n" - $Result += "**Total Transport Rules Checked:** $($TransportRules.Count)" + $Result = [System.Text.StringBuilder]::new("No transport rules allow list domains by setting SCL to -1.`n`n") + $null = $Result.Append("**Total Transport Rules Checked:** $($TransportRules.Count)") } else { $Status = 'Failed' - $Result = "$($FailedRules.Count) transport rules allow list domains by setting SCL to -1.`n`n" - $Result += "**Non-Compliant Rules:** $($FailedRules.Count)`n`n" - $Result += "| Rule Name | Sender Domains |`n" - $Result += "|-----------|---------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedRules.Count) transport rules allow list domains by setting SCL to -1.`n`n") + $null = $Result.Append("**Non-Compliant Rules:** $($FailedRules.Count)`n`n") + $null = $Result.Append("| Rule Name | Sender Domains |`n") + $null = $Result.Append("|-----------|---------------|`n") foreach ($Rule in $FailedRules) { $Domains = if ($Rule.SenderDomainIs) { ($Rule.SenderDomainIs -join ', ') } else { 'N/A' } - $Result += "| $($Rule.Name) | $Domains |`n" + $null = $Result.Append("| $($Rule.Name) | $Domains |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_3.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_3.ps1 index dba391a02923..ffdfdc713135 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_3.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_3.ps1 @@ -44,17 +44,17 @@ function Invoke-CippTestORCA118_3 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "No anti-spam policies have own domains in the allow list.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("No anti-spam policies have own domains in the allow list.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies have own domains in the allow list.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Own Domains in Allow List |`n" - $Result += "|------------|---------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies have own domains in the allow list.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Own Domains in Allow List |`n") + $null = $Result.Append("|------------|---------------------------|`n") foreach ($Policy in $FailedPolicies) { $OwnDomainsInList = $Policy.AllowedSenderDomains | Where-Object { $OwnDomains -contains $_ } - $Result += "| $($Policy.Identity) | $($OwnDomainsInList -join ', ') |`n" + $null = $Result.Append("| $($Policy.Identity) | $($OwnDomainsInList -join ', ') |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_4.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_4.ps1 index 08a49d21713b..d953d21670e5 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_4.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA118_4.ps1 @@ -41,17 +41,17 @@ function Invoke-CippTestORCA118_4 { if ($FailedRules.Count -eq 0) { $Status = 'Passed' - $Result = "No transport rules allow list own domains by setting SCL to -1.`n`n" - $Result += "**Total Transport Rules Checked:** $($TransportRules.Count)" + $Result = [System.Text.StringBuilder]::new("No transport rules allow list own domains by setting SCL to -1.`n`n") + $null = $Result.Append("**Total Transport Rules Checked:** $($TransportRules.Count)") } else { $Status = 'Failed' - $Result = "$($FailedRules.Count) transport rules allow list own domains by setting SCL to -1.`n`n" - $Result += "**Non-Compliant Rules:** $($FailedRules.Count)`n`n" - $Result += "| Rule Name | Own Domains in Rule |`n" - $Result += "|-----------|-------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedRules.Count) transport rules allow list own domains by setting SCL to -1.`n`n") + $null = $Result.Append("**Non-Compliant Rules:** $($FailedRules.Count)`n`n") + $null = $Result.Append("| Rule Name | Own Domains in Rule |`n") + $null = $Result.Append("|-----------|-------------------|`n") foreach ($Rule in $FailedRules) { $OwnDomainsInRule = $Rule.SenderDomainIs | Where-Object { $OwnDomains -contains $_ } - $Result += "| $($Rule.Name) | $($OwnDomainsInRule -join ', ') |`n" + $null = $Result.Append("| $($Rule.Name) | $($OwnDomainsInRule -join ', ') |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA119.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA119.ps1 index 15b1ce1804cf..872bc1744665 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA119.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA119.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA119 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have Similar Domains Safety Tips enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have Similar Domains Safety Tips enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have Similar Domains Safety Tips enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Similar Domains Safety Tips |`n" - $Result += "|------------|-----------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have Similar Domains Safety Tips enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Similar Domains Safety Tips |`n") + $null = $Result.Append("|------------|-----------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableSimilarDomainsSafetyTips) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableSimilarDomainsSafetyTips) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_malware.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_malware.ps1 index 2fabda7833fb..5c40f7398aa6 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_malware.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_malware.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA120_malware { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All malware filter policies have Zero Hour Autopurge enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All malware filter policies have Zero Hour Autopurge enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) malware filter policies do not have Zero Hour Autopurge enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | ZAP Enabled |`n" - $Result += "|------------|------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) malware filter policies do not have Zero Hour Autopurge enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | ZAP Enabled |`n") + $null = $Result.Append("|------------|------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.ZapEnabled) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.ZapEnabled) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_phish.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_phish.ps1 index 8e7a42135d36..82c1244f8ca4 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_phish.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_phish.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA120_phish { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have Zero Hour Autopurge for Phish enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have Zero Hour Autopurge for Phish enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have Zero Hour Autopurge for Phish enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Phish ZAP Enabled |`n" - $Result += "|------------|------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have Zero Hour Autopurge for Phish enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Phish ZAP Enabled |`n") + $null = $Result.Append("|------------|------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.PhishZapEnabled) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.PhishZapEnabled) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_spam.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_spam.ps1 index b09403c2fed7..3f19a6ddb540 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_spam.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA120_spam.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA120_spam { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have Zero Hour Autopurge for Spam enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have Zero Hour Autopurge for Spam enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have Zero Hour Autopurge for Spam enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Spam ZAP Enabled |`n" - $Result += "|------------|-----------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have Zero Hour Autopurge for Spam enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Spam ZAP Enabled |`n") + $null = $Result.Append("|------------|-----------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.SpamZapEnabled) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.SpamZapEnabled) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA121.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA121.ps1 index 1a0aa4ec7523..d18f29b16a7c 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA121.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA121.ps1 @@ -14,8 +14,8 @@ function Invoke-CippTestORCA121 { } $Status = 'Passed' - $Result = "Quarantine policies are configured to support Zero Hour Auto Purge.`n`n" - $Result += "**Total Policies:** $($Policies.Count)" + $Result = [System.Text.StringBuilder]::new("Quarantine policies are configured to support Zero Hour Auto Purge.`n`n") + $null = $Result.Append("**Total Policies:** $($Policies.Count)") Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA121' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Low' -Name 'Supported filter policy action used' -UserImpact 'Medium' -ImplementationEffort 'Low' -Category 'Quarantine' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA123.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA123.ps1 index 274e8c4cd810..f1690920bf62 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA123.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA123.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA123 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have Unusual Characters Safety Tips enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have Unusual Characters Safety Tips enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have Unusual Characters Safety Tips enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Unusual Characters Safety Tips |`n" - $Result += "|------------|---------------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have Unusual Characters Safety Tips enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Unusual Characters Safety Tips |`n") + $null = $Result.Append("|------------|---------------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableUnusualCharactersSafetyTips) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableUnusualCharactersSafetyTips) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA124.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA124.ps1 index 041ed8304074..a09a297734ba 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA124.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA124.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA124 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Attachments policies have unknown malware response set to Block.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All Safe Attachments policies have unknown malware response set to Block.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) Safe Attachments policies do not have unknown malware response set to Block.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Action |`n" - $Result += "|------------|--------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) Safe Attachments policies do not have unknown malware response set to Block.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Action |`n") + $null = $Result.Append("|------------|--------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.Action) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.Action) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA139.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA139.ps1 index ea31ca0bfc90..a2c7d81be2c5 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA139.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA139.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA139 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have Spam action set to move to Junk Email folder or Quarantine.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have Spam action set to move to Junk Email folder or Quarantine.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have Spam action set appropriately.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Spam Action |`n" - $Result += "|------------|------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have Spam action set appropriately.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Spam Action |`n") + $null = $Result.Append("|------------|------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.SpamAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.SpamAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA140.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA140.ps1 index af9d6bcfd607..9041c6fabbf8 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA140.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA140.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA140 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have High Confidence Spam action set to Quarantine.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have High Confidence Spam action set to Quarantine.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have High Confidence Spam action set to Quarantine.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | High Confidence Spam Action |`n" - $Result += "|------------|---------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have High Confidence Spam action set to Quarantine.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | High Confidence Spam Action |`n") + $null = $Result.Append("|------------|---------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.HighConfidenceSpamAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.HighConfidenceSpamAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA141.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA141.ps1 index d9698edb6515..f19ce8ece8d0 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA141.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA141.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA141 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have Bulk action set to Move to Junk Email folder.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have Bulk action set to Move to Junk Email folder.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have Bulk action set to Move to Junk Email folder.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Bulk Spam Action |`n" - $Result += "|------------|-----------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have Bulk action set to Move to Junk Email folder.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Bulk Spam Action |`n") + $null = $Result.Append("|------------|-----------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.BulkSpamAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.BulkSpamAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA142.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA142.ps1 index 2041970791e8..a85111e1578a 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA142.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA142.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA142 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have Phish action set to Quarantine.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have Phish action set to Quarantine.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have Phish action set to Quarantine.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Phish Spam Action |`n" - $Result += "|------------|------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have Phish action set to Quarantine.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Phish Spam Action |`n") + $null = $Result.Append("|------------|------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.PhishSpamAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.PhishSpamAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA143.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA143.ps1 index fc1ea3a3acbd..c19e61bc153b 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA143.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA143.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA143 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies have Safety Tips enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies have Safety Tips enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not have Safety Tips enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Inline Safety Tips Enabled |`n" - $Result += "|------------|---------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not have Safety Tips enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Inline Safety Tips Enabled |`n") + $null = $Result.Append("|------------|---------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.InlineSafetyTipsEnabled) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.InlineSafetyTipsEnabled) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA156.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA156.ps1 index 7cb949d8800e..54e346fb3116 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA156.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA156.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA156 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Links policies are tracking user clicks.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All Safe Links policies are tracking user clicks.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) Safe Links policies are not tracking user clicks.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Track Clicks |`n" - $Result += "|------------|-------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) Safe Links policies are not tracking user clicks.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Track Clicks |`n") + $null = $Result.Append("|------------|-------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.TrackClicks) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.TrackClicks) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA158.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA158.ps1 index 60765c5d2ea5..0bf527155b7a 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA158.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA158.ps1 @@ -17,12 +17,12 @@ function Invoke-CippTestORCA158 { if ($Policy.EnableATPForSPOTeamsODB -eq $true) { $Status = 'Passed' - $Result = "Safe Attachments is enabled for SharePoint, OneDrive, and Teams.`n`n" - $Result += "**EnableATPForSPOTeamsODB:** $($Policy.EnableATPForSPOTeamsODB)" + $Result = [System.Text.StringBuilder]::new("Safe Attachments is enabled for SharePoint, OneDrive, and Teams.`n`n") + $null = $Result.Append("**EnableATPForSPOTeamsODB:** $($Policy.EnableATPForSPOTeamsODB)") } else { $Status = 'Failed' - $Result = "Safe Attachments is NOT enabled for SharePoint, OneDrive, and Teams.`n`n" - $Result += "**EnableATPForSPOTeamsODB:** $($Policy.EnableATPForSPOTeamsODB)" + $Result = [System.Text.StringBuilder]::new("Safe Attachments is NOT enabled for SharePoint, OneDrive, and Teams.`n`n") + $null = $Result.Append("**EnableATPForSPOTeamsODB:** $($Policy.EnableATPForSPOTeamsODB)") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA158' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'High' -Name 'Safe Attachments enabled for SharePoint and Teams' -UserImpact 'High' -ImplementationEffort 'Low' -Category 'Safe Attachments' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA179.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA179.ps1 index faf0fab99079..c2ea2cf19ed6 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA179.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA179.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA179 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Links policies are enabled for internal senders.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All Safe Links policies are enabled for internal senders.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) Safe Links policies are not enabled for internal senders.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable For Internal Senders |`n" - $Result += "|------------|----------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) Safe Links policies are not enabled for internal senders.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable For Internal Senders |`n") + $null = $Result.Append("|------------|----------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableForInternalSenders) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableForInternalSenders) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA180.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA180.ps1 index 7bd541c3be64..25bead866513 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA180.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA180.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA180 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have Spoof Intelligence enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have Spoof Intelligence enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have Spoof Intelligence enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Spoof Intelligence |`n" - $Result += "|------------|--------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have Spoof Intelligence enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Spoof Intelligence |`n") + $null = $Result.Append("|------------|--------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableSpoofIntelligence) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableSpoofIntelligence) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189.ps1 index c2d4bb76cda1..d5618addeeac 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189.ps1 @@ -22,14 +22,14 @@ function Invoke-CippTestORCA189 { if ($BypassRules.Count -eq 0) { $Status = 'Passed' - $Result = "No transport rules are bypassing Safe Attachments processing." + $Result = [System.Text.StringBuilder]::new("No transport rules are bypassing Safe Attachments processing.") } else { $Status = 'Failed' - $Result = "$($BypassRules.Count) transport rules are bypassing Safe Attachments processing.`n`n" - $Result += "| Rule Name | Priority |`n" - $Result += "|-----------|----------|`n" + $Result = [System.Text.StringBuilder]::new("$($BypassRules.Count) transport rules are bypassing Safe Attachments processing.`n`n") + $null = $Result.Append("| Rule Name | Priority |`n") + $null = $Result.Append("|-----------|----------|`n") foreach ($Rule in $BypassRules) { - $Result += "| $($Rule.Name) | $($Rule.Priority) |`n" + $null = $Result.Append("| $($Rule.Name) | $($Rule.Priority) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189_2.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189_2.ps1 index f5eeb2ed4d47..ae5adf2160dd 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189_2.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA189_2.ps1 @@ -22,14 +22,14 @@ function Invoke-CippTestORCA189_2 { if ($BypassRules.Count -eq 0) { $Status = 'Passed' - $Result = "No transport rules are bypassing Safe Links processing." + $Result = [System.Text.StringBuilder]::new("No transport rules are bypassing Safe Links processing.") } else { $Status = 'Failed' - $Result = "$($BypassRules.Count) transport rules are bypassing Safe Links processing.`n`n" - $Result += "| Rule Name | Priority |`n" - $Result += "|-----------|----------|`n" + $Result = [System.Text.StringBuilder]::new("$($BypassRules.Count) transport rules are bypassing Safe Links processing.`n`n") + $null = $Result.Append("| Rule Name | Priority |`n") + $null = $Result.Append("|-----------|----------|`n") foreach ($Rule in $BypassRules) { - $Result += "| $($Rule.Name) | $($Rule.Priority) |`n" + $null = $Result.Append("| $($Rule.Name) | $($Rule.Priority) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA205.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA205.ps1 index bf6b8e732fa8..cfe5ce687551 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA205.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA205.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA205 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All malware filter policies have common attachment type filter enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All malware filter policies have common attachment type filter enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) malware filter policies do not have common attachment type filter enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable File Filter |`n" - $Result += "|------------|-------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) malware filter policies do not have common attachment type filter enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable File Filter |`n") + $null = $Result.Append("|------------|-------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableFileFilter) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableFileFilter) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA220.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA220.ps1 index e805a5afad39..2fe281429948 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA220.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA220.ps1 @@ -27,16 +27,16 @@ function Invoke-CippTestORCA220 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have adequate phishing threshold levels (2 or higher).`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have adequate phishing threshold levels (2 or higher).`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies have inadequate phishing threshold levels.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Phish Threshold Level |`n" - $Result += "|------------|----------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies have inadequate phishing threshold levels.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Phish Threshold Level |`n") + $null = $Result.Append("|------------|----------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.PhishThresholdLevel) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.PhishThresholdLevel) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA221.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA221.ps1 index ec51d22b4575..3a4e73ffcf9b 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA221.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA221.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA221 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have mailbox intelligence enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have mailbox intelligence enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have mailbox intelligence enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Mailbox Intelligence |`n" - $Result += "|------------|----------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have mailbox intelligence enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Mailbox Intelligence |`n") + $null = $Result.Append("|------------|----------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableMailboxIntelligence) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableMailboxIntelligence) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA222.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA222.ps1 index 184f047d72b8..b28d794331d0 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA222.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA222.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA222 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have Domain Impersonation action set to Quarantine.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have Domain Impersonation action set to Quarantine.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have Domain Impersonation action set to Quarantine.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Targeted Domain Protection Action |`n" - $Result += "|------------|----------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have Domain Impersonation action set to Quarantine.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Targeted Domain Protection Action |`n") + $null = $Result.Append("|------------|----------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.TargetedDomainProtectionAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.TargetedDomainProtectionAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA223.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA223.ps1 index 034a0af3980d..861687b05c67 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA223.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA223.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA223 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have User Impersonation action set to Quarantine.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have User Impersonation action set to Quarantine.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have User Impersonation action set to Quarantine.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Targeted User Protection Action |`n" - $Result += "|------------|--------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have User Impersonation action set to Quarantine.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Targeted User Protection Action |`n") + $null = $Result.Append("|------------|--------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.TargetedUserProtectionAction) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.TargetedUserProtectionAction) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA224.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA224.ps1 index 6ab7c9533dde..e50ca47e8309 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA224.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA224.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA224 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have Similar Users Safety Tips enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have Similar Users Safety Tips enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have Similar Users Safety Tips enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Similar Users Safety Tips |`n" - $Result += "|------------|----------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have Similar Users Safety Tips enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Similar Users Safety Tips |`n") + $null = $Result.Append("|------------|----------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableSimilarUsersSafetyTips) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableSimilarUsersSafetyTips) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA225.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA225.ps1 index 25a1bd09b827..487b3af7820e 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA225.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA225.ps1 @@ -17,12 +17,12 @@ function Invoke-CippTestORCA225 { if ($Policy.EnableSafeDocs -eq $true) { $Status = 'Passed' - $Result = "Safe Documents is enabled for Office clients.`n`n" - $Result += "**EnableSafeDocs:** $($Policy.EnableSafeDocs)" + $Result = [System.Text.StringBuilder]::new("Safe Documents is enabled for Office clients.`n`n") + $null = $Result.Append("**EnableSafeDocs:** $($Policy.EnableSafeDocs)") } else { $Status = 'Failed' - $Result = "Safe Documents is NOT enabled for Office clients.`n`n" - $Result += "**EnableSafeDocs:** $($Policy.EnableSafeDocs)" + $Result = [System.Text.StringBuilder]::new("Safe Documents is NOT enabled for Office clients.`n`n") + $null = $Result.Append("**EnableSafeDocs:** $($Policy.EnableSafeDocs)") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA225' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Safe Documents is enabled for Office clients' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Safe Attachments' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA226.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA226.ps1 index 3293fc49bb0f..a25c716e83bf 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA226.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA226.ps1 @@ -38,15 +38,15 @@ function Invoke-CippTestORCA226 { if ($DomainsWithoutPolicy.Count -eq 0) { $Status = 'Passed' - $Result = "All accepted domains are covered by Safe Links policies.`n`n" - $Result += "**Total Accepted Domains:** $($AcceptedDomains.Count)`n" - $Result += "**Total Safe Links Policies:** $($SafeLinksPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All accepted domains are covered by Safe Links policies.`n`n") + $null = $Result.Append("**Total Accepted Domains:** $($AcceptedDomains.Count)`n") + $null = $Result.Append("**Total Safe Links Policies:** $($SafeLinksPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($DomainsWithoutPolicy.Count) domains do not have a Safe Links policy.`n`n" - $Result += "**Domains Without Policy:**`n`n" + $Result = [System.Text.StringBuilder]::new("$($DomainsWithoutPolicy.Count) domains do not have a Safe Links policy.`n`n") + $null = $Result.Append("**Domains Without Policy:**`n`n") foreach ($Domain in $DomainsWithoutPolicy) { - $Result += "- $Domain`n" + $null = $Result.Append("- $Domain`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA227.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA227.ps1 index 8ec0b4a589a9..519d060dbf1c 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA227.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA227.ps1 @@ -38,15 +38,15 @@ function Invoke-CippTestORCA227 { if ($DomainsWithoutPolicy.Count -eq 0) { $Status = 'Passed' - $Result = "All accepted domains are covered by Safe Attachments policies.`n`n" - $Result += "**Total Accepted Domains:** $($AcceptedDomains.Count)`n" - $Result += "**Total Safe Attachments Policies:** $($SafeAttachmentPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All accepted domains are covered by Safe Attachments policies.`n`n") + $null = $Result.Append("**Total Accepted Domains:** $($AcceptedDomains.Count)`n") + $null = $Result.Append("**Total Safe Attachments Policies:** $($SafeAttachmentPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($DomainsWithoutPolicy.Count) domains do not have a Safe Attachments policy.`n`n" - $Result += "**Domains Without Policy:**`n`n" + $Result = [System.Text.StringBuilder]::new("$($DomainsWithoutPolicy.Count) domains do not have a Safe Attachments policy.`n`n") + $null = $Result.Append("**Domains Without Policy:**`n`n") foreach ($Domain in $DomainsWithoutPolicy) { - $Result += "- $Domain`n" + $null = $Result.Append("- $Domain`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA228.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA228.ps1 index 9cda62f6721f..932a544e49ed 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA228.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA228.ps1 @@ -28,17 +28,17 @@ function Invoke-CippTestORCA228 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "No anti-phishing policies have trusted senders configured.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("No anti-phishing policies have trusted senders configured.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies have trusted senders configured.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Excluded Senders Count |`n" - $Result += "|------------|----------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies have trusted senders configured.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Excluded Senders Count |`n") + $null = $Result.Append("|------------|----------------------|`n") foreach ($Policy in $FailedPolicies) { $Count = if ($Policy.ExcludedSenders) { $Policy.ExcludedSenders.Count } else { 0 } - $Result += "| $($Policy.Identity) | $Count |`n" + $null = $Result.Append("| $($Policy.Identity) | $Count |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA229.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA229.ps1 index 8ec32aeae7ba..94ade900dfca 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA229.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA229.ps1 @@ -28,17 +28,17 @@ function Invoke-CippTestORCA229 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "No anti-phishing policies have trusted domains configured.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("No anti-phishing policies have trusted domains configured.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies have trusted domains configured.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Excluded Domains Count |`n" - $Result += "|------------|----------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies have trusted domains configured.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Excluded Domains Count |`n") + $null = $Result.Append("|------------|----------------------|`n") foreach ($Policy in $FailedPolicies) { $Count = if ($Policy.ExcludedDomains) { $Policy.ExcludedDomains.Count } else { 0 } - $Result += "| $($Policy.Identity) | $Count |`n" + $null = $Result.Append("| $($Policy.Identity) | $Count |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA230.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA230.ps1 index 434dbd2a1376..37b89f62c44f 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA230.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA230.ps1 @@ -38,15 +38,15 @@ function Invoke-CippTestORCA230 { if ($DomainsWithoutPolicy.Count -eq 0) { $Status = 'Passed' - $Result = "All accepted domains are covered by Anti-phishing policies.`n`n" - $Result += "**Total Accepted Domains:** $($AcceptedDomains.Count)`n" - $Result += "**Total Anti-phishing Policies:** $($AntiPhishPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All accepted domains are covered by Anti-phishing policies.`n`n") + $null = $Result.Append("**Total Accepted Domains:** $($AcceptedDomains.Count)`n") + $null = $Result.Append("**Total Anti-phishing Policies:** $($AntiPhishPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($DomainsWithoutPolicy.Count) domains do not have an Anti-phishing policy.`n`n" - $Result += "**Domains Without Policy:**`n`n" + $Result = [System.Text.StringBuilder]::new("$($DomainsWithoutPolicy.Count) domains do not have an Anti-phishing policy.`n`n") + $null = $Result.Append("**Domains Without Policy:**`n`n") foreach ($Domain in $DomainsWithoutPolicy) { - $Result += "- $Domain`n" + $null = $Result.Append("- $Domain`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA231.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA231.ps1 index 10803fd0a5d4..b9a01cd1529a 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA231.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA231.ps1 @@ -38,15 +38,15 @@ function Invoke-CippTestORCA231 { if ($DomainsWithoutPolicy.Count -eq 0) { $Status = 'Passed' - $Result = "All accepted domains are covered by anti-spam policies.`n`n" - $Result += "**Total Accepted Domains:** $($AcceptedDomains.Count)`n" - $Result += "**Total Anti-spam Policies:** $($ContentFilterPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All accepted domains are covered by anti-spam policies.`n`n") + $null = $Result.Append("**Total Accepted Domains:** $($AcceptedDomains.Count)`n") + $null = $Result.Append("**Total Anti-spam Policies:** $($ContentFilterPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($DomainsWithoutPolicy.Count) domains do not have an anti-spam policy.`n`n" - $Result += "**Domains Without Policy:**`n`n" + $Result = [System.Text.StringBuilder]::new("$($DomainsWithoutPolicy.Count) domains do not have an anti-spam policy.`n`n") + $null = $Result.Append("**Domains Without Policy:**`n`n") foreach ($Domain in $DomainsWithoutPolicy) { - $Result += "- $Domain`n" + $null = $Result.Append("- $Domain`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA232.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA232.ps1 index f65ae48d5ec3..280d54cc0877 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA232.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA232.ps1 @@ -38,15 +38,15 @@ function Invoke-CippTestORCA232 { if ($DomainsWithoutPolicy.Count -eq 0) { $Status = 'Passed' - $Result = "All accepted domains are covered by malware filter policies.`n`n" - $Result += "**Total Accepted Domains:** $($AcceptedDomains.Count)`n" - $Result += "**Total Malware Filter Policies:** $($MalwarePolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All accepted domains are covered by malware filter policies.`n`n") + $null = $Result.Append("**Total Accepted Domains:** $($AcceptedDomains.Count)`n") + $null = $Result.Append("**Total Malware Filter Policies:** $($MalwarePolicies.Count)") } else { $Status = 'Failed' - $Result = "$($DomainsWithoutPolicy.Count) domains do not have a malware filter policy.`n`n" - $Result += "**Domains Without Policy:**`n`n" + $Result = [System.Text.StringBuilder]::new("$($DomainsWithoutPolicy.Count) domains do not have a malware filter policy.`n`n") + $null = $Result.Append("**Domains Without Policy:**`n`n") foreach ($Domain in $DomainsWithoutPolicy) { - $Result += "- $Domain`n" + $null = $Result.Append("- $Domain`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233.ps1 index 30484096a341..853732211588 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233.ps1 @@ -34,14 +34,14 @@ function Invoke-CippTestORCA233 { if ($NonCompliantDomains.Count -eq 0) { $Status = 'Passed' - $Result = "All domains are properly configured for mail flow.`n`n" - $Result += "**Compliant Domains:** $($CompliantDomains.Count)" + $Result = [System.Text.StringBuilder]::new("All domains are properly configured for mail flow.`n`n") + $null = $Result.Append("**Compliant Domains:** $($CompliantDomains.Count)") } else { $Status = 'Failed' - $Result = "$($NonCompliantDomains.Count) domains may not be properly configured for mail flow.`n`n" - $Result += "**Domains Needing Review:**`n`n" + $Result = [System.Text.StringBuilder]::new("$($NonCompliantDomains.Count) domains may not be properly configured for mail flow.`n`n") + $null = $Result.Append("**Domains Needing Review:**`n`n") foreach ($Domain in $NonCompliantDomains) { - $Result += "- $Domain`n" + $null = $Result.Append("- $Domain`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233_1.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233_1.ps1 index 4cd287b564f9..3fb1c2970d87 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA233_1.ps1 @@ -26,12 +26,12 @@ function Invoke-CippTestORCA233_1 { if ($EnhancedFilteringEnabled) { $Status = 'Passed' - $Result = "Enhanced filtering appears to be properly configured.`n`n" - $Result += "**Configuration:** Reviewed" + $Result = [System.Text.StringBuilder]::new("Enhanced filtering appears to be properly configured.`n`n") + $null = $Result.Append("**Configuration:** Reviewed") } else { $Status = 'Informational' - $Result = "Unable to fully determine enhanced filtering status. Manual review recommended.`n`n" - $Result += "**Action Required:** Review inbound connectors for enhanced filtering configuration" + $Result = [System.Text.StringBuilder]::new("Unable to fully determine enhanced filtering status. Manual review recommended.`n`n") + $null = $Result.Append("**Action Required:** Review inbound connectors for enhanced filtering configuration") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA233_1' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Enhanced filtering on default connectors' -UserImpact 'Medium' -ImplementationEffort 'Medium' -Category 'Configuration' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA234.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA234.ps1 index 7b6c7e3abf74..a5ec89530341 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA234.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA234.ps1 @@ -17,12 +17,12 @@ function Invoke-CippTestORCA234 { if ($Policy.AllowSafeDocsOpen -eq $false) { $Status = 'Passed' - $Result = "Click through is disabled for Safe Documents.`n`n" - $Result += "**AllowSafeDocsOpen:** $($Policy.AllowSafeDocsOpen)" + $Result = [System.Text.StringBuilder]::new("Click through is disabled for Safe Documents.`n`n") + $null = $Result.Append("**AllowSafeDocsOpen:** $($Policy.AllowSafeDocsOpen)") } else { $Status = 'Failed' - $Result = "Click through is enabled for Safe Documents.`n`n" - $Result += "**AllowSafeDocsOpen:** $($Policy.AllowSafeDocsOpen)" + $Result = [System.Text.StringBuilder]::new("Click through is enabled for Safe Documents.`n`n") + $null = $Result.Append("**AllowSafeDocsOpen:** $($Policy.AllowSafeDocsOpen)") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA234' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Click through is disabled for Safe Documents' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Safe Attachments' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA235.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA235.ps1 index db2406d9e9d8..ae123a59de2c 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA235.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA235.ps1 @@ -20,17 +20,17 @@ function Invoke-CippTestORCA235 { if ($CustomDomains.Count -eq 0) { $Status = 'Passed' - $Result = "No custom domains found. Only using onmicrosoft.com domain.`n`n" - $Result += "**Total Domains:** $($AcceptedDomains.Count)" + $Result = [System.Text.StringBuilder]::new("No custom domains found. Only using onmicrosoft.com domain.`n`n") + $null = $Result.Append("**Total Domains:** $($AcceptedDomains.Count)") } else { $Status = 'Informational' - $Result = "Found $($CustomDomains.Count) custom domains that should have SPF records configured.`n`n" - $Result += "**Custom Domains:**`n`n" + $Result = [System.Text.StringBuilder]::new("Found $($CustomDomains.Count) custom domains that should have SPF records configured.`n`n") + $null = $Result.Append("**Custom Domains:**`n`n") foreach ($Domain in $CustomDomains) { - $Result += "- $($Domain.DomainName)`n" + $null = $Result.Append("- $($Domain.DomainName)`n") } - $Result += "`n**Action Required:** Verify that each custom domain has an SPF record including Microsoft 365:`n" - $Result += "``v=spf1 include:spf.protection.outlook.com -all``" + $null = $Result.Append("`n**Action Required:** Verify that each custom domain has an SPF record including Microsoft 365:`n") + $null = $Result.Append("``v=spf1 include:spf.protection.outlook.com -all``") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA235' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'High' -Name 'SPF records setup for custom domains' -UserImpact 'High' -ImplementationEffort 'Medium' -Category 'Configuration' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA236.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA236.ps1 index 36bdc40b63a1..290b843d81af 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA236.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA236.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA236 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Links policies have email protection enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All Safe Links policies have email protection enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) Safe Links policies do not have email protection enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Safe Links For Email |`n" - $Result += "|------------|----------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) Safe Links policies do not have email protection enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Safe Links For Email |`n") + $null = $Result.Append("|------------|----------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableSafeLinksForEmail) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableSafeLinksForEmail) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA237.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA237.ps1 index 37bbc9ba6f15..03d259bde938 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA237.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA237.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA237 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Links policies have Teams protection enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All Safe Links policies have Teams protection enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) Safe Links policies do not have Teams protection enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Safe Links For Teams |`n" - $Result += "|------------|----------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) Safe Links policies do not have Teams protection enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Safe Links For Teams |`n") + $null = $Result.Append("|------------|----------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableSafeLinksForTeams) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableSafeLinksForTeams) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA238.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA238.ps1 index 53c85ab0955f..c93892a42786 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA238.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA238.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA238 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All Safe Links policies have Office document protection enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All Safe Links policies have Office document protection enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) Safe Links policies do not have Office document protection enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable Safe Links For Office |`n" - $Result += "|------------|------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) Safe Links policies do not have Office document protection enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable Safe Links For Office |`n") + $null = $Result.Append("|------------|------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableSafeLinksForOffice) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableSafeLinksForOffice) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 index 629181cb5c95..414b918df87f 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 @@ -63,13 +63,13 @@ function Invoke-CippTestORCA239 { if ($Issues.Count -eq 0) { $Status = 'Passed' - $Result = "No exclusions found in built-in protection policies." + $Result = [System.Text.StringBuilder]::new("No exclusions found in built-in protection policies.") } else { $Status = 'Failed' - $Result = "Found $($Issues.Count) policies with exclusions that bypass built-in protection.`n`n" - $Result += "**Issues Found:**`n`n" + $Result = [System.Text.StringBuilder]::new("Found $($Issues.Count) policies with exclusions that bypass built-in protection.`n`n") + $null = $Result.Append("**Issues Found:**`n`n") foreach ($Issue in $Issues) { - $Result += "- $Issue`n" + $null = $Result.Append("- $Issue`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA240.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA240.ps1 index 746b76c06af1..ee20556e366d 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA240.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA240.ps1 @@ -17,12 +17,12 @@ function Invoke-CippTestORCA240 { if ($Config.ExternalInOutlook -ne 'Disabled') { $Status = 'Passed' - $Result = "Outlook external tags are configured.`n`n" - $Result += "**ExternalInOutlook:** $($Config.ExternalInOutlook)" + $Result = [System.Text.StringBuilder]::new("Outlook external tags are configured.`n`n") + $null = $Result.Append("**ExternalInOutlook:** $($Config.ExternalInOutlook)") } else { $Status = 'Failed' - $Result = "Outlook external tags are NOT configured.`n`n" - $Result += "**ExternalInOutlook:** $($Config.ExternalInOutlook)" + $Result = [System.Text.StringBuilder]::new("Outlook external tags are NOT configured.`n`n") + $null = $Result.Append("**ExternalInOutlook:** $($Config.ExternalInOutlook)") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA240' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Outlook external tags are configured' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Configuration' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA241.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA241.ps1 index 6b53171fe109..e01a95a886c1 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA241.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA241.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA241 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-phishing policies have First Contact Safety Tips enabled.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-phishing policies have First Contact Safety Tips enabled.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-phishing policies do not have First Contact Safety Tips enabled.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Enable First Contact Safety Tips |`n" - $Result += "|------------|----------------------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-phishing policies do not have First Contact Safety Tips enabled.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Enable First Contact Safety Tips |`n") + $null = $Result.Append("|------------|----------------------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.EnableFirstContactSafetyTips) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.EnableFirstContactSafetyTips) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA242.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA242.ps1 index ba691ddf939e..b2e15b06483a 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA242.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA242.ps1 @@ -10,15 +10,15 @@ function Invoke-CippTestORCA242 { # Since we don't have an alert policy cache, we'll provide informational guidance $Status = 'Informational' - $Result = "Alert policies for protection features should be enabled and monitored.`n`n" - $Result += "**Recommended Alert Policies:**`n`n" - $Result += "- Messages reported by users as malware or phish`n" - $Result += "- Email sending limit exceeded`n" - $Result += "- Suspicious email forwarding activity`n" - $Result += "- Malware campaign detected`n" - $Result += "- Suspicious connector activity`n" - $Result += "- Unusual external user file activity`n" - $Result += "`n**Action Required:** Verify alert policies are configured in Microsoft 365 Security & Compliance Center" + $Result = [System.Text.StringBuilder]::new("Alert policies for protection features should be enabled and monitored.`n`n") + $null = $Result.Append("**Recommended Alert Policies:**`n`n") + $null = $Result.Append("- Messages reported by users as malware or phish`n") + $null = $Result.Append("- Email sending limit exceeded`n") + $null = $Result.Append("- Suspicious email forwarding activity`n") + $null = $Result.Append("- Malware campaign detected`n") + $null = $Result.Append("- Suspicious connector activity`n") + $null = $Result.Append("- Unusual external user file activity`n") + $null = $Result.Append("`n**Action Required:** Verify alert policies are configured in Microsoft 365 Security & Compliance Center") Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA242' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Important protection alerts enabled' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Configuration' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA243.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA243.ps1 index 18e8f8ceb02f..7f6fac60c6ed 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA243.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA243.ps1 @@ -18,16 +18,16 @@ function Invoke-CippTestORCA243 { if ($NonAuthDomains.Count -eq 0) { $Status = 'Passed' - $Result = "All domains are authoritative. No inbound connectors needed.`n`n" - $Result += "**Total Domains:** $($AcceptedDomains.Count)" + $Result = [System.Text.StringBuilder]::new("All domains are authoritative. No inbound connectors needed.`n`n") + $null = $Result.Append("**Total Domains:** $($AcceptedDomains.Count)") } else { $Status = 'Informational' - $Result = "Found $($NonAuthDomains.Count) non-authoritative domains.`n`n" - $Result += "**Domains Requiring Inbound Connectors:**`n`n" + $Result = [System.Text.StringBuilder]::new("Found $($NonAuthDomains.Count) non-authoritative domains.`n`n") + $null = $Result.Append("**Domains Requiring Inbound Connectors:**`n`n") foreach ($Domain in $NonAuthDomains) { - $Result += "- $($Domain.DomainName) (Type: $($Domain.DomainType))`n" + $null = $Result.Append("- $($Domain.DomainName) (Type: $($Domain.DomainType))`n") } - $Result += "`n**Action Required:** Verify inbound connectors are configured with proper authentication for these domains" + $null = $Result.Append("`n**Action Required:** Verify inbound connectors are configured with proper authentication for these domains") } Add-CippTestResult -TenantFilter $Tenant -TestId 'ORCA243' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Authenticated Receive Chain for non-EOP domains' -UserImpact 'Medium' -ImplementationEffort 'High' -Category 'Configuration' diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA244.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA244.ps1 index 7c08c0afce41..7b413894a8a4 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA244.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA244.ps1 @@ -26,16 +26,16 @@ function Invoke-CippTestORCA244 { if ($FailedPolicies.Count -eq 0) { $Status = 'Passed' - $Result = "All anti-spam policies honor sending domain DMARC.`n`n" - $Result += "**Compliant Policies:** $($PassedPolicies.Count)" + $Result = [System.Text.StringBuilder]::new("All anti-spam policies honor sending domain DMARC.`n`n") + $null = $Result.Append("**Compliant Policies:** $($PassedPolicies.Count)") } else { $Status = 'Failed' - $Result = "$($FailedPolicies.Count) anti-spam policies do not honor sending domain DMARC.`n`n" - $Result += "**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n" - $Result += "| Policy Name | Honor DMARC Policy |`n" - $Result += "|------------|--------------------|`n" + $Result = [System.Text.StringBuilder]::new("$($FailedPolicies.Count) anti-spam policies do not honor sending domain DMARC.`n`n") + $null = $Result.Append("**Non-Compliant Policies:** $($FailedPolicies.Count)`n`n") + $null = $Result.Append("| Policy Name | Honor DMARC Policy |`n") + $null = $Result.Append("|------------|--------------------|`n") foreach ($Policy in $FailedPolicies) { - $Result += "| $($Policy.Identity) | $($Policy.HonorDMARCPolicy) |`n" + $null = $Result.Append("| $($Policy.Identity) | $($Policy.HonorDMARCPolicy) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24541.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24541.ps1 index 72b65a2d6f21..e3142271da5b 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24541.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24541.ps1 @@ -23,18 +23,18 @@ function Invoke-CippTestZTNA24541 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one Windows compliance policy exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one Windows compliance policy exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No Windows compliance policy exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No Windows compliance policy exists or none are assigned.`n`n") } - $ResultMarkdown += "## Windows Compliance Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## Windows Compliance Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $WindowsPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24542.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24542.ps1 index 1fb548edd6e8..7a750097f195 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24542.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24542.ps1 @@ -20,18 +20,18 @@ function Invoke-CippTestZTNA24542 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one macOS compliance policy exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one macOS compliance policy exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No macOS compliance policy exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No macOS compliance policy exists or none are assigned.`n`n") } - $ResultMarkdown += "## macOS Compliance Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## macOS Compliance Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $MacOSPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24543.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24543.ps1 index 06a3802e8724..c4bb08d74a22 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24543.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24543.ps1 @@ -21,18 +21,18 @@ function Invoke-CippTestZTNA24543 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one iOS/iPadOS compliance policy exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one iOS/iPadOS compliance policy exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No iOS/iPadOS compliance policy exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No iOS/iPadOS compliance policy exists or none are assigned.`n`n") } - $ResultMarkdown += "## iOS/iPadOS Compliance Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## iOS/iPadOS Compliance Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $iOSPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24545.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24545.ps1 index bdbc75e5a84e..c490b477c98e 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24545.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24545.ps1 @@ -22,18 +22,18 @@ function Invoke-CippTestZTNA24545 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one compliance policy for Android Enterprise Fully managed devices exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one compliance policy for Android Enterprise Fully managed devices exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No compliance policy for Android Enterprise exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No compliance policy for Android Enterprise exists or none are assigned.`n`n") } - $ResultMarkdown += "## Android Device Owner Compliance Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## Android Device Owner Compliance Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $AndroidPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24547.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24547.ps1 index b7f2ace76377..38ceb556ca60 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24547.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24547.ps1 @@ -22,18 +22,18 @@ function Invoke-CippTestZTNA24547 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one compliance policy for Android Work Profile devices exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one compliance policy for Android Work Profile devices exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No compliance policy for Android Work Profile exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No compliance policy for Android Work Profile exists or none are assigned.`n`n") } - $ResultMarkdown += "## Android Work Profile Compliance Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## Android Work Profile Compliance Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $AndroidPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24548.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24548.ps1 index 0d5e586f0f67..0809843b6969 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24548.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24548.ps1 @@ -20,18 +20,18 @@ function Invoke-CippTestZTNA24548 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one iOS app protection policy exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one iOS app protection policy exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No iOS app protection policy exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No iOS app protection policy exists or none are assigned.`n`n") } - $ResultMarkdown += "## iOS App Protection Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## iOS App Protection Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $IosPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24549.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24549.ps1 index 0b8c6887875f..2837b609f04d 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24549.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24549.ps1 @@ -20,18 +20,18 @@ function Invoke-CippTestZTNA24549 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one Android app protection policy exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one Android app protection policy exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No Android app protection policy exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No Android app protection policy exists or none are assigned.`n`n") } - $ResultMarkdown += "## Android App Protection Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## Android App Protection Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $AndroidPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24553.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24553.ps1 index cb9586dcf3c5..859ecdf83c60 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24553.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24553.ps1 @@ -27,19 +27,19 @@ function Invoke-CippTestZTNA24553 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ Windows Update policies are configured and assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ Windows Update policies are configured and assigned.`n`n") } else { - $ResultMarkdown = "❌ No Windows Update policies are configured or assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No Windows Update policies are configured or assigned.`n`n") } - $ResultMarkdown += "## Windows Update Policies`n`n" - $ResultMarkdown += "| Policy Name | Type | Assigned |`n" - $ResultMarkdown += "| :---------- | :--- | :------- |`n" + $null = $ResultMarkdown.Append("## Windows Update Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Type | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :--- | :------- |`n") foreach ($policy in $UpdatePolicies) { $type = if ($policy.'@odata.type' -eq '#microsoft.graph.windowsUpdateForBusinessConfiguration') { 'Update' } else { 'Compliance' } $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $type | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $type | $assigned |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24569.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24569.ps1 index 244fff8ecaa3..544ebe43eaed 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24569.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24569.ps1 @@ -25,23 +25,23 @@ function Invoke-CippTestZTNA24569 { $Passed = $AssignedFileVaultPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ macOS FileVault encryption policies are configured and assigned in Intune.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ macOS FileVault encryption policies are configured and assigned in Intune.`n`n") } else { - $ResultMarkdown = "❌ No relevant macOS FileVault encryption policies are configured or assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No relevant macOS FileVault encryption policies are configured or assigned.`n`n") } if ($FileVaultEnabledPolicies.Count -gt 0) { - $ResultMarkdown += "## macOS FileVault Policies`n`n" - $ResultMarkdown += "| Policy Name | FileVault Enabled | Assigned |`n" - $ResultMarkdown += "| :---------- | :---------------- | :------- |`n" + $null = $ResultMarkdown.Append("## macOS FileVault Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | FileVault Enabled | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :---------------- | :------- |`n") foreach ($policy in $FileVaultEnabledPolicies) { $fileVault = if ($policy.fileVaultEnabled -eq $true) { '✅ Yes' } else { '❌ No' } $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $fileVault | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $fileVault | $assigned |`n") } } else { - $ResultMarkdown += "No macOS Endpoint Protection policies with FileVault settings found.`n" + $null = $ResultMarkdown.Append("No macOS Endpoint Protection policies with FileVault settings found.`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24576.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24576.ps1 index a513cf5f40ce..a767d7884d42 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24576.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24576.ps1 @@ -24,22 +24,22 @@ function Invoke-CippTestZTNA24576 { $Passed = $AssignedPolicies.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ An Endpoint analytics policy is created and assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ An Endpoint analytics policy is created and assigned.`n`n") } else { - $ResultMarkdown = "❌ Endpoint analytics policy is not created or not assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Endpoint analytics policy is not created or not assigned.`n`n") } if ($WindowsHealthMonitoringPolicies.Count -gt 0) { - $ResultMarkdown += "## Endpoint Analytics Policies`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## Endpoint Analytics Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $WindowsHealthMonitoringPolicies) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } } else { - $ResultMarkdown += "No Endpoint Analytics policies found in this tenant.`n" + $null = $ResultMarkdown.Append("No Endpoint Analytics policies found in this tenant.`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24839.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24839.ps1 index e3e9757e12cc..b7feb312655c 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24839.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24839.ps1 @@ -22,23 +22,23 @@ function Invoke-CippTestZTNA24839 { $Passed = $AssignedCompliantProfiles.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one Enterprise Wi-Fi profile for iOS exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one Enterprise Wi-Fi profile for iOS exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No Enterprise Wi-Fi profile for iOS exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No Enterprise Wi-Fi profile for iOS exists or none are assigned.`n`n") } if ($iOSWifiConfProfiles.Count -gt 0) { - $ResultMarkdown += "## iOS WiFi Configuration Profiles`n`n" - $ResultMarkdown += "| Policy Name | Wi-Fi Security Type | Assigned |`n" - $ResultMarkdown += "| :---------- | :------------------ | :------- |`n" + $null = $ResultMarkdown.Append("## iOS WiFi Configuration Profiles`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Wi-Fi Security Type | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------------------ | :------- |`n") foreach ($policy in $iOSWifiConfProfiles) { $securityType = if ($policy.wiFiSecurityType) { $policy.wiFiSecurityType } else { 'Unknown' } $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $securityType | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $securityType | $assigned |`n") } } else { - $ResultMarkdown += "No iOS WiFi configuration profiles found.`n" + $null = $ResultMarkdown.Append("No iOS WiFi configuration profiles found.`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24840.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24840.ps1 index 4175cdf68936..8b9f5db74108 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24840.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24840.ps1 @@ -23,23 +23,23 @@ function Invoke-CippTestZTNA24840 { $Passed = $AssignedCompliantProfiles.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one Enterprise Wi-Fi profile for android exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one Enterprise Wi-Fi profile for android exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No Enterprise Wi-Fi profile for android exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No Enterprise Wi-Fi profile for android exists or none are assigned.`n`n") } if ($CompliantAndroidWifiConfProfiles.Count -gt 0) { - $ResultMarkdown += "## Android Wi-Fi Configuration Profiles`n`n" - $ResultMarkdown += "| Policy Name | Wi-Fi Security Type | Assigned |`n" - $ResultMarkdown += "| :---------- | :------------------ | :------- |`n" + $null = $ResultMarkdown.Append("## Android Wi-Fi Configuration Profiles`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Wi-Fi Security Type | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------------------ | :------- |`n") foreach ($policy in $CompliantAndroidWifiConfProfiles) { $securityType = if ($policy.wiFiSecurityType) { $policy.wiFiSecurityType } else { 'Unknown' } $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $securityType | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $securityType | $assigned |`n") } } else { - $ResultMarkdown += "No compliant Android Enterprise WiFi configuration profiles found.`n" + $null = $ResultMarkdown.Append("No compliant Android Enterprise WiFi configuration profiles found.`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24870.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24870.ps1 index 3b3fb2c65249..40ac0201df45 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24870.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24870.ps1 @@ -22,23 +22,23 @@ function Invoke-CippTestZTNA24870 { $Passed = $AssignedCompliantProfiles.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one Enterprise Wi-Fi profile for macOS exists and is assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one Enterprise Wi-Fi profile for macOS exists and is assigned.`n`n") } else { - $ResultMarkdown = "❌ No Enterprise Wi-Fi profile for macOS exists or none are assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No Enterprise Wi-Fi profile for macOS exists or none are assigned.`n`n") } if ($CompliantMacOSWifiConfProfiles.Count -gt 0) { - $ResultMarkdown += "## macOS WiFi Configuration Profiles`n`n" - $ResultMarkdown += "| Policy Name | Wi-Fi Security Type | Assigned |`n" - $ResultMarkdown += "| :---------- | :------------------ | :------- |`n" + $null = $ResultMarkdown.Append("## macOS WiFi Configuration Profiles`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Wi-Fi Security Type | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------------------ | :------- |`n") foreach ($policy in $CompliantMacOSWifiConfProfiles) { $securityType = if ($policy.wiFiSecurityType) { $policy.wiFiSecurityType } else { 'Unknown' } $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $securityType | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $securityType | $assigned |`n") } } else { - $ResultMarkdown += "No compliant macOS Enterprise WiFi configuration profiles found.`n" + $null = $ResultMarkdown.Append("No compliant macOS Enterprise WiFi configuration profiles found.`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 index 64a0681cf1bc..5c82edcc4e51 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 @@ -82,59 +82,59 @@ function Invoke-CippTestZTNA21797 { } } - $mdInfo = "`n## Passwordless Authentication Methods allowed in tenant`n`n" + $mdInfo = [System.Text.StringBuilder]::new("`n## Passwordless Authentication Methods allowed in tenant`n`n") if ($passwordlessAuthMethods.Count -gt 0) { - $mdInfo += "| Authentication Method Name | State | Additional Info |`n" - $mdInfo += "| :------------------------ | :---- | :-------------- |`n" + $null = $mdInfo.Append("| Authentication Method Name | State | Additional Info |`n") + $null = $mdInfo.Append("| :------------------------ | :---- | :-------------- |`n") foreach ($method in $passwordlessAuthMethods) { - $mdInfo += "| $($method.Name) | $($method.State) | $($method.AdditionalInfo) |`n" + $null = $mdInfo.Append("| $($method.Name) | $($method.State) | $($method.AdditionalInfo) |`n") } } else { - $mdInfo += "No passwordless authentication methods are enabled.`n" + $null = $mdInfo.Append("No passwordless authentication methods are enabled.`n") } - $mdInfo += "`n## Conditional Access Policies targeting high risk users`n`n" + $null = $mdInfo.Append("`n## Conditional Access Policies targeting high risk users`n`n") $allEnabledHighRiskPolicies = @($caPasswordChangePolicies) + @($caBlockPolicies) if ($allEnabledHighRiskPolicies.Count -gt 0) { - $mdInfo += "| Conditional Access Policy Name | Status | Conditions |`n" - $mdInfo += "| :--------------------- | :----- | :--------- |`n" + $null = $mdInfo.Append("| Conditional Access Policy Name | Status | Conditions |`n") + $null = $mdInfo.Append("| :--------------------- | :----- | :--------- |`n") foreach ($policy in $allEnabledHighRiskPolicies) { - $conditions = 'User Risk Level: High' + $conditions = [System.Text.StringBuilder]::new('User Risk Level: High') if ($policy.grantControls.builtInControls -contains 'passwordChange') { - $conditions += ', Control: Password Change' + $null = $conditions.Append(', Control: Password Change') } if ($policy.grantControls.builtInControls -contains 'block') { - $conditions += ', Control: Block' + $null = $conditions.Append(', Control: Block') } - $mdInfo += "| $($policy.displayName) | Enabled | $conditions |`n" + $null = $mdInfo.Append("| $($policy.displayName) | Enabled | $conditions |`n") } } if ($inactiveCAPolicies.Count -gt 0) { if ($allEnabledHighRiskPolicies.Count -eq 0) { - $mdInfo += "No conditional access policies targeting high risk users found.`n`n" - $mdInfo += "### Inactive policies targeting high risk users (not contributing to security posture):`n`n" - $mdInfo += "| Conditional Access Policy Name | Status | Conditions |`n" - $mdInfo += "| :--------------------- | :----- | :--------- |`n" + $null = $mdInfo.Append("No conditional access policies targeting high risk users found.`n`n") + $null = $mdInfo.Append("### Inactive policies targeting high risk users (not contributing to security posture):`n`n") + $null = $mdInfo.Append("| Conditional Access Policy Name | Status | Conditions |`n") + $null = $mdInfo.Append("| :--------------------- | :----- | :--------- |`n") } foreach ($policy in $inactiveCAPolicies) { - $conditions = 'User Risk Level: High' + $conditions = [System.Text.StringBuilder]::new('User Risk Level: High') if ($policy.grantControls.builtInControls -contains 'passwordChange') { - $conditions += ', Control: Password Change' + $null = $conditions.Append(', Control: Password Change') } if ($policy.grantControls.builtInControls -contains 'block') { - $conditions += ', Control: Block' + $null = $conditions.Append(', Control: Block') } $status = if ($policy.state -eq 'enabledForReportingButNotEnforced') { 'Report-only' } else { 'Disabled' } - $mdInfo += "| $($policy.displayName) | $status | $conditions |`n" + $null = $mdInfo.Append("| $($policy.displayName) | $status | $conditions |`n") } } elseif ($allEnabledHighRiskPolicies.Count -eq 0) { - $mdInfo += "No conditional access policies targeting high risk users found.`n" + $null = $mdInfo.Append("No conditional access policies targeting high risk users found.`n") } $testResultMarkdown = $testResultMarkdown + $mdInfo diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21799.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21799.ps1 index 630493d373c2..98e8ceda9efd 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21799.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21799.ps1 @@ -46,9 +46,9 @@ function Invoke-CippTestZTNA21799 { $tableRows = '' if ($matchedPolicies.Count -gt 0) { - $mdInfo = "`n## $reportTitle`n`n" - $mdInfo += "| Policy Name | Grant Controls | Target Users |`n" - $mdInfo += "| :---------- | :------------- | :----------- |`n" + $mdInfo = [System.Text.StringBuilder]::new("`n## $reportTitle`n`n") + $null = $mdInfo.Append("| Policy Name | Grant Controls | Target Users |`n") + $null = $mdInfo.Append("| :---------- | :------------- | :----------- |`n") foreach ($policy in $matchedPolicies) { $grantControls = switch ($policy.grantControls) { @@ -69,7 +69,7 @@ function Invoke-CippTestZTNA21799 { $policy.conditions.users.includeUsers -join ', ' } - $mdInfo += "| $($policy.displayName) | $grantControls | $targetUsers |`n" + $null = $mdInfo.Append("| $($policy.displayName) | $grantControls | $targetUsers |`n") } } $testResultMarkdown = $testResultMarkdown + $mdInfo diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21804.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21804.ps1 index 9efb77db92a8..2882e78b0876 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21804.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21804.ps1 @@ -27,12 +27,12 @@ function Invoke-CippTestZTNA21804 { $reportTitle = 'Weak authentication methods' - $mdInfo = "`n## $reportTitle`n`n" - $mdInfo += "| Method ID | Is method weak? | State |`n" - $mdInfo += "| :-------- | :-------------- | :---- |`n" + $mdInfo = [System.Text.StringBuilder]::new("`n## $reportTitle`n`n") + $null = $mdInfo.Append("| Method ID | Is method weak? | State |`n") + $null = $mdInfo.Append("| :-------- | :-------------- | :---- |`n") foreach ($method in $matchedMethods) { - $mdInfo += "| $($method.id) | Yes | $($method.state) |`n" + $null = $mdInfo.Append("| $($method.id) | Yes | $($method.state) |`n") } $testResultMarkdown = $testResultMarkdown + $mdInfo diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21806.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21806.ps1 index 101620a0966f..cd18bb9a0cbc 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21806.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21806.ps1 @@ -33,15 +33,15 @@ function Invoke-CippTestZTNA21806 { $tableRows = '' if ($matchedPolicies.Count -gt 0) { - $mdInfo = "`n## $reportTitle`n`n" - $mdInfo += "| Policy Name | User Actions Targeted | Grant Controls Applied |`n" - $mdInfo += "| :---------- | :-------------------- | :--------------------- |`n" + $mdInfo = [System.Text.StringBuilder]::new("`n## $reportTitle`n`n") + $null = $mdInfo.Append("| Policy Name | User Actions Targeted | Grant Controls Applied |`n") + $null = $mdInfo.Append("| :---------- | :-------------------- | :--------------------- |`n") foreach ($policy in $matchedPolicies) { - $mdInfo += "| $($policy.displayName) | $($policy.conditions.applications.includeUserActions) | $($policy.grantControls.builtInControls -join ', ') |`n" + $null = $mdInfo.Append("| $($policy.displayName) | $($policy.conditions.applications.includeUserActions) | $($policy.grantControls.builtInControls -join ', ') |`n") } } else { - $mdInfo = 'No Conditional Access policies targeting security information registration.' + $mdInfo = [System.Text.StringBuilder]::new('No Conditional Access policies targeting security information registration.') } $testResultMarkdown = $testResultMarkdown + $mdInfo diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21811.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21811.ps1 index 41ac254e7ae6..3f937afc706e 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21811.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21811.ps1 @@ -44,12 +44,12 @@ function Invoke-CippTestZTNA21811 { if ($misconfiguredDomains) { $reportTitle1 = 'Domains with password expiration enabled' - $mdInfo1 = "`n## $reportTitle1`n`n" - $mdInfo1 += "| Domain Name | Password Validity Interval |`n" - $mdInfo1 += "| :---------- | :------------------------- |`n" + $mdInfo1 = [System.Text.StringBuilder]::new("`n## $reportTitle1`n`n") + $null = $mdInfo1.Append("| Domain Name | Password Validity Interval |`n") + $null = $mdInfo1.Append("| :---------- | :------------------------- |`n") foreach ($domain in $misconfiguredDomains) { - $mdInfo1 += "| $($domain.id) | $($domain.passwordValidityPeriodInDays) |`n" + $null = $mdInfo1.Append("| $($domain.id) | $($domain.passwordValidityPeriodInDays) |`n") } $testResultMarkdown = $testResultMarkdown + $mdInfo1 @@ -57,16 +57,16 @@ function Invoke-CippTestZTNA21811 { if ($misconfiguredUsers) { $reportTitle2 = 'Users with password expiration enabled' - $mdInfo2 = "`n## $reportTitle2`n`n" - $mdInfo2 += "| Display Name | User Principal Name | User Password Expiration setting | Domain Password Expiration setting |`n" - $mdInfo2 += "| :----------- | :------------------ | :------------------------------- | :--------------------------------- |`n" + $mdInfo2 = [System.Text.StringBuilder]::new("`n## $reportTitle2`n`n") + $null = $mdInfo2.Append("| Display Name | User Principal Name | User Password Expiration setting | Domain Password Expiration setting |`n") + $null = $mdInfo2.Append("| :----------- | :------------------ | :------------------------------- | :--------------------------------- |`n") foreach ($misconfiguredUser in $misconfiguredUsers) { $displayName = $misconfiguredUser.displayName $userPrincipalName = $misconfiguredUser.userPrincipalName $userPasswordExpiration = $misconfiguredUser.passwordPolicies $domainPasswordExpiration = $misconfiguredUser.DomainPasswordValidity - $mdInfo2 += "| $displayName | $userPrincipalName | $userPasswordExpiration | $domainPasswordExpiration |`n" + $null = $mdInfo2.Append("| $displayName | $userPrincipalName | $userPasswordExpiration | $domainPasswordExpiration |`n") } $testResultMarkdown = $testResultMarkdown + $mdInfo2 diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21812.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21812.ps1 index 5bd0291730a5..8b961abed443 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21812.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21812.ps1 @@ -19,16 +19,16 @@ function Invoke-CippTestZTNA21812 { $Passed = $GlobalAdmins.Count -le 5 if ($Passed) { - $ResultMarkdown = "Maximum number of Global Administrators doesn't exceed five users/service principals.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Maximum number of Global Administrators doesn't exceed five users/service principals.`n`n") } else { - $ResultMarkdown = "Maximum number of Global Administrators exceeds five users/service principals.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Maximum number of Global Administrators exceeds five users/service principals.`n`n") } if ($GlobalAdmins.Count -gt 0) { - $ResultMarkdown += "## Global Administrators`n`n" - $ResultMarkdown += "### Total number of Global Administrators: $($GlobalAdmins.Count)`n`n" - $ResultMarkdown += "| Display Name | Object Type | User Principal Name |`n" - $ResultMarkdown += "| :----------- | :---------- | :------------------ |`n" + $null = $ResultMarkdown.Append("## Global Administrators`n`n") + $null = $ResultMarkdown.Append("### Total number of Global Administrators: $($GlobalAdmins.Count)`n`n") + $null = $ResultMarkdown.Append("| Display Name | Object Type | User Principal Name |`n") + $null = $ResultMarkdown.Append("| :----------- | :---------- | :------------------ |`n") foreach ($GlobalAdmin in $GlobalAdmins) { $DisplayName = $GlobalAdmin.displayName @@ -45,7 +45,7 @@ function Invoke-CippTestZTNA21812 { default { 'https://entra.microsoft.com' } } - $ResultMarkdown += "| [$DisplayName]($PortalLink) | $ObjectType | $UserPrincipalName |`n" + $null = $ResultMarkdown.Append("| [$DisplayName]($PortalLink) | $ObjectType | $UserPrincipalName |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21813.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21813.ps1 index 600acbfc9ab4..9f3f82f2e05a 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21813.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21813.ps1 @@ -94,13 +94,13 @@ function Invoke-CippTestZTNA21813 { $HasHighRatio = $true } - $MdInfo = "`n## Privileged role assignment summary`n`n" - $MdInfo += "**Global administrator role count:** $GARoleAssignmentCount ($GAPercentage%) - $StatusIndicator`n`n" - $MdInfo += "**Other privileged role count:** $PrivilegedRoleAssignmentCount ($OtherPercentage%)`n`n" + $MdInfo = [System.Text.StringBuilder]::new("`n## Privileged role assignment summary`n`n") + $null = $MdInfo.Append("**Global administrator role count:** $GARoleAssignmentCount ($GAPercentage%) - $StatusIndicator`n`n") + $null = $MdInfo.Append("**Other privileged role count:** $PrivilegedRoleAssignmentCount ($OtherPercentage%)`n`n") - $MdInfo += "## User privileged role assignments`n`n" - $MdInfo += "| User | Global administrator | Other Privileged Role(s) |`n" - $MdInfo += "| :--- | :------------------- | :------ |`n" + $null = $MdInfo.Append("## User privileged role assignments`n`n") + $null = $MdInfo.Append("| User | Global administrator | Other Privileged Role(s) |`n") + $null = $MdInfo.Append("| :--- | :------------------- | :------ |`n") $SortedUsers = $UserRoleMap.Values | Sort-Object @{Expression = { -not $_.IsGA } }, @{Expression = { $_.User.displayName } } @@ -112,11 +112,11 @@ function Invoke-CippTestZTNA21813 { $RolesList = if ($OtherRoles.Count -gt 0) { ($OtherRoles -join ', ') } else { '-' } $UserLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/AdministrativeRole/userId/$($User.id)/hidePreviewBanner~/true" - $MdInfo += "| [$($User.displayName)]($UserLink) | $IsGA | $RolesList |`n" + $null = $MdInfo.Append("| [$($User.displayName)]($UserLink) | $IsGA | $RolesList |`n") } if ($UserRoleMap.Count -eq 0) { - $MdInfo += "| No privileged users found | - | - |`n" + $null = $MdInfo.Append("| No privileged users found | - | - |`n") } if ($TotalPrivilegedRoleAssignmentCount -eq 0) { diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21814.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21814.ps1 index 3e81eafb1b68..f297f694d2da 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21814.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21814.ps1 @@ -40,15 +40,15 @@ function Invoke-CippTestZTNA21814 { $Passed = $SyncedUsers.Count -eq 0 if ($Passed) { - $ResultMarkdown = "Validated that standing or eligible privileged accounts are cloud only accounts.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Validated that standing or eligible privileged accounts are cloud only accounts.`n`n") } else { - $ResultMarkdown = "This tenant has $($SyncedUsers.Count) privileged users that are synced from on-premise.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("This tenant has $($SyncedUsers.Count) privileged users that are synced from on-premise.`n`n") } if ($RoleData.Count -gt 0) { - $ResultMarkdown += "## Privileged Roles`n`n" - $ResultMarkdown += "| Role Name | User | Source | Status |`n" - $ResultMarkdown += "| :--- | :--- | :--- | :---: |`n" + $null = $ResultMarkdown.Append("## Privileged Roles`n`n") + $null = $ResultMarkdown.Append("| Role Name | User | Source | Status |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :--- | :---: |`n") foreach ($RoleUser in ($RoleData | Sort-Object RoleName, UserDisplayName)) { if ($RoleUser.OnPremisesSyncEnabled) { @@ -60,7 +60,7 @@ function Invoke-CippTestZTNA21814 { } $UserLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/AdministrativeRole/userId/$($RoleUser.UserId)" - $ResultMarkdown += "| $($RoleUser.RoleName) | [$($RoleUser.UserDisplayName)]($UserLink) | $Type | $Status |`n" + $null = $ResultMarkdown.Append("| $($RoleUser.RoleName) | [$($RoleUser.UserDisplayName)]($UserLink) | $Type | $Status |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21815.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21815.ps1 index c6b7cf55ccfc..be06391c03d0 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21815.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21815.ps1 @@ -41,17 +41,17 @@ function Invoke-CippTestZTNA21815 { if ($PermanentAssignments.Count -eq 0) { $Passed = $true - $ResultMarkdown = 'No privileged users have permanent role assignments.' + $ResultMarkdown = [System.Text.StringBuilder]::new('No privileged users have permanent role assignments.') } else { $Passed = $false - $ResultMarkdown = "Privileged users with permanent role assignments were found.`n`n" - $ResultMarkdown += "## Privileged users with permanent role assignments`n`n" - $ResultMarkdown += "| User | UPN | Role Name | Assignment Type |`n" - $ResultMarkdown += "| :--- | :-- | :-------- | :-------------- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Privileged users with permanent role assignments were found.`n`n") + $null = $ResultMarkdown.Append("## Privileged users with permanent role assignments`n`n") + $null = $ResultMarkdown.Append("| User | UPN | Role Name | Assignment Type |`n") + $null = $ResultMarkdown.Append("| :--- | :-- | :-------- | :-------------- |`n") foreach ($Result in $PermanentAssignments) { $PortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/AdministrativeRole/userId/$($Result.PrincipalId)/hidePreviewBanner~/true" - $ResultMarkdown += "| [$($Result.PrincipalDisplayName)]($PortalLink) | $($Result.UserPrincipalName) | $($Result.RoleDisplayName) | $($Result.PrivilegeType) |`n" + $null = $ResultMarkdown.Append("| [$($Result.PrincipalDisplayName)]($PortalLink) | $($Result.UserPrincipalName) | $($Result.RoleDisplayName) | $($Result.PrivilegeType) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21816.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21816.ps1 index d45786e122de..3705a2cb776e 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21816.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21816.ps1 @@ -24,18 +24,32 @@ function Invoke-CippTestZTNA21816 { $Users = Get-CIPPTestData -TenantFilter $Tenant -Type 'Users' $Groups = Get-CIPPTestData -TenantFilter $Tenant -Type 'Groups' - $EligibleGAs = $RoleEligibilitySchedules | Where-Object { $_.roleDefinitionId -eq $GlobalAdminRoleId } + $EligibleGAs = $RoleEligibilitySchedules.Where({ $_.roleDefinitionId -eq $GlobalAdminRoleId }) $EligibleGAUsers = 0 + # Build id-keyed lookups once to avoid O(N*M) Where-Object scans + $UsersById = @{} + foreach ($U in $Users) { $UsersById[$U.id] = $U } + $GroupsById = @{} + foreach ($G in $Groups) { $GroupsById[$G.id] = $G } + # Composite-key lookup: principalId|roleDefinitionId + $AssignmentByPrincipalRole = @{} + foreach ($A in $RoleAssignmentScheduleInstances) { + $key = '{0}|{1}' -f $A.principalId, $A.roleDefinitionId + if (-not $AssignmentByPrincipalRole.ContainsKey($key)) { + $AssignmentByPrincipalRole[$key] = $A + } + } + foreach ($EligibleGA in $EligibleGAs) { - $Principal = $Users | Where-Object { $_.id -eq $EligibleGA.principalId } | Select-Object -First 1 + $Principal = $UsersById[$EligibleGA.principalId] if ($Principal) { $EligibleGAUsers++ } else { - $GroupPrincipal = $Groups | Where-Object { $_.id -eq $EligibleGA.principalId } | Select-Object -First 1 - if ($GroupPrincipal) { - $GroupMembers = $Users | Where-Object { $_.id -in $GroupPrincipal.members } - $EligibleGAUsers = $EligibleGAUsers + $GroupMembers.Count + $GroupPrincipal = $GroupsById[$EligibleGA.principalId] + if ($GroupPrincipal -and $GroupPrincipal.members) { + $MemberSet = [System.Collections.Generic.HashSet[string]]::new([string[]]$GroupPrincipal.members) + foreach ($U in $Users) { if ($MemberSet.Contains($U.id)) { $EligibleGAUsers++ } } } } } @@ -46,9 +60,7 @@ function Invoke-CippTestZTNA21816 { $RoleMembers = Get-CippDbRoleMembers -TenantFilter $Tenant -RoleTemplateId $Role.RoletemplateId foreach ($Member in $RoleMembers) { - $Assignment = $RoleAssignmentScheduleInstances | Where-Object { - $_.principalId -eq $Member.id -and $_.roleDefinitionId -eq $Role.RoletemplateId - } | Select-Object -First 1 + $Assignment = $AssignmentByPrincipalRole['{0}|{1}' -f $Member.id, $Role.RoletemplateId] if (-not $Assignment -or ($Assignment.assignmentType -eq 'Assigned' -and $null -eq $Assignment.endDateTime)) { $MemberInfo = [PSCustomObject]@{ @@ -72,9 +84,7 @@ function Invoke-CippTestZTNA21816 { $GAMembers = Get-CippDbRoleMembers -TenantFilter $Tenant -RoleTemplateId $GlobalAdminRoleId foreach ($Member in $GAMembers) { - $Assignment = $RoleAssignmentScheduleInstances | Where-Object { - $_.principalId -eq $Member.id -and $_.roleDefinitionId -eq $GlobalAdminRoleId - } | Select-Object -First 1 + $Assignment = $AssignmentByPrincipalRole['{0}|{1}' -f $Member.id, $GlobalAdminRoleId] if (-not $Assignment -or ($Assignment.assignmentType -eq 'Assigned' -and $null -eq $Assignment.endDateTime)) { $MemberInfo = [PSCustomObject]@{ @@ -88,7 +98,7 @@ function Invoke-CippTestZTNA21816 { } if ($Member.'@odata.type' -eq '#microsoft.graph.user') { - $UserDetail = $Users | Where-Object { $_.id -eq $Member.id } | Select-Object -First 1 + $UserDetail = $UsersById[$Member.id] if ($UserDetail) { $MemberInfo.onPremisesSyncEnabled = $UserDetail.onPremisesSyncEnabled } @@ -96,10 +106,11 @@ function Invoke-CippTestZTNA21816 { } elseif ($Member.'@odata.type' -eq '#microsoft.graph.group') { $PermanentGAGroupList.Add($MemberInfo) - $Group = $Groups | Where-Object { $_.id -eq $Member.id } | Select-Object -First 1 - if ($Group) { - $GroupMembers = $Users | Where-Object { $_.id -in $Group.members } - foreach ($GroupMember in $GroupMembers) { + $Group = $GroupsById[$Member.id] + if ($Group -and $Group.members) { + $MemberSet = [System.Collections.Generic.HashSet[string]]::new([string[]]$Group.members) + foreach ($GroupMember in $Users) { + if (-not $MemberSet.Contains($GroupMember.id)) { continue } $GroupMemberInfo = [PSCustomObject]@{ displayName = $GroupMember.displayName userPrincipalName = $GroupMember.userPrincipalName @@ -123,53 +134,53 @@ function Invoke-CippTestZTNA21816 { if (-not $HasPIMUsage) { $Passed = $false - $ResultMarkdown = 'No eligible Global Administrator assignments found. PIM usage cannot be confirmed.' + $ResultMarkdown = [System.Text.StringBuilder]::new('No eligible Global Administrator assignments found. PIM usage cannot be confirmed.') } elseif ($HasNonPIMPrivileged) { $Passed = $false - $ResultMarkdown = 'Found Microsoft Entra privileged role assignments that are not managed with PIM.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Found Microsoft Entra privileged role assignments that are not managed with PIM.') } elseif ($PermanentGACount -gt 2) { $Passed = $false $CustomStatus = 'Investigate' - $ResultMarkdown = 'Three or more accounts are permanently assigned the Global Administrator role. Review to determine whether these are emergency access accounts.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Three or more accounts are permanently assigned the Global Administrator role. Review to determine whether these are emergency access accounts.') } else { $Passed = $true - $ResultMarkdown = 'All Microsoft Entra privileged role assignments are managed with PIM with the exception of up to two standing Global Administrator accounts.' + $ResultMarkdown = [System.Text.StringBuilder]::new('All Microsoft Entra privileged role assignments are managed with PIM with the exception of up to two standing Global Administrator accounts.') } - $ResultMarkdown += "`n`n## Assessment summary`n`n" - $ResultMarkdown += "| Metric | Count |`n" - $ResultMarkdown += "| :----- | :---- |`n" - $ResultMarkdown += "| Privileged roles found | $($PrivilegedRoles.Count) |`n" - $ResultMarkdown += "| Eligible Global Administrators | $EligibleGAUsers |`n" - $ResultMarkdown += "| Non-PIM privileged users | $($NonPIMPrivilegedUsers.Count) |`n" - $ResultMarkdown += "| Non-PIM privileged groups | $($NonPIMPrivilegedGroups.Count) |`n" - $ResultMarkdown += "| Permanent Global Administrator users | $($PermanentGAUserList.Count) |`n" + $null = $ResultMarkdown.Append("`n`n## Assessment summary`n`n") + $null = $ResultMarkdown.Append("| Metric | Count |`n") + $null = $ResultMarkdown.Append("| :----- | :---- |`n") + $null = $ResultMarkdown.Append("| Privileged roles found | $($PrivilegedRoles.Count) |`n") + $null = $ResultMarkdown.Append("| Eligible Global Administrators | $EligibleGAUsers |`n") + $null = $ResultMarkdown.Append("| Non-PIM privileged users | $($NonPIMPrivilegedUsers.Count) |`n") + $null = $ResultMarkdown.Append("| Non-PIM privileged groups | $($NonPIMPrivilegedGroups.Count) |`n") + $null = $ResultMarkdown.Append("| Permanent Global Administrator users | $($PermanentGAUserList.Count) |`n") if ($NonPIMPrivilegedUsers.Count -gt 0 -or $NonPIMPrivilegedGroups.Count -gt 0) { - $ResultMarkdown += "`n## Non-PIM managed privileged role assignments`n`n" - $ResultMarkdown += "| Display name | User principal name | Role name | Assignment type |`n" - $ResultMarkdown += "| :----------- | :------------------ | :-------- | :-------------- |`n" + $null = $ResultMarkdown.Append("`n## Non-PIM managed privileged role assignments`n`n") + $null = $ResultMarkdown.Append("| Display name | User principal name | Role name | Assignment type |`n") + $null = $ResultMarkdown.Append("| :----------- | :------------------ | :-------- | :-------------- |`n") foreach ($User in $NonPIMPrivilegedUsers) { $UserLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/AdministrativeRole/userId/$($User.id)/hidePreviewBanner~/true" - $ResultMarkdown += "| [$($User.displayName)]($UserLink) | $($User.userPrincipalName) | $($User.roleName) | $($User.assignmentType) |`n" + $null = $ResultMarkdown.Append("| [$($User.displayName)]($UserLink) | $($User.userPrincipalName) | $($User.roleName) | $($User.assignmentType) |`n") } foreach ($Group in $NonPIMPrivilegedGroups) { $GroupLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/GroupDetailsMenuBlade/~/RolesAndAdministrators/groupId/$($Group.id)/menuId/" - $ResultMarkdown += "| [$($Group.displayName)]($GroupLink) | N/A (Group) | $($Group.roleName) | $($Group.assignmentType) |`n" + $null = $ResultMarkdown.Append("| [$($Group.displayName)]($GroupLink) | N/A (Group) | $($Group.roleName) | $($Group.assignmentType) |`n") } } if ($PermanentGAUserList.Count -gt 0) { - $ResultMarkdown += "`n## Permanent Global Administrator assignments`n`n" - $ResultMarkdown += "| Display name | User principal name | Assignment type | On-Premises synced |`n" - $ResultMarkdown += "| :----------- | :------------------ | :-------------- | :----------------- |`n" + $null = $ResultMarkdown.Append("`n## Permanent Global Administrator assignments`n`n") + $null = $ResultMarkdown.Append("| Display name | User principal name | Assignment type | On-Premises synced |`n") + $null = $ResultMarkdown.Append("| :----------- | :------------------ | :-------------- | :----------------- |`n") foreach ($User in $PermanentGAUserList) { $SyncStatus = if ($null -ne $User.onPremisesSyncEnabled) { $User.onPremisesSyncEnabled } else { 'N/A' } $UserLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/AdministrativeRole/userId/$($User.id)/hidePreviewBanner~/true" - $ResultMarkdown += "| [$($User.displayName)]($UserLink) | $($User.userPrincipalName) | $($User.assignmentType) | $SyncStatus |`n" + $null = $ResultMarkdown.Append("| [$($User.displayName)]($UserLink) | $($User.userPrincipalName) | $($User.assignmentType) | $SyncStatus |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21818.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21818.ps1 index a23c36de9b9a..1ebe2fdddbc7 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21818.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21818.ps1 @@ -93,19 +93,19 @@ function Invoke-CippTestZTNA21818 { } if ($Passed) { - $ResultMarkdown = "Role notifications are properly configured for privileged role.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Role notifications are properly configured for privileged role.`n`n") } else { - $ResultMarkdown = "Role notifications are not properly configured.`n`nNote: To save time, this check stops when it finds the first role that does not have notifications. After fixing this role and all other roles, we recommend running the check again to verify.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Role notifications are not properly configured.`n`nNote: To save time, this check stops when it finds the first role that does not have notifications. After fixing this role and all other roles, we recommend running the check again to verify.`n`n") } - $ResultMarkdown += "## Notifications for high privileged roles`n`n" - $ResultMarkdown += "| Role Name | Notification Scenario | Notification Type | Default Recipients Enabled | Additional Recipients |`n" - $ResultMarkdown += "| :-------- | :-------------------- | :---------------- | :------------------------- | :-------------------- |`n" + $null = $ResultMarkdown.Append("## Notifications for high privileged roles`n`n") + $null = $ResultMarkdown.Append("| Role Name | Notification Scenario | Notification Type | Default Recipients Enabled | Additional Recipients |`n") + $null = $ResultMarkdown.Append("| :-------- | :-------------------- | :---------------- | :------------------------- | :-------------------- |`n") foreach ($NotificationRule in $NotificationRules) { $MatchingNotification = $Notifications | Where-Object { $_.RuleId -eq $NotificationRule.id } $Recipients = if ($NotificationRule.notificationRecipients) { ($NotificationRule.notificationRecipients -join ', ') } else { '' } - $ResultMarkdown += "| $($NotificationRule.roleDisplayName) | $($MatchingNotification.notificationScenario) | $($MatchingNotification.notificationType) | $($NotificationRule.isDefaultRecipientsEnabled) | $Recipients |`n" + $null = $ResultMarkdown.Append("| $($NotificationRule.roleDisplayName) | $($MatchingNotification.notificationScenario) | $($MatchingNotification.notificationType) | $($NotificationRule.isDefaultRecipientsEnabled) | $Recipients |`n") } $Status = if ($Passed) { 'Passed' } else { 'Failed' } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21819.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21819.ps1 index f6f5a85e3d82..af4ca97404ec 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21819.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21819.ps1 @@ -48,13 +48,13 @@ function Invoke-CippTestZTNA21819 { } if ($Passed -eq 'Passed') { - $ResultMarkdown = "Activation alerts are configured for Global Administrator role.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Activation alerts are configured for Global Administrator role.`n`n") } else { - $ResultMarkdown = "Activation alerts are missing or improperly configured for Global Administrator role.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Activation alerts are missing or improperly configured for Global Administrator role.`n`n") } - $ResultMarkdown += "| Role display name | Default recipients | Additional recipients |`n" - $ResultMarkdown += "| :---------------- | :----------------- | :------------------- |`n" + $null = $ResultMarkdown.Append("| Role display name | Default recipients | Additional recipients |`n") + $null = $ResultMarkdown.Append("| :---------------- | :----------------- | :------------------- |`n") $RoleLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_IAM/RolesManagementMenuBlade/~/AllRoles' $DisplayNameLink = "[$($GlobalAdminRole.displayName)]($RoleLink)" @@ -73,7 +73,7 @@ function Invoke-CippTestZTNA21819 { $Recipients } - $ResultMarkdown += "| $DisplayNameLink | $DefaultRecipientsStatus | $RecipientsDisplay |`n" + $null = $ResultMarkdown.Append("| $DisplayNameLink | $DefaultRecipientsStatus | $RecipientsDisplay |`n") Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'Low' -Name 'Activation alert for Global Administrator role assignment' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Privileged access' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21820.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21820.ps1 index e1715d6d8ff6..e4afd33d4392 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21820.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21820.ps1 @@ -71,15 +71,15 @@ function Invoke-CippTestZTNA21820 { } if ($RolesWithIssues.Count -eq 0) { - $ResultMarkdown = 'Activation alerts are configured for privileged role assignments.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Activation alerts are configured for privileged role assignments.') } else { - $ResultMarkdown = 'Activation alerts are missing or improperly configured for privileged roles.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Activation alerts are missing or improperly configured for privileged roles.') } if ($RolesWithIssues.Count -gt 0) { - $ResultMarkdown += "`n`n## Roles with missing or misconfigured alerts`n`n" - $ResultMarkdown += "| Role display name | Default recipients | Additional recipients |`n" - $ResultMarkdown += "| :---------------- | :----------------- | :------------------- |`n" + $null = $ResultMarkdown.Append("`n`n## Roles with missing or misconfigured alerts`n`n") + $null = $ResultMarkdown.Append("| Role display name | Default recipients | Additional recipients |`n") + $null = $ResultMarkdown.Append("| :---------------- | :----------------- | :------------------- |`n") foreach ($RoleIssue in $RolesWithIssues) { $Role = $RoleIssue.Role @@ -93,9 +93,9 @@ function Invoke-CippTestZTNA21820 { } $Recipients = $RoleIssue.NotificationRecipients - $ResultMarkdown += "| $DisplayNameLink | $DefaultRecipientsStatus | $Recipients |`n" + $null = $ResultMarkdown.Append("| $DisplayNameLink | $DefaultRecipientsStatus | $Recipients |`n") } - $ResultMarkdown += "`n`n*Not all misconfigured roles may be listed. For performance reasons, this assessment stops at the first detected issue.*`n" + $null = $ResultMarkdown.Append("`n`n*Not all misconfigured roles may be listed. For performance reasons, this assessment stops at the first detected issue.*`n") } Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'Low' -Name 'Activation alert for all privileged role assignments' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Privileged access' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21822.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21822.ps1 index 3fa2fa43af58..5df414fc8046 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21822.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21822.ps1 @@ -26,34 +26,34 @@ function Invoke-CippTestZTNA21822 { } if ($Passed -eq 'Passed') { - $ResultMarkdown = "Guest access is limited to approved tenants.`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Guest access is limited to approved tenants.`n") } else { - $ResultMarkdown = "Guest access is not limited to approved tenants.`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Guest access is not limited to approved tenants.`n") } - $ResultMarkdown += "`n`n## [Collaboration restrictions](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/CompanyRelationshipsMenuBlade/~/Settings/menuId/)`n`n" - $ResultMarkdown += 'The tenant is configured to: ' + $null = $ResultMarkdown.Append("`n`n## [Collaboration restrictions](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/CompanyRelationshipsMenuBlade/~/Settings/menuId/)`n`n") + $null = $ResultMarkdown.Append('The tenant is configured to: ') if ($Passed -eq 'Passed') { - $ResultMarkdown += "**Allow invitations only to the specified domains (most restrictive)** ✅`n" + $null = $ResultMarkdown.Append("**Allow invitations only to the specified domains (most restrictive)** ✅`n") } else { if ($BlockedDomains -and $BlockedDomains.Count -gt 0) { - $ResultMarkdown += "**Deny invitations to the specified domains** ❌`n" + $null = $ResultMarkdown.Append("**Deny invitations to the specified domains** ❌`n") } else { - $ResultMarkdown += "**Allow invitations to be sent to any domain (most inclusive)** ❌`n" + $null = $ResultMarkdown.Append("**Allow invitations to be sent to any domain (most inclusive)** ❌`n") } } if (($AllowedDomains -and $AllowedDomains.Count -gt 0) -or ($BlockedDomains -and $BlockedDomains.Count -gt 0)) { - $ResultMarkdown += "| Domain | Status |`n" - $ResultMarkdown += "| :--- | :--- |`n" + $null = $ResultMarkdown.Append("| Domain | Status |`n") + $null = $ResultMarkdown.Append("| :--- | :--- |`n") foreach ($Domain in $AllowedDomains) { - $ResultMarkdown += "| $Domain | ✅ Allowed |`n" + $null = $ResultMarkdown.Append("| $Domain | ✅ Allowed |`n") } foreach ($Domain in $BlockedDomains) { - $ResultMarkdown += "| $Domain | ❌ Blocked |`n" + $null = $ResultMarkdown.Append("| $Domain | ❌ Blocked |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21824.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21824.ps1 index c2c731d70cb6..6e88785cf1f7 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21824.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21824.ps1 @@ -41,9 +41,9 @@ function Invoke-CippTestZTNA21824 { $reportTitle = 'Sign-in frequency policies' if ($filteredCAPolicies -and $filteredCAPolicies.Count -gt 0) { - $mdInfo = "`n## $reportTitle`n`n" - $mdInfo += "| Policy Name | Sign-in Frequency | Status |`n" - $mdInfo += "| :---------- | :---------------- | :----- |`n" + $mdInfo = [System.Text.StringBuilder]::new("`n## $reportTitle`n`n") + $null = $mdInfo.Append("| Policy Name | Sign-in Frequency | Status |`n") + $null = $mdInfo.Append("| :---------- | :---------------- | :----- |`n") foreach ($filteredCAPolicy in $filteredCAPolicies) { $policyName = $filteredCAPolicy.DisplayName @@ -71,7 +71,7 @@ function Invoke-CippTestZTNA21824 { '❌' } - $mdInfo += "| $policyName | $signInFreqValue | $status |`n" + $null = $mdInfo.Append("| $policyName | $signInFreqValue | $status |`n") } $testResultMarkdown = $testResultMarkdown + $mdInfo diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21825.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21825.ps1 index 635ce60eca4c..be0a438023a5 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21825.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21825.ps1 @@ -27,16 +27,16 @@ function Invoke-CippTestZTNA21825 { # Recommended: Sign-in frequency should be 4 hours or less for privileged users $RecommendedMaxHours = 4 - $ResultMarkdown = "## Privileged User Sign-In Sessions`n`n" - $ResultMarkdown += "**Total Privileged Roles Found:** $($PrivilegedRoles.Count)`n`n" - $ResultMarkdown += "**CA Policies Targeting Roles:** $($RoleScopedPolicies.Count)`n`n" - $ResultMarkdown += "**Recommended Sign In Session Hours:** $RecommendedMaxHours`n`n" - $ResultMarkdown += "### Conditional Access Policies by Privileged Role`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("## Privileged User Sign-In Sessions`n`n") + $null = $ResultMarkdown.Append("**Total Privileged Roles Found:** $($PrivilegedRoles.Count)`n`n") + $null = $ResultMarkdown.Append("**CA Policies Targeting Roles:** $($RoleScopedPolicies.Count)`n`n") + $null = $ResultMarkdown.Append("**Recommended Sign In Session Hours:** $RecommendedMaxHours`n`n") + $null = $ResultMarkdown.Append("### Conditional Access Policies by Privileged Role`n`n") $AllRolesCovered = $true foreach ($Role in $PrivilegedRoles) { - $ResultMarkdown += "#### $($Role.displayName)`n`n" + $null = $ResultMarkdown.Append("#### $($Role.displayName)`n`n") # Get CA policies assigned to this role $AssignedPolicies = $CAPolicies | Where-Object { $_.conditions.users.includeRoles -contains $Role.id } @@ -55,10 +55,10 @@ function Invoke-CippTestZTNA21825 { } else { '❌ Not Covered'; $AllRolesCovered = $false } - $ResultMarkdown += "**Status:** $RoleStatus`n`n" + $null = $ResultMarkdown.Append("**Status:** $RoleStatus`n`n") - $ResultMarkdown += "| Policy Name | Sign-In Frequency | Compliant |`n" - $ResultMarkdown += "| :--- | :--- | :--- |`n" + $null = $ResultMarkdown.Append("| Policy Name | Sign-In Frequency | Compliant |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :--- |`n") foreach ($Policy in $EnabledPolicies) { $FreqValue = 'Not Configured' @@ -78,12 +78,12 @@ function Invoke-CippTestZTNA21825 { } $PolicyLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($Policy.id)" - $ResultMarkdown += "| [$($Policy.displayName)]($PolicyLink) | $FreqValue | $IsCompliant |`n" + $null = $ResultMarkdown.Append("| [$($Policy.displayName)]($PolicyLink) | $FreqValue | $IsCompliant |`n") } - $ResultMarkdown += "`n" + $null = $ResultMarkdown.Append("`n") } else { - $ResultMarkdown += "**Status:** ❌ No CA policies assigned`n`n" - $ResultMarkdown += "*No Conditional Access policies target this privileged role.*`n`n" + $null = $ResultMarkdown.Append("**Status:** ❌ No CA policies assigned`n`n") + $null = $ResultMarkdown.Append("*No Conditional Access policies target this privileged role.*`n`n") $AllRolesCovered = $false } } @@ -91,10 +91,10 @@ function Invoke-CippTestZTNA21825 { $Passed = if ($AllRolesCovered -and $PrivilegedRoles.Count -gt 0) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown += "✅ **All privileged roles are covered by enabled policies enforcing short-lived sessions (≤$RecommendedMaxHours hours).**`n" + $null = $ResultMarkdown.Append("✅ **All privileged roles are covered by enabled policies enforcing short-lived sessions (≤$RecommendedMaxHours hours).**`n") } else { - $ResultMarkdown += "❌ **Not all privileged roles are covered by compliant sign-in frequency controls.**`n" - $ResultMarkdown += "`n**Recommendation:** Configure Conditional Access policies to enforce sign-in frequency of $RecommendedMaxHours hours or less for ALL privileged roles.`n" + $null = $ResultMarkdown.Append("❌ **Not all privileged roles are covered by compliant sign-in frequency controls.**`n") + $null = $ResultMarkdown.Append("`n**Recommendation:** Configure Conditional Access policies to enforce sign-in frequency of $RecommendedMaxHours hours or less for ALL privileged roles.`n") } Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'Medium' -Name 'Privileged users have short-lived sign-in sessions' -UserImpact 'Medium' -ImplementationEffort 'Low' -Category 'Access control' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21828.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21828.ps1 index 78432f754cf2..c29e1c8712a7 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21828.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21828.ps1 @@ -32,14 +32,14 @@ function Invoke-CippTestZTNA21828 { $reportTitle = 'Conditional Access Policies targeting Authentication Transfer' if ($matchedPolicies.Count -gt 0) { - $mdInfo = "`n## $reportTitle`n`n" - $mdInfo += "| Policy Name | Policy ID | State | Created | Modified |`n" - $mdInfo += "| :---------- | :-------- | :---- | :------ | :------- |`n" + $mdInfo = [System.Text.StringBuilder]::new("`n## $reportTitle`n`n") + $null = $mdInfo.Append("| Policy Name | Policy ID | State | Created | Modified |`n") + $null = $mdInfo.Append("| :---------- | :-------- | :---- | :------ | :------- |`n") foreach ($policy in $matchedPolicies) { $created = if ($policy.createdDateTime) { $policy.createdDateTime } else { 'N/A' } $modified = if ($policy.modifiedDateTime) { $policy.modifiedDateTime } else { 'N/A' } - $mdInfo += "| $($policy.displayName) | $($policy.id) | $($policy.state) | $created | $modified |`n" + $null = $mdInfo.Append("| $($policy.displayName) | $($policy.id) | $($policy.state) | $created | $modified |`n") } $testResultMarkdown = $testResultMarkdown + $mdInfo diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21829.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21829.ps1 index 134741579838..661089db8794 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21829.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21829.ps1 @@ -20,15 +20,15 @@ function Invoke-CippTestZTNA21829 { $Passed = if ($FederatedDomains.Count -eq 0) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = "All domains are using cloud authentication.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("All domains are using cloud authentication.`n`n") } else { - $ResultMarkdown = "Federated authentication is in use.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Federated authentication is in use.`n`n") - $ResultMarkdown += "`n## List of federated domains`n`n" - $ResultMarkdown += "| Domain Name |`n" - $ResultMarkdown += "| :--- |`n" + $null = $ResultMarkdown.Append("`n## List of federated domains`n`n") + $null = $ResultMarkdown.Append("| Domain Name |`n") + $null = $ResultMarkdown.Append("| :--- |`n") foreach ($Domain in $FederatedDomains) { - $ResultMarkdown += "| $($Domain.id) |`n" + $null = $ResultMarkdown.Append("| $($Domain.id) |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21830.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21830.ps1 index a37e509cc499..d35d77867c04 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21830.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21830.ps1 @@ -56,24 +56,24 @@ function Invoke-CippTestZTNA21830 { $Passed = if ($CompliantDevicePolicies.Count -eq 0 -or $DeviceFilterPolicies.Count -eq 0) { 'Failed' } else { 'Passed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = 'Conditional Access policies restrict privileged role access to PAW devices.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Conditional Access policies restrict privileged role access to PAW devices.') } else { - $ResultMarkdown = 'No Conditional Access policies found that restrict privileged roles to PAW device.' + $ResultMarkdown = [System.Text.StringBuilder]::new('No Conditional Access policies found that restrict privileged roles to PAW device.') } $CompliantDeviceMarkdown = if ($CompliantDevicePolicies.Count -gt 0) { '✅' } else { '❌' } $DeviceFilterMarkdown = if ($DeviceFilterPolicies.Count -gt 0) { '✅' } else { '❌' } - $ResultMarkdown += "`n`n**$CompliantDeviceMarkdown Found $($CompliantDevicePolicies.Count) policy(s) with compliant device control targeting all privileged roles**`n" + $null = $ResultMarkdown.Append("`n`n**$CompliantDeviceMarkdown Found $($CompliantDevicePolicies.Count) policy(s) with compliant device control targeting all privileged roles**`n") foreach ($Policy in $CompliantDevicePolicies) { $PortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($Policy.id)" - $ResultMarkdown += "- **Policy:** [$($Policy.displayName)]($PortalLink)`n" + $null = $ResultMarkdown.Append("- **Policy:** [$($Policy.displayName)]($PortalLink)`n") } - $ResultMarkdown += "`n`n**$DeviceFilterMarkdown Found $($DeviceFilterPolicies.Count) policy(s) with PAW/SAW device filter targeting all privileged roles**`n" + $null = $ResultMarkdown.Append("`n`n**$DeviceFilterMarkdown Found $($DeviceFilterPolicies.Count) policy(s) with PAW/SAW device filter targeting all privileged roles**`n") foreach ($Policy in $DeviceFilterPolicies) { $PortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($Policy.id)" - $ResultMarkdown += "- **Policy:** [$($Policy.displayName)]($PortalLink)`n" + $null = $ResultMarkdown.Append("- **Policy:** [$($Policy.displayName)]($PortalLink)`n") } Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'High' -Name 'Conditional Access policies for Privileged Access Workstations are configured' -UserImpact 'Low' -ImplementationEffort 'High' -Category 'Application management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21835.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21835.ps1 index d30efa2e7327..0fa5d363f333 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21835.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21835.ps1 @@ -124,27 +124,27 @@ function Invoke-CippTestZTNA21835 { $AccountCount = $EmergencyAccessAccounts.Count $Passed = 'Failed' - $ResultMarkdown = '' + $ResultMarkdown = [System.Text.StringBuilder]::new() if ($AccountCount -lt 2) { - $ResultMarkdown = "Fewer than two emergency access accounts were identified based on cloud-only state, registered phishing-resistant credentials and Conditional Access policy exclusions.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Fewer than two emergency access accounts were identified based on cloud-only state, registered phishing-resistant credentials and Conditional Access policy exclusions.`n`n") } elseif ($AccountCount -ge 2 -and $AccountCount -le 4) { $Passed = 'Passed' - $ResultMarkdown = "Emergency access accounts appear to be configured as per Microsoft guidance based on cloud-only state, registered phishing-resistant credentials and Conditional Access policy exclusions.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Emergency access accounts appear to be configured as per Microsoft guidance based on cloud-only state, registered phishing-resistant credentials and Conditional Access policy exclusions.`n`n") } else { - $ResultMarkdown = "$AccountCount emergency access accounts appear to be configured based on cloud-only state, registered phishing-resistant credentials and Conditional Access policy exclusions. Review these accounts to determine whether this volume is excessive for your organization.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("$AccountCount emergency access accounts appear to be configured based on cloud-only state, registered phishing-resistant credentials and Conditional Access policy exclusions. Review these accounts to determine whether this volume is excessive for your organization.`n`n") } - $ResultMarkdown += "**Summary:**`n" - $ResultMarkdown += "- Total permanent Global Administrators: $($PermanentGAMembers.Count)`n" - $ResultMarkdown += "- Cloud-only GAs with phishing-resistant auth: $($EmergencyAccountCandidates.Count)`n" - $ResultMarkdown += "- Emergency access accounts (excluded from all CA): $AccountCount`n" - $ResultMarkdown += "- Enabled Conditional Access policies: $($EnabledCAPolicies.Count)`n`n" + $null = $ResultMarkdown.Append("**Summary:**`n") + $null = $ResultMarkdown.Append("- Total permanent Global Administrators: $($PermanentGAMembers.Count)`n") + $null = $ResultMarkdown.Append("- Cloud-only GAs with phishing-resistant auth: $($EmergencyAccountCandidates.Count)`n") + $null = $ResultMarkdown.Append("- Emergency access accounts (excluded from all CA): $AccountCount`n") + $null = $ResultMarkdown.Append("- Enabled Conditional Access policies: $($EnabledCAPolicies.Count)`n`n") if ($EmergencyAccessAccounts.Count -gt 0) { - $ResultMarkdown += "## Emergency access accounts`n`n" - $ResultMarkdown += "| Display name | UPN | Synced from on-premises | Authentication methods |`n" - $ResultMarkdown += "| :----------- | :-- | :---------------------- | :--------------------- |`n" + $null = $ResultMarkdown.Append("## Emergency access accounts`n`n") + $null = $ResultMarkdown.Append("| Display name | UPN | Synced from on-premises | Authentication methods |`n") + $null = $ResultMarkdown.Append("| :----------- | :-- | :---------------------- | :--------------------- |`n") foreach ($Account in $EmergencyAccessAccounts) { $SyncStatus = if ($Account.OnPremisesSyncEnabled -ne $true) { 'No' } else { 'Yes' } @@ -153,15 +153,15 @@ function Invoke-CippTestZTNA21835 { }) -join ', ' $PortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/overview/userId/$($Account.Id)" - $ResultMarkdown += "| $($Account.DisplayName) | [$($Account.UserPrincipalName)]($PortalLink) | $SyncStatus | $AuthMethodDisplay |`n" + $null = $ResultMarkdown.Append("| $($Account.DisplayName) | [$($Account.UserPrincipalName)]($PortalLink) | $SyncStatus | $AuthMethodDisplay |`n") } - $ResultMarkdown += "`n" + $null = $ResultMarkdown.Append("`n") } if ($PermanentGAMembers.Count -gt 0) { - $ResultMarkdown += "## All permanent Global Administrators`n`n" - $ResultMarkdown += "| Display name | UPN | Cloud only | All CA excluded | Phishing resistant auth |`n" - $ResultMarkdown += "| :----------- | :-- | :--------: | :---------: | :---------------------: |`n" + $null = $ResultMarkdown.Append("## All permanent Global Administrators`n`n") + $null = $ResultMarkdown.Append("| Display name | UPN | Cloud only | All CA excluded | Phishing resistant auth |`n") + $null = $ResultMarkdown.Append("| :----------- | :-- | :--------: | :---------: | :---------------------: |`n") $UserSummary = [System.Collections.Generic.List[object]]::new() foreach ($Member in $PermanentGAMembers) { @@ -189,10 +189,10 @@ function Invoke-CippTestZTNA21835 { } foreach ($UserSum in $UserSummary) { - $ResultMarkdown += "| $($UserSum.DisplayName) | [$($UserSum.UserPrincipalName)]($($UserSum.PortalLink)) | $($UserSum.CloudOnly) | $($UserSum.CAExcluded) | $($UserSum.PhishingResistant) |`n" + $null = $ResultMarkdown.Append("| $($UserSum.DisplayName) | [$($UserSum.UserPrincipalName)]($($UserSum.PortalLink)) | $($UserSum.CloudOnly) | $($UserSum.CAExcluded) | $($UserSum.PhishingResistant) |`n") } - $ResultMarkdown += "`n" + $null = $ResultMarkdown.Append("`n") } Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'High' -Name 'Emergency access accounts are configured appropriately' -UserImpact 'Low' -ImplementationEffort 'High' -Category 'Application management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21836.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21836.ps1 index 8253e3737d3d..f7c37115662a 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21836.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21836.ps1 @@ -37,24 +37,24 @@ function Invoke-CippTestZTNA21836 { } $Passed = 'Passed' - $ResultMarkdown = '' + $ResultMarkdown = [System.Text.StringBuilder]::new() if ($WorkloadIdentitiesWithPrivilegedRoles.Count -gt 0) { $Passed = 'Failed' - $ResultMarkdown = "**Found workload identities assigned to privileged roles.**`n" - $ResultMarkdown += "| Service Principal Name | Privileged Role | Assignment Type |`n" - $ResultMarkdown += "| :--- | :--- | :--- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("**Found workload identities assigned to privileged roles.**`n") + $null = $ResultMarkdown.Append("| Service Principal Name | Privileged Role | Assignment Type |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :--- |`n") $SortedAssignments = $WorkloadIdentitiesWithPrivilegedRoles | Sort-Object -Property PrincipalDisplayName foreach ($Assignment in $SortedAssignments) { $SPLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/Overview/objectId/$($Assignment.PrincipalId)/appId/$($Assignment.AppId)/preferredSingleSignOnMode~/null/servicePrincipalType/Application/fromNav/" - $ResultMarkdown += "| [$($Assignment.PrincipalDisplayName)]($SPLink) | $($Assignment.RoleDisplayName) | $($Assignment.AssignmentType) |`n" + $null = $ResultMarkdown.Append("| [$($Assignment.PrincipalDisplayName)]($SPLink) | $($Assignment.RoleDisplayName) | $($Assignment.AssignmentType) |`n") } - $ResultMarkdown += "`n" - $ResultMarkdown += "`n**Recommendation:** Review and remove privileged role assignments from workload identities unless absolutely necessary. Use least-privilege principles and consider alternative approaches like managed identities with specific API permissions instead of directory roles.`n" + $null = $ResultMarkdown.Append("`n") + $null = $ResultMarkdown.Append("`n**Recommendation:** Review and remove privileged role assignments from workload identities unless absolutely necessary. Use least-privilege principles and consider alternative approaches like managed identities with specific API permissions instead of directory roles.`n") } else { - $ResultMarkdown = "✅ **No workload identities found with privileged role assignments.**`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **No workload identities found with privileged role assignments.**`n") } Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'High' -Name 'Workload Identities are not assigned privileged roles' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Application management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21838.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21838.ps1 index f34f45450fc9..e1088f360404 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21838.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21838.ps1 @@ -28,28 +28,28 @@ function Invoke-CippTestZTNA21838 { $StatusEmoji = if ($Fido2Enabled) { '✅' } else { '❌' } if ($Fido2Enabled) { - $ResultMarkdown = "Security key authentication method is enabled for your tenant, providing hardware-backed phishing-resistant authentication.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Security key authentication method is enabled for your tenant, providing hardware-backed phishing-resistant authentication.`n`n") } else { - $ResultMarkdown = "Security key authentication method is not enabled; users cannot register FIDO2 security keys for strong authentication.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Security key authentication method is not enabled; users cannot register FIDO2 security keys for strong authentication.`n`n") } - $ResultMarkdown += "## FIDO2 security key authentication settings`n`n" - $ResultMarkdown += "$StatusEmoji **FIDO2 authentication method**`n" - $ResultMarkdown += "- Status: $($Fido2Config.state)`n" + $null = $ResultMarkdown.Append("## FIDO2 security key authentication settings`n`n") + $null = $ResultMarkdown.Append("$StatusEmoji **FIDO2 authentication method**`n") + $null = $ResultMarkdown.Append("- Status: $($Fido2Config.state)`n") $IncludeTargetsDisplay = if ($Fido2Config.includeTargets -and $Fido2Config.includeTargets.Count -gt 0) { ($Fido2Config.includeTargets | ForEach-Object { if ($_.id -eq 'all_users') { 'All users' } else { $_.id } }) -join ', ' } else { 'None' } - $ResultMarkdown += "- Include targets: $IncludeTargetsDisplay`n" + $null = $ResultMarkdown.Append("- Include targets: $IncludeTargetsDisplay`n") $ExcludeTargetsDisplay = if ($Fido2Config.excludeTargets -and $Fido2Config.excludeTargets.Count -gt 0) { ($Fido2Config.excludeTargets | ForEach-Object { $_.id }) -join ', ' } else { 'None' } - $ResultMarkdown += "- Exclude targets: $ExcludeTargetsDisplay`n" + $null = $ResultMarkdown.Append("- Exclude targets: $ExcludeTargetsDisplay`n") Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'High' -Name 'Security key authentication method enabled' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Access control' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21839.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21839.ps1 index 8d65a5c8dc73..43b19b3b4a6a 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21839.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21839.ps1 @@ -33,35 +33,35 @@ function Invoke-CippTestZTNA21839 { $PortalLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/~/AdminAuthMethods' - $ResultMarkdown = "`n## [Passkey authentication method details]($PortalLink)`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("`n## [Passkey authentication method details]($PortalLink)`n") $StatusDisplay = if ($Fido2Enabled) { 'Enabled ✅' } else { 'Disabled ❌' } - $ResultMarkdown += "- **Status** : $StatusDisplay`n" + $null = $ResultMarkdown.Append("- **Status** : $StatusDisplay`n") if ($Fido2Enabled) { - $ResultMarkdown += '- **Include targets** : ' + $null = $ResultMarkdown.Append('- **Include targets** : ') if ($IncludeTargets) { $TargetsDisplay = ($IncludeTargets | ForEach-Object { if ($_.id -eq 'all_users') { 'All users' } else { $_.id } }) -join ', ' - $ResultMarkdown += "$TargetsDisplay`n" + $null = $ResultMarkdown.Append("$TargetsDisplay`n") } else { - $ResultMarkdown += "None`n" + $null = $ResultMarkdown.Append("None`n") } - $ResultMarkdown += "- **Enforce attestation** : $IsAttestationEnforced`n" + $null = $ResultMarkdown.Append("- **Enforce attestation** : $IsAttestationEnforced`n") if ($KeyRestrictions) { - $ResultMarkdown += "- **Key restriction policy** :`n" + $null = $ResultMarkdown.Append("- **Key restriction policy** :`n") if ($null -ne $KeyRestrictions.isEnforced) { - $ResultMarkdown += " - **Enforce key restrictions** : $($KeyRestrictions.isEnforced)`n" + $null = $ResultMarkdown.Append(" - **Enforce key restrictions** : $($KeyRestrictions.isEnforced)`n") } else { - $ResultMarkdown += " - **Enforce key restrictions** : Not configured`n" + $null = $ResultMarkdown.Append(" - **Enforce key restrictions** : Not configured`n") } if ($KeyRestrictions.enforcementType) { - $ResultMarkdown += " - **Restrict specific keys** : $($KeyRestrictions.enforcementType)`n" + $null = $ResultMarkdown.Append(" - **Restrict specific keys** : $($KeyRestrictions.enforcementType)`n") } else { - $ResultMarkdown += " - **Restrict specific keys** : Not configured`n" + $null = $ResultMarkdown.Append(" - **Restrict specific keys** : Not configured`n") } } } @@ -69,9 +69,9 @@ function Invoke-CippTestZTNA21839 { $Passed = if ($Fido2Enabled -and $HasIncludeTargets) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = "Passkey authentication method is enabled and configured for users in your tenant.$ResultMarkdown" + $ResultMarkdown = [System.Text.StringBuilder]::new("Passkey authentication method is enabled and configured for users in your tenant.$ResultMarkdown") } else { - $ResultMarkdown = "Passkey authentication method is not enabled or not configured for any users in your tenant.$ResultMarkdown" + $ResultMarkdown = [System.Text.StringBuilder]::new("Passkey authentication method is not enabled or not configured for any users in your tenant.$ResultMarkdown") } Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'High' -Name 'Passkey authentication method enabled' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Credential management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21840.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21840.ps1 index 70c7f74f8ca7..d7e5de935eef 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21840.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21840.ps1 @@ -28,28 +28,28 @@ function Invoke-CippTestZTNA21840 { $PortalLink = 'https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/~/AdminAuthMethods' - $ResultMarkdown = "`n## [Security key attestation policy details]($PortalLink)`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("`n## [Security key attestation policy details]($PortalLink)`n") $AttestationStatus = if ($IsAttestationEnforced -eq $true) { 'True ✅' } else { 'False ❌' } - $ResultMarkdown += "- **Enforce attestation** : $AttestationStatus`n" + $null = $ResultMarkdown.Append("- **Enforce attestation** : $AttestationStatus`n") if ($KeyRestrictions) { - $ResultMarkdown += "- **Key restriction policy** :`n" + $null = $ResultMarkdown.Append("- **Key restriction policy** :`n") if ($null -ne $KeyRestrictions.isEnforced) { - $ResultMarkdown += " - **Enforce key restrictions** : $($KeyRestrictions.isEnforced)`n" + $null = $ResultMarkdown.Append(" - **Enforce key restrictions** : $($KeyRestrictions.isEnforced)`n") } else { - $ResultMarkdown += " - **Enforce key restrictions** : Not configured`n" + $null = $ResultMarkdown.Append(" - **Enforce key restrictions** : Not configured`n") } if ($KeyRestrictions.enforcementType) { - $ResultMarkdown += " - **Restrict specific keys** : $($KeyRestrictions.enforcementType)`n" + $null = $ResultMarkdown.Append(" - **Restrict specific keys** : $($KeyRestrictions.enforcementType)`n") } else { - $ResultMarkdown += " - **Restrict specific keys** : Not configured`n" + $null = $ResultMarkdown.Append(" - **Restrict specific keys** : Not configured`n") } if ($KeyRestrictions.aaGuids -and $KeyRestrictions.aaGuids.Count -gt 0) { - $ResultMarkdown += " - **AAGUID** :`n" + $null = $ResultMarkdown.Append(" - **AAGUID** :`n") foreach ($Guid in $KeyRestrictions.aaGuids) { - $ResultMarkdown += " - $Guid`n" + $null = $ResultMarkdown.Append(" - $Guid`n") } } } @@ -57,9 +57,9 @@ function Invoke-CippTestZTNA21840 { $Passed = if ($IsAttestationEnforced -eq $true) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = "Security key attestation is properly enforced, ensuring only verified hardware authenticators can be registered.$ResultMarkdown" + $ResultMarkdown = [System.Text.StringBuilder]::new("Security key attestation is properly enforced, ensuring only verified hardware authenticators can be registered.$ResultMarkdown") } else { - $ResultMarkdown = "Security key attestation is not enforced, allowing unverified or potentially compromised security keys to be registered.$ResultMarkdown" + $ResultMarkdown = [System.Text.StringBuilder]::new("Security key attestation is not enforced, allowing unverified or potentially compromised security keys to be registered.$ResultMarkdown") } Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'High' -Name 'Security key attestation is enforced' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Credential management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21845.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21845.ps1 index e6d305282de3..9021fa574bf6 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21845.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21845.ps1 @@ -43,25 +43,25 @@ function Invoke-CippTestZTNA21845 { $Passed = 'Failed' if ($TAPEnabled -and $TargetsAllUsers -and $HasConditionalAccessEnforcement -and $TAPSupportedInAuthStrength) { $Passed = 'Passed' - $ResultMarkdown = 'Temporary Access Pass is enabled, targeting all users, and enforced with conditional access policies.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Temporary Access Pass is enabled, targeting all users, and enforced with conditional access policies.') } elseif ($TAPEnabled -and $TargetsAllUsers -and $HasConditionalAccessEnforcement -and -not $TAPSupportedInAuthStrength) { - $ResultMarkdown = "Temporary Access Pass is enabled but authentication strength policies don't include TAP methods." + $ResultMarkdown = [System.Text.StringBuilder]::new("Temporary Access Pass is enabled but authentication strength policies don't include TAP methods.") } elseif ($TAPEnabled -and $TargetsAllUsers -and -not $HasConditionalAccessEnforcement) { - $ResultMarkdown = 'Temporary Access Pass is enabled but no conditional access enforcement for security info registration found. Consider adding conditional access policies for stronger security.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Temporary Access Pass is enabled but no conditional access enforcement for security info registration found. Consider adding conditional access policies for stronger security.') } else { - $ResultMarkdown = 'Temporary Access Pass is not properly configured or does not target all users.' + $ResultMarkdown = [System.Text.StringBuilder]::new('Temporary Access Pass is not properly configured or does not target all users.') } - $ResultMarkdown += "`n`n**Configuration summary**`n`n" + $null = $ResultMarkdown.Append("`n`n**Configuration summary**`n`n") $TAPStatus = if ($TAPConfig.state -eq 'enabled') { 'Enabled ✅' } else { 'Disabled ❌' } - $ResultMarkdown += "[Temporary Access Pass](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/~/AdminAuthMethods/fromNav/Identity): $TAPStatus`n`n" + $null = $ResultMarkdown.Append("[Temporary Access Pass](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/~/AdminAuthMethods/fromNav/Identity): $TAPStatus`n`n") $CAStatus = if ($HasConditionalAccessEnforcement) { 'Enabled ✅' } else { 'Not enabled ❌' } - $ResultMarkdown += "[Conditional Access policy for Security info registration](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies/fromNav/Identity): $CAStatus`n`n" + $null = $ResultMarkdown.Append("[Conditional Access policy for Security info registration](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies/fromNav/Identity): $CAStatus`n`n") $AuthStrengthStatus = if ($TAPSupportedInAuthStrength) { 'Enabled ✅' } else { 'Not enabled ❌' } - $ResultMarkdown += "[Authentication strength policy for Temporary Access Pass](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/AuthenticationStrength.ReactView/fromNav/Identity): $AuthStrengthStatus`n" + $null = $ResultMarkdown.Append("[Authentication strength policy for Temporary Access Pass](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/AuthenticationStrength.ReactView/fromNav/Identity): $AuthStrengthStatus`n") Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'Medium' -Name 'Temporary access pass is enabled' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Credential management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21846.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21846.ps1 index 6db468ee206c..241787f5ec71 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21846.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21846.ps1 @@ -20,19 +20,19 @@ function Invoke-CippTestZTNA21846 { $Passed = if ($TAPConfig.isUsableOnce -eq $true) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = "Temporary Access Pass is configured for one-time use only.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Temporary Access Pass is configured for one-time use only.`n`n") } else { - $ResultMarkdown = "Temporary Access Pass allows multiple uses during validity period.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("Temporary Access Pass allows multiple uses during validity period.`n`n") } - $ResultMarkdown += "## Temporary Access Pass Configuration`n`n" - $ResultMarkdown += "| Setting | Value | Status |`n" - $ResultMarkdown += "| :------ | :---- | :----- |`n" + $null = $ResultMarkdown.Append("## Temporary Access Pass Configuration`n`n") + $null = $ResultMarkdown.Append("| Setting | Value | Status |`n") + $null = $ResultMarkdown.Append("| :------ | :---- | :----- |`n") $IsUsableOnceValue = if ($TAPConfig.isUsableOnce) { 'Enabled' } else { 'Disabled' } $StatusEmoji = if ($Passed -eq 'Passed') { '✅ Pass' } else { '❌ Fail' } - $ResultMarkdown += "| [One-time use restriction](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/~/AdminAuthMethods/fromNav/) | $IsUsableOnceValue | $StatusEmoji |`n" + $null = $ResultMarkdown.Append("| [One-time use restriction](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/~/AdminAuthMethods/fromNav/) | $IsUsableOnceValue | $StatusEmoji |`n") Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'Medium' -Name 'Restrict Temporary Access Pass to Single Use' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Credential management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21848.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21848.ps1 index 92957ca70c24..a68c36ebcef2 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21848.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21848.ps1 @@ -46,15 +46,15 @@ function Invoke-CippTestZTNA21848 { } if ($Passed -eq 'Passed') { - $ResultMarkdown = "✅ Custom banned passwords are properly configured with organization-specific terms.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ Custom banned passwords are properly configured with organization-specific terms.`n`n") } else { - $ResultMarkdown = "❌ Custom banned passwords are not enabled or lack organization-specific terms.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Custom banned passwords are not enabled or lack organization-specific terms.`n`n") } - $ResultMarkdown += "## [Password protection settings]($PortalLink)`n`n" - $ResultMarkdown += "| Enforce custom list | Custom banned password list | Number of terms |`n" - $ResultMarkdown += "| :------------------ | :-------------------------- | :-------------- |`n" - $ResultMarkdown += "| $Enforced | $($DisplayList -join ', ') | $($BannedPasswordArray.Count) |`n" + $null = $ResultMarkdown.Append("## [Password protection settings]($PortalLink)`n`n") + $null = $ResultMarkdown.Append("| Enforce custom list | Custom banned password list | Number of terms |`n") + $null = $ResultMarkdown.Append("| :------------------ | :-------------------------- | :-------------- |`n") + $null = $ResultMarkdown.Append("| $Enforced | $($DisplayList -join ', ') | $($BannedPasswordArray.Count) |`n") Add-CippTestResult -TenantFilter $Tenant -TestId $TestId -TestType 'Identity' -Status $Passed -ResultMarkdown $ResultMarkdown -Risk 'Medium' -Name 'Add organizational terms to the banned password list' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Credential management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21849.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21849.ps1 index fc2a359a35d6..db14d7067bd1 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21849.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21849.ps1 @@ -17,37 +17,37 @@ function Invoke-CippTestZTNA21849 { if ($null -eq $PasswordRuleSettings) { # Default is 60 seconds $Passed = 'Passed' - $ResultMarkdown = "✅ Smart Lockout duration is configured to 60 seconds or higher (default).`n`n" - $ResultMarkdown += "## [Smart Lockout Settings]($PortalLink)`n`n" - $ResultMarkdown += "| Setting | Value |`n" - $ResultMarkdown += "| :---- | :---- |`n" - $ResultMarkdown += "| Lockout Duration (seconds) | 60 (Default) |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ Smart Lockout duration is configured to 60 seconds or higher (default).`n`n") + $null = $ResultMarkdown.Append("## [Smart Lockout Settings]($PortalLink)`n`n") + $null = $ResultMarkdown.Append("| Setting | Value |`n") + $null = $ResultMarkdown.Append("| :---- | :---- |`n") + $null = $ResultMarkdown.Append("| Lockout Duration (seconds) | 60 (Default) |`n") } else { $LockoutDurationSetting = $PasswordRuleSettings.values | Where-Object { $_.name -eq 'LockoutDurationInSeconds' } if ($null -eq $LockoutDurationSetting) { # Default is 60 seconds $Passed = 'Passed' - $ResultMarkdown = "✅ Smart Lockout duration is configured to 60 seconds or higher (default).`n`n" - $ResultMarkdown += "## [Smart Lockout Settings]($PortalLink)`n`n" - $ResultMarkdown += "| Setting | Value |`n" - $ResultMarkdown += "| :---- | :---- |`n" - $ResultMarkdown += "| Lockout Duration (seconds) | 60 (Default) |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ Smart Lockout duration is configured to 60 seconds or higher (default).`n`n") + $null = $ResultMarkdown.Append("## [Smart Lockout Settings]($PortalLink)`n`n") + $null = $ResultMarkdown.Append("| Setting | Value |`n") + $null = $ResultMarkdown.Append("| :---- | :---- |`n") + $null = $ResultMarkdown.Append("| Lockout Duration (seconds) | 60 (Default) |`n") } else { $LockoutDuration = [int]$LockoutDurationSetting.value if ($LockoutDuration -ge 60) { $Passed = 'Passed' - $ResultMarkdown = "✅ Smart Lockout duration is configured to 60 seconds or higher.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ Smart Lockout duration is configured to 60 seconds or higher.`n`n") } else { $Passed = 'Failed' - $ResultMarkdown = "❌ Smart Lockout duration is configured below 60 seconds.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Smart Lockout duration is configured below 60 seconds.`n`n") } - $ResultMarkdown += "## [Smart Lockout Settings]($PortalLink)`n`n" - $ResultMarkdown += "| Setting | Value |`n" - $ResultMarkdown += "| :---- | :---- |`n" - $ResultMarkdown += "| Lockout Duration (seconds) | $LockoutDuration |`n" + $null = $ResultMarkdown.Append("## [Smart Lockout Settings]($PortalLink)`n`n") + $null = $ResultMarkdown.Append("| Setting | Value |`n") + $null = $ResultMarkdown.Append("| :---- | :---- |`n") + $null = $ResultMarkdown.Append("| Lockout Duration (seconds) | $LockoutDuration |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21850.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21850.ps1 index 3e3708422b81..cbd2c7d6dbe0 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21850.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21850.ps1 @@ -16,28 +16,28 @@ function Invoke-CippTestZTNA21850 { if ($null -eq $PasswordRuleSettings) { $Passed = 'Failed' - $ResultMarkdown = '❌ Password rule settings template not found.' + $ResultMarkdown = [System.Text.StringBuilder]::new('❌ Password rule settings template not found.') } else { $LockoutThresholdSetting = $PasswordRuleSettings.values | Where-Object { $_.name -eq 'LockoutThreshold' } if ($null -eq $LockoutThresholdSetting) { $Passed = 'Failed' - $ResultMarkdown = "❌ Lockout threshold setting not found in [password rule settings]($PortalLink)." + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Lockout threshold setting not found in [password rule settings]($PortalLink).") } else { $LockoutThreshold = [int]$LockoutThresholdSetting.value if ($LockoutThreshold -le 10) { $Passed = 'Passed' - $ResultMarkdown = "✅ Smart lockout threshold is set to 10 or below.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ Smart lockout threshold is set to 10 or below.`n`n") } else { $Passed = 'Failed' - $ResultMarkdown = "❌ Smart lockout threshold is configured above 10.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Smart lockout threshold is configured above 10.`n`n") } - $ResultMarkdown += "## [Smart lockout configuration]($PortalLink)`n`n" - $ResultMarkdown += "| Setting | Value |`n" - $ResultMarkdown += "| :---- | :---- |`n" - $ResultMarkdown += "| Lockout threshold | $LockoutThreshold attempts |`n" + $null = $ResultMarkdown.Append("## [Smart lockout configuration]($PortalLink)`n`n") + $null = $ResultMarkdown.Append("| Setting | Value |`n") + $null = $ResultMarkdown.Append("| :---- | :---- |`n") + $null = $ResultMarkdown.Append("| Lockout threshold | $LockoutThreshold attempts |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21861.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21861.ps1 index baed2106b48c..b22f2ed98354 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21861.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21861.ps1 @@ -22,12 +22,12 @@ function Invoke-CippTestZTNA21861 { $Passed = if ($UntriagedHighRiskUsers.Count -eq 0) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = '✅ All high-risk users are properly triaged in Entra ID Protection.' + $ResultMarkdown = [System.Text.StringBuilder]::new('✅ All high-risk users are properly triaged in Entra ID Protection.') } else { - $ResultMarkdown = "❌ Found **$($UntriagedHighRiskUsers.Count)** untriaged high-risk users in Entra ID Protection.`n`n" - $ResultMarkdown += "## Untriaged High-Risk Users`n`n" - $ResultMarkdown += "| User | Risk level | Last updated | Risk detail |`n" - $ResultMarkdown += "| :--- | :--- | :--- | :--- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Found **$($UntriagedHighRiskUsers.Count)** untriaged high-risk users in Entra ID Protection.`n`n") + $null = $ResultMarkdown.Append("## Untriaged High-Risk Users`n`n") + $null = $ResultMarkdown.Append("| User | Risk level | Last updated | Risk detail |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :--- | :--- |`n") foreach ($User in $UntriagedHighRiskUsers) { $UserPrincipalName = if ($User.userPrincipalName) { $User.userPrincipalName } else { $User.id } @@ -41,7 +41,7 @@ function Invoke-CippTestZTNA21861 { $RiskDetail = $User.riskDetail $PortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_UsersAndTenants/UserProfileMenuBlade/~/overview/userId/$($User.id)" - $ResultMarkdown += "| [$UserPrincipalName]($PortalLink) | $RiskLevel | $RiskDate | $RiskDetail |`n" + $null = $ResultMarkdown.Append("| [$UserPrincipalName]($PortalLink) | $RiskLevel | $RiskDate | $RiskDetail |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21862.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21862.ps1 index c5c00bc795c6..fe5726e31390 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21862.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21862.ps1 @@ -21,16 +21,16 @@ function Invoke-CippTestZTNA21862 { $Passed = if (($UntriagedRiskyPrincipals.Count -eq 0) -and ($UntriagedRiskDetections.Count -eq 0)) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = '✅ All risky workload identities have been triaged' + $ResultMarkdown = [System.Text.StringBuilder]::new('✅ All risky workload identities have been triaged') } else { $RiskySPCount = $UntriagedRiskyPrincipals.Count $RiskyDetectionCount = $UntriagedRiskDetections.Count - $ResultMarkdown = "❌ Found $RiskySPCount untriaged risky service principals and $RiskyDetectionCount untriaged risk detections`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Found $RiskySPCount untriaged risky service principals and $RiskyDetectionCount untriaged risk detections`n`n") if ($RiskySPCount -gt 0) { - $ResultMarkdown += "## Untriaged Risky Service Principals`n`n" - $ResultMarkdown += "| Service Principal | Type | Risk Level | Risk State | Risk Last Updated |`n" - $ResultMarkdown += "| :--- | :--- | :--- | :--- | :--- |`n" + $null = $ResultMarkdown.Append("## Untriaged Risky Service Principals`n`n") + $null = $ResultMarkdown.Append("| Service Principal | Type | Risk Level | Risk State | Risk Last Updated |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :--- | :--- | :--- |`n") foreach ($SP in $UntriagedRiskyPrincipals) { $PortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/SignOn/objectId/$($SP.id)/appId/$($SP.appId)" $RiskLevel = switch ($SP.riskLevel) { @@ -46,14 +46,14 @@ function Invoke-CippTestZTNA21862 { 'remediated' { '✅ Remediated' } default { $SP.riskState } } - $ResultMarkdown += "| [$($SP.displayName)]($PortalLink) | $($SP.servicePrincipalType) | $RiskLevel | $RiskState | $($SP.riskLastUpdatedDateTime) |`n" + $null = $ResultMarkdown.Append("| [$($SP.displayName)]($PortalLink) | $($SP.servicePrincipalType) | $RiskLevel | $RiskState | $($SP.riskLastUpdatedDateTime) |`n") } } if ($RiskyDetectionCount -gt 0) { - $ResultMarkdown += "`n`n## Untriaged Risk Detection Events`n`n" - $ResultMarkdown += "| Service Principal | Risk Level | Risk State | Risk Event Type | Risk Last Updated |`n" - $ResultMarkdown += "| :--- | :--- | :--- | :--- | :--- |`n" + $null = $ResultMarkdown.Append("`n`n## Untriaged Risk Detection Events`n`n") + $null = $ResultMarkdown.Append("| Service Principal | Risk Level | Risk State | Risk Event Type | Risk Last Updated |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :--- | :--- | :--- |`n") foreach ($Detection in $UntriagedRiskDetections) { $PortalLink = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/ManagedAppMenuBlade/~/SignOn/objectId/$($Detection.servicePrincipalId)/appId/$($Detection.appId)" $RiskLevel = switch ($Detection.riskLevel) { @@ -69,7 +69,7 @@ function Invoke-CippTestZTNA21862 { 'remediated' { '✅ Remediated' } default { $Detection.riskState } } - $ResultMarkdown += "| [$($Detection.servicePrincipalDisplayName)]($PortalLink) | $RiskLevel | $RiskState | $($Detection.riskEventType) | $($Detection.detectedDateTime) |`n" + $null = $ResultMarkdown.Append("| [$($Detection.servicePrincipalDisplayName)]($PortalLink) | $RiskLevel | $RiskState | $($Detection.riskEventType) | $($Detection.detectedDateTime) |`n") } } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21863.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21863.ps1 index a6d40ba2445e..688ba6438366 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21863.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21863.ps1 @@ -21,12 +21,12 @@ function Invoke-CippTestZTNA21863 { $Passed = if ($UntriagedHighRiskSignIns.Count -eq 0) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = '✅ No untriaged risky sign ins in the tenant.' + $ResultMarkdown = [System.Text.StringBuilder]::new('✅ No untriaged risky sign ins in the tenant.') } else { - $ResultMarkdown = "❌ Found **$($UntriagedHighRiskSignIns.Count)** untriaged high-risk sign ins.`n`n" - $ResultMarkdown += "## Untriaged High-Risk Sign ins`n`n" - $ResultMarkdown += "| Date | User Principal Name | Type | Risk Level |`n" - $ResultMarkdown += "| :---- | :---- | :---- | :---- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Found **$($UntriagedHighRiskSignIns.Count)** untriaged high-risk sign ins.`n`n") + $null = $ResultMarkdown.Append("## Untriaged High-Risk Sign ins`n`n") + $null = $ResultMarkdown.Append("| Date | User Principal Name | Type | Risk Level |`n") + $null = $ResultMarkdown.Append("| :---- | :---- | :---- | :---- |`n") foreach ($Risk in $UntriagedHighRiskSignIns) { $UserPrincipalName = $Risk.userPrincipalName @@ -38,7 +38,7 @@ function Invoke-CippTestZTNA21863 { } $RiskEventType = $Risk.riskEventType $RiskDate = $Risk.detectedDateTime - $ResultMarkdown += "| $RiskDate | $UserPrincipalName | $RiskEventType | $RiskLevel |`n" + $null = $ResultMarkdown.Append("| $RiskDate | $UserPrincipalName | $RiskEventType | $RiskLevel |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21865.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21865.ps1 index 0aa3d5bb3837..3043fc8ec517 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21865.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21865.ps1 @@ -19,17 +19,17 @@ function Invoke-CippTestZTNA21865 { $Passed = $TrustedLocations.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ Trusted named locations are configured.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ Trusted named locations are configured.`n`n") } else { - $ResultMarkdown = "❌ No trusted named locations configured.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No trusted named locations configured.`n`n") } - $ResultMarkdown += "## Named Locations`n`n" - $ResultMarkdown += "$($NamedLocations.Count) named locations found.`n`n" + $null = $ResultMarkdown.Append("## Named Locations`n`n") + $null = $ResultMarkdown.Append("$($NamedLocations.Count) named locations found.`n`n") if ($NamedLocations.Count -gt 0) { - $ResultMarkdown += "| Name | Type | Trusted |`n" - $ResultMarkdown += "| :--- | :--- | :------ |`n" + $null = $ResultMarkdown.Append("| Name | Type | Trusted |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :------ |`n") foreach ($Location in $NamedLocations) { $Name = $Location.displayName @@ -37,7 +37,7 @@ function Invoke-CippTestZTNA21865 { elseif ($Location.'@odata.type' -eq '#microsoft.graph.countryNamedLocation') { 'Country-based' } else { 'Unknown' } $Trusted = if ($Location.isTrusted) { 'Yes' } else { 'No' } - $ResultMarkdown += "| $Name | $Type | $Trusted |`n" + $null = $ResultMarkdown.Append("| $Name | $Type | $Trusted |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21866.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21866.ps1 index 6bcc48f48d77..fecd3a3fe9b8 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21866.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21866.ps1 @@ -22,19 +22,19 @@ function Invoke-CippTestZTNA21866 { $Passed = if ($UnaddressedRecommendations.Count -eq 0) { 'Passed' } else { 'Failed' } if ($Passed -eq 'Passed') { - $ResultMarkdown = '✅ All Entra Recommendations are addressed.' + $ResultMarkdown = [System.Text.StringBuilder]::new('✅ All Entra Recommendations are addressed.') } else { - $ResultMarkdown = "❌ Found $($UnaddressedRecommendations.Count) unaddressed Entra recommendations.`n`n" - $ResultMarkdown += "## Unaddressed Entra recommendations`n`n" - $ResultMarkdown += "| Display Name | Status | Insights | Priority |`n" - $ResultMarkdown += "| :--- | :--- | :--- | :--- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ Found $($UnaddressedRecommendations.Count) unaddressed Entra recommendations.`n`n") + $null = $ResultMarkdown.Append("## Unaddressed Entra recommendations`n`n") + $null = $ResultMarkdown.Append("| Display Name | Status | Insights | Priority |`n") + $null = $ResultMarkdown.Append("| :--- | :--- | :--- | :--- |`n") foreach ($Item in $UnaddressedRecommendations) { $DisplayName = $Item.displayName $Status = $Item.status $Insights = $Item.insights $Priority = $Item.priority - $ResultMarkdown += "| $DisplayName | $Status | $Insights | $Priority |`n" + $null = $ResultMarkdown.Append("| $DisplayName | $Status | $Insights | $Priority |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21872.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21872.ps1 index b2a689e4c7f4..5c547748f391 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21872.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21872.ps1 @@ -54,32 +54,32 @@ function Invoke-CippTestZTNA21872 { # Determine pass/fail conditions if ($MfaRequiredInDeviceSettings) { $Passed = 'Failed' - $ResultMarkdown = "❌ **MFA is configured incorrectly.** Device Settings has 'Require Multi-Factor Authentication to register or join devices' set to Yes. According to best practices, this should be set to No, and MFA should be enforced through Conditional Access policies instead.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **MFA is configured incorrectly.** Device Settings has 'Require Multi-Factor Authentication to register or join devices' set to Yes. According to best practices, this should be set to No, and MFA should be enforced through Conditional Access policies instead.`n`n") } elseif ($DeviceRegistrationPolicies.Count -eq 0) { $Passed = 'Failed' - $ResultMarkdown = "❌ **No Conditional Access policies found** for device registration or device join. Create a policy that requires MFA for these user actions.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **No Conditional Access policies found** for device registration or device join. Create a policy that requires MFA for these user actions.`n`n") } elseif ($ValidPolicies.Count -eq 0) { $Passed = 'Failed' - $ResultMarkdown = "❌ **Conditional Access policies found**, but they're not correctly configured. Policies should require MFA or appropriate authentication strength.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Conditional Access policies found**, but they're not correctly configured. Policies should require MFA or appropriate authentication strength.`n`n") } else { $Passed = 'Passed' - $ResultMarkdown = "✅ **Properly configured Conditional Access policies found** that require MFA for device registration/join actions.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Properly configured Conditional Access policies found** that require MFA for device registration/join actions.`n`n") } # Add device settings information - $ResultMarkdown += "## Device Settings Configuration`n`n" - $ResultMarkdown += "| Setting | Value | Recommended Value | Status |`n" - $ResultMarkdown += "| :------ | :---- | :---------------- | :----- |`n" + $null = $ResultMarkdown.Append("## Device Settings Configuration`n`n") + $null = $ResultMarkdown.Append("| Setting | Value | Recommended Value | Status |`n") + $null = $ResultMarkdown.Append("| :------ | :---- | :---------------- | :----- |`n") $DeviceSettingStatus = if ($MfaRequiredInDeviceSettings) { '❌ Should be set to No' } else { '✅ Correctly configured' } $DeviceSettingValue = if ($MfaRequiredInDeviceSettings) { 'Yes' } else { 'No' } - $ResultMarkdown += "| Require Multi-Factor Authentication to register or join devices | $DeviceSettingValue | No | $DeviceSettingStatus |`n" + $null = $ResultMarkdown.Append("| Require Multi-Factor Authentication to register or join devices | $DeviceSettingValue | No | $DeviceSettingStatus |`n") # Add policies information if any found if ($DeviceRegistrationPolicies.Count -gt 0) { - $ResultMarkdown += "`n## Device Registration/Join Conditional Access Policies`n`n" - $ResultMarkdown += "| Policy Name | State | Requires MFA | Status |`n" - $ResultMarkdown += "| :---------- | :---- | :----------- | :----- |`n" + $null = $ResultMarkdown.Append("`n## Device Registration/Join Conditional Access Policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | State | Requires MFA | Status |`n") + $null = $ResultMarkdown.Append("| :---------- | :---- | :----------- | :----- |`n") foreach ($Policy in $DeviceRegistrationPolicies) { $PolicyName = $Policy.displayName @@ -92,7 +92,7 @@ function Invoke-CippTestZTNA21872 { $RequiresMfaText = if ($IsValid) { 'Yes' } else { 'No' } $StatusText = if ($IsValid) { '✅ Properly configured' } else { '❌ Incorrectly configured' } - $ResultMarkdown += "| $PolicyNameLink | $PolicyState | $RequiresMfaText | $StatusText |`n" + $null = $ResultMarkdown.Append("| $PolicyNameLink | $PolicyState | $RequiresMfaText | $StatusText |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21883.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21883.ps1 index b99980e3f165..1973fe5e4c0a 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21883.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21883.ps1 @@ -70,10 +70,10 @@ function Invoke-CippTestZTNA21883 { # Determine pass/fail if ($MatchedPolicies.Count -ge 1) { $Status = 'Passed' - $ResultMarkdown = "✅ **Pass**: Workload identities are protected by risk-based Conditional Access policies.`n`n" - $ResultMarkdown += "## Matching policies`n`n" - $ResultMarkdown += "| Policy name | State | Service principals | Grant controls |`n" - $ResultMarkdown += "| :---------- | :---- | :----------------- | :------------- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Workload identities are protected by risk-based Conditional Access policies.`n`n") + $null = $ResultMarkdown.Append("## Matching policies`n`n") + $null = $ResultMarkdown.Append("| Policy name | State | Service principals | Grant controls |`n") + $null = $ResultMarkdown.Append("| :---------- | :---- | :----------------- | :------------- |`n") foreach ($Policy in $MatchedPolicies) { $policyLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($Policy.id)" @@ -92,12 +92,12 @@ function Invoke-CippTestZTNA21883 { } else { 'None' } - $ResultMarkdown += "| [$policyName]($policyLink) | $($Policy.state) | $spTargets | $grants |`n" + $null = $ResultMarkdown.Append("| [$policyName]($policyLink) | $($Policy.state) | $spTargets | $grants |`n") } } else { $Status = 'Failed' - $ResultMarkdown = "❌ **Fail**: No Conditional Access policies found that protect workload identities with risk-based controls.`n`n" - $ResultMarkdown += 'Workload identities should be protected by policies that block authentication when service principal risk is detected.' + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: No Conditional Access policies found that protect workload identities with risk-based controls.`n`n") + $null = $ResultMarkdown.Append('Workload identities should be protected by policies that block authentication when service principal risk is detected.') } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21889.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21889.ps1 index f7e88c66552e..b357ea8add45 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21889.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21889.ps1 @@ -83,15 +83,15 @@ function Invoke-CippTestZTNA21889 { # Build result message if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: Your organization has implemented multiple passwordless authentication methods reducing password exposure.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Your organization has implemented multiple passwordless authentication methods reducing password exposure.`n`n") } else { - $ResultMarkdown = "❌ **Fail**: Your organization relies heavily on password-based authentication, creating security vulnerabilities.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: Your organization relies heavily on password-based authentication, creating security vulnerabilities.`n`n") } # Build detailed markdown table - $ResultMarkdown += "## Passwordless authentication methods`n`n" - $ResultMarkdown += "| Method | State | Include targets | Authentication mode | Status |`n" - $ResultMarkdown += "| :----- | :---- | :-------------- | :------------------ | :----- |`n" + $null = $ResultMarkdown.Append("## Passwordless authentication methods`n`n") + $null = $ResultMarkdown.Append("| Method | State | Include targets | Authentication mode | Status |`n") + $null = $ResultMarkdown.Append("| :----- | :---- | :-------------- | :------------------ | :----- |`n") # FIDO2 row $Fido2State = if ($Fido2Enabled) { '✅ Enabled' } else { '❌ Disabled' } @@ -101,7 +101,7 @@ function Invoke-CippTestZTNA21889 { 'None' } $Fido2Status = if ($Fido2Valid) { '✅ Pass' } else { '❌ Fail' } - $ResultMarkdown += "| FIDO2 Security Keys | $Fido2State | $Fido2TargetsDisplay | N/A | $Fido2Status |`n" + $null = $ResultMarkdown.Append("| FIDO2 Security Keys | $Fido2State | $Fido2TargetsDisplay | N/A | $Fido2Status |`n") # Microsoft Authenticator row $AuthState = if ($AuthEnabled) { '✅ Enabled' } else { '❌ Disabled' } @@ -112,7 +112,7 @@ function Invoke-CippTestZTNA21889 { } $AuthModeDisplay = if ($AuthModeValid) { "✅ $AuthMode" } else { "❌ $AuthMode" } $AuthStatus = if ($AuthValid) { '✅ Pass' } else { '❌ Fail' } - $ResultMarkdown += "| Microsoft Authenticator | $AuthState | $AuthTargetsDisplay | $AuthModeDisplay | $AuthStatus |`n" + $null = $ResultMarkdown.Append("| Microsoft Authenticator | $AuthState | $AuthTargetsDisplay | $AuthModeDisplay | $AuthStatus |`n") $TestParams = @{ TestId = 'ZTNA21889' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21892.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21892.ps1 index 404834bc99a9..7464fdf309fe 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21892.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21892.ps1 @@ -99,10 +99,10 @@ function Invoke-CippTestZTNA21892 { # Determine pass/fail if ($MatchingPolicies.Count -gt 0) { $Status = 'Passed' - $ResultMarkdown = "✅ **Pass**: Conditional Access policies require managed devices for all sign-in activity.`n`n" - $ResultMarkdown += "## Matching policies`n`n" - $ResultMarkdown += "| Policy name | State | All users | All apps | Compliant device | Hybrid joined |`n" - $ResultMarkdown += "| :---------- | :---- | :-------- | :------- | :--------------- | :------------ |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Conditional Access policies require managed devices for all sign-in activity.`n`n") + $null = $ResultMarkdown.Append("## Matching policies`n`n") + $null = $ResultMarkdown.Append("| Policy name | State | All users | All apps | Compliant device | Hybrid joined |`n") + $null = $ResultMarkdown.Append("| :---------- | :---- | :-------- | :------- | :--------------- | :------------ |`n") foreach ($Policy in $MatchingPolicies) { $policyLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($Policy.PolicyId)" @@ -112,12 +112,12 @@ function Invoke-CippTestZTNA21892 { $compliant = if ($Policy.CompliantDevice) { '✅' } else { '❌' } $hybrid = if ($Policy.HybridJoinedDevice) { '✅' } else { '❌' } - $ResultMarkdown += "| [$policyName]($policyLink) | $($Policy.PolicyState) | $allUsers | $allApps | $compliant | $hybrid |`n" + $null = $ResultMarkdown.Append("| [$policyName]($policyLink) | $($Policy.PolicyState) | $allUsers | $allApps | $compliant | $hybrid |`n") } } else { $Status = 'Failed' - $ResultMarkdown = "❌ **Fail**: No Conditional Access policies found that require managed devices for all sign-in activity.`n`n" - $ResultMarkdown += 'Organizations should enforce that all sign-ins come from managed devices (compliant or hybrid Azure AD joined) to ensure security controls are applied.' + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: No Conditional Access policies found that require managed devices for all sign-in activity.`n`n") + $null = $ResultMarkdown.Append('Organizations should enforce that all sign-ins come from managed devices (compliant or hybrid Azure AD joined) to ensure security controls are applied.') } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21941.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21941.ps1 index acf30fce0c67..15892a8feae1 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21941.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21941.ps1 @@ -129,23 +129,23 @@ function Invoke-CippTestZTNA21941 { # Build result markdown if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: Token protection policies are properly configured for Windows devices.`n`n" - $ResultMarkdown += "Token protection binds authentication tokens to devices, making stolen tokens unusable on other devices.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Token protection policies are properly configured for Windows devices.`n`n") + $null = $ResultMarkdown.Append("Token protection binds authentication tokens to devices, making stolen tokens unusable on other devices.`n`n") } else { if ($TokenProtectionPolicies.Count -eq 0) { - $ResultMarkdown = "❌ **Fail**: No token protection policies found for Windows devices.`n`n" - $ResultMarkdown += "Without token protection, authentication tokens can be stolen and replayed from other devices.`n`n" - $ResultMarkdown += '[Create token protection policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)' + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: No token protection policies found for Windows devices.`n`n") + $null = $ResultMarkdown.Append("Without token protection, authentication tokens can be stolen and replayed from other devices.`n`n") + $null = $ResultMarkdown.Append('[Create token protection policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)') } else { - $ResultMarkdown = "❌ **Fail**: Token protection policies exist but are not properly configured.`n`n" - $ResultMarkdown += "Policies must target users and include both Office 365 and Microsoft Graph applications.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: Token protection policies exist but are not properly configured.`n`n") + $null = $ResultMarkdown.Append("Policies must target users and include both Office 365 and Microsoft Graph applications.`n`n") } } if ($TokenProtectionPolicies.Count -gt 0) { - $ResultMarkdown += "## Token protection policies`n`n" - $ResultMarkdown += "| Policy Name | State | Has Users | Has Required Apps | Status |`n" - $ResultMarkdown += "| :---------- | :---- | :-------- | :---------------- | :----- |`n" + $null = $ResultMarkdown.Append("## Token protection policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | State | Has Users | Has Required Apps | Status |`n") + $null = $ResultMarkdown.Append("| :---------- | :---- | :-------- | :---------------- | :----- |`n") foreach ($policy in $TokenProtectionPolicies) { $stateIcon = if ($policy.State -eq 'enabled') { '✅' } else { '❌' } @@ -153,10 +153,10 @@ function Invoke-CippTestZTNA21941 { $appsIcon = if ($policy.HasRequiredApps) { '✅' } else { '❌' } $statusIcon = if ($policy.Status -eq 'Pass') { '✅' } else { '❌' } - $ResultMarkdown += "| $($policy.Name) | $stateIcon $($policy.State) | $usersIcon | $appsIcon | $statusIcon $($policy.Status) |`n" + $null = $ResultMarkdown.Append("| $($policy.Name) | $stateIcon $($policy.State) | $usersIcon | $appsIcon | $statusIcon $($policy.Status) |`n") } - $ResultMarkdown += "`n[Review policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)" + $null = $ResultMarkdown.Append("`n[Review policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)") } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21953.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21953.ps1 index 2a8f7db6e09a..e302b970ce13 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21953.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21953.ps1 @@ -44,11 +44,11 @@ function Invoke-CippTestZTNA21953 { $Status = if ($LapsEnabled) { 'Passed' } else { 'Failed' } if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: LAPS is deployed. Your organization can automatically manage and rotate local administrator passwords on all Entra joined and hybrid Entra joined Windows devices.`n`n" - $ResultMarkdown += '[Learn more](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)' + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: LAPS is deployed. Your organization can automatically manage and rotate local administrator passwords on all Entra joined and hybrid Entra joined Windows devices.`n`n") + $null = $ResultMarkdown.Append('[Learn more](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)') } else { - $ResultMarkdown = "❌ **Fail**: LAPS is not deployed. Local administrator passwords may be weak, shared, or unchanged, increasing security risk.`n`n" - $ResultMarkdown += '[Deploy LAPS](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)' + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: LAPS is not deployed. Local administrator passwords may be weak, shared, or unchanged, increasing security risk.`n`n") + $null = $ResultMarkdown.Append('[Deploy LAPS](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)') } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21954.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21954.ps1 index 763f98070216..f9e6678d5bcb 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21954.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21954.ps1 @@ -43,11 +43,11 @@ function Invoke-CippTestZTNA21954 { $Status = if ($IsRestricted) { 'Passed' } else { 'Failed' } if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: Non-admin users cannot read BitLocker recovery keys, reducing the risk of unauthorized access.`n`n" - $ResultMarkdown += '[Review settings](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/PoliciesTemplateBlade)' + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Non-admin users cannot read BitLocker recovery keys, reducing the risk of unauthorized access.`n`n") + $null = $ResultMarkdown.Append('[Review settings](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/PoliciesTemplateBlade)') } else { - $ResultMarkdown = "❌ **Fail**: Non-admin users can read BitLocker recovery keys for their own devices, which may allow unauthorized access.`n`n" - $ResultMarkdown += '[Restrict access](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/PoliciesTemplateBlade)' + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: Non-admin users can read BitLocker recovery keys for their own devices, which may allow unauthorized access.`n`n") + $null = $ResultMarkdown.Append('[Restrict access](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/PoliciesTemplateBlade)') } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21955.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21955.ps1 index ad25a84468ac..2d672a23bf97 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21955.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21955.ps1 @@ -43,11 +43,11 @@ function Invoke-CippTestZTNA21955 { $Status = if ($GlobalAdminsEnabled) { 'Passed' } else { 'Failed' } if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: Global Administrators are automatically added as local administrators on Entra joined devices.`n`n" - $ResultMarkdown += '[Review settings](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)' + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Global Administrators are automatically added as local administrators on Entra joined devices.`n`n") + $null = $ResultMarkdown.Append('[Review settings](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)') } else { - $ResultMarkdown = "❌ **Fail**: Global Administrators are not automatically added as local administrators, which may limit emergency access capabilities.`n`n" - $ResultMarkdown += '[Configure settings](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)' + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: Global Administrators are not automatically added as local administrators, which may limit emergency access capabilities.`n`n") + $null = $ResultMarkdown.Append('[Configure settings](https://entra.microsoft.com/#view/Microsoft_AAD_Devices/DevicesMenuBlade/~/DeviceSettings/menuId/)') } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21964.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21964.ps1 index 3e1fbc1e140a..1f6860ca32a8 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21964.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21964.ps1 @@ -18,16 +18,16 @@ function Invoke-CippTestZTNA21964 { $BuiltInStrengths = @($AuthStrengths | Where-Object { $_.policyType -eq 'builtIn' }) $CustomStrengths = @($AuthStrengths | Where-Object { $_.policyType -eq 'custom' }) - $ResultMarkdown = "## Authentication Strength Policies`n`n" - $ResultMarkdown += "Found $($AuthStrengths.Count) authentication strength policies ($($BuiltInStrengths.Count) built-in, $($CustomStrengths.Count) custom).`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("## Authentication Strength Policies`n`n") + $null = $ResultMarkdown.Append("Found $($AuthStrengths.Count) authentication strength policies ($($BuiltInStrengths.Count) built-in, $($CustomStrengths.Count) custom).`n`n") if ($CustomStrengths.Count -gt 0) { - $ResultMarkdown += "### Custom Authentication Strengths`n`n" - $ResultMarkdown += "| Name | Combinations |`n" - $ResultMarkdown += "| :--- | :---------- |`n" + $null = $ResultMarkdown.Append("### Custom Authentication Strengths`n`n") + $null = $ResultMarkdown.Append("| Name | Combinations |`n") + $null = $ResultMarkdown.Append("| :--- | :---------- |`n") foreach ($strength in $CustomStrengths) { $combinations = if ($strength.allowedCombinations) { $strength.allowedCombinations.Count } else { 0 } - $ResultMarkdown += "| $($strength.displayName) | $combinations methods |`n" + $null = $ResultMarkdown.Append("| $($strength.displayName) | $combinations methods |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22124.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22124.ps1 index 7ac318bbd035..1414851d8a18 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22124.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22124.ps1 @@ -48,22 +48,22 @@ function Invoke-CippTestZTNA22124 { $Status = if ($HighPriorityIssues.Count -eq 0) { 'Passed' } else { 'Failed' } if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: All high priority Entra recommendations have been addressed.`n`n" - $ResultMarkdown += '[View recommendations](https://entra.microsoft.com/#view/Microsoft_Azure_SecureScore/OverviewBlade)' + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: All high priority Entra recommendations have been addressed.`n`n") + $null = $ResultMarkdown.Append('[View recommendations](https://entra.microsoft.com/#view/Microsoft_Azure_SecureScore/OverviewBlade)') } else { - $ResultMarkdown = "❌ **Fail**: There are $($HighPriorityIssues.Count) high priority recommendation(s) that have not been addressed.`n`n" - $ResultMarkdown += "## Outstanding high priority recommendations`n`n" - $ResultMarkdown += "| Display Name | Status | Insights |`n" - $ResultMarkdown += "| :----------- | :----- | :------- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: There are $($HighPriorityIssues.Count) high priority recommendation(s) that have not been addressed.`n`n") + $null = $ResultMarkdown.Append("## Outstanding high priority recommendations`n`n") + $null = $ResultMarkdown.Append("| Display Name | Status | Insights |`n") + $null = $ResultMarkdown.Append("| :----------- | :----- | :------- |`n") foreach ($issue in $HighPriorityIssues) { $displayName = if ($issue.displayName) { $issue.displayName } else { 'N/A' } $status = if ($issue.status) { $issue.status } else { 'N/A' } $insights = if ($issue.insights) { $issue.insights } else { 'N/A' } - $ResultMarkdown += "| $displayName | $status | $insights |`n" + $null = $ResultMarkdown.Append("| $displayName | $status | $insights |`n") } - $ResultMarkdown += "`n[Address recommendations](https://entra.microsoft.com/#view/Microsoft_Azure_SecureScore/OverviewBlade)" + $null = $ResultMarkdown.Append("`n[Address recommendations](https://entra.microsoft.com/#view/Microsoft_Azure_SecureScore/OverviewBlade)") } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22659.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22659.ps1 index 05f1985ddb04..2ae158e97554 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22659.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22659.ps1 @@ -48,13 +48,13 @@ function Invoke-CippTestZTNA22659 { $Status = if ($RiskySignIns.Count -eq 0) { 'Passed' } else { 'Failed' } if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: No risky workload identity sign-ins detected or all have been triaged.`n`n" - $ResultMarkdown += '[View identity protection](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/IdentityProtectionMenuBlade/~/RiskyServicePrincipals)' + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: No risky workload identity sign-ins detected or all have been triaged.`n`n") + $null = $ResultMarkdown.Append('[View identity protection](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/IdentityProtectionMenuBlade/~/RiskyServicePrincipals)') } else { - $ResultMarkdown = "❌ **Fail**: There are $($RiskySignIns.Count) risky workload identity sign-in(s) that require investigation.`n`n" - $ResultMarkdown += "## Risky service principal sign-ins`n`n" - $ResultMarkdown += "| Service Principal | App ID | Risk State | Risk Level | Last Updated |`n" - $ResultMarkdown += "| :---------------- | :----- | :--------- | :--------- | :----------- |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: There are $($RiskySignIns.Count) risky workload identity sign-in(s) that require investigation.`n`n") + $null = $ResultMarkdown.Append("## Risky service principal sign-ins`n`n") + $null = $ResultMarkdown.Append("| Service Principal | App ID | Risk State | Risk Level | Last Updated |`n") + $null = $ResultMarkdown.Append("| :---------------- | :----- | :--------- | :--------- | :----------- |`n") foreach ($signin in $RiskySignIns) { $spName = if ($signin.servicePrincipalDisplayName) { $signin.servicePrincipalDisplayName } else { 'N/A' } @@ -73,10 +73,10 @@ function Invoke-CippTestZTNA22659 { } } - $ResultMarkdown += "| $spName | $appId | $riskState | $riskLevel | $lastUpdated |`n" + $null = $ResultMarkdown.Append("| $spName | $appId | $riskState | $riskLevel | $lastUpdated |`n") } - $ResultMarkdown += "`n[Investigate and remediate](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/IdentityProtectionMenuBlade/~/RiskyServicePrincipals)" + $null = $ResultMarkdown.Append("`n[Investigate and remediate](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/IdentityProtectionMenuBlade/~/RiskyServicePrincipals)") } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24570.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24570.ps1 index 7e8b90173796..68e6c82a6487 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24570.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24570.ps1 @@ -137,21 +137,21 @@ function Invoke-CippTestZTNA24570 { } if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: Hybrid identity is enabled and using a service principal for synchronization.`n`n" - $ResultMarkdown += "**Last Sync**: $lastSyncDate`n`n" - $ResultMarkdown += '[Review configuration](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/RolesManagementMenuBlade/~/AllRoles)' + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Hybrid identity is enabled and using a service principal for synchronization.`n`n") + $null = $ResultMarkdown.Append("**Last Sync**: $lastSyncDate`n`n") + $null = $ResultMarkdown.Append('[Review configuration](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/RolesManagementMenuBlade/~/AllRoles)') } else { - $ResultMarkdown = "❌ **Fail**: Hybrid identity is enabled but using $($EnabledUsers.Count) enabled user account(s) for synchronization.`n`n" - $ResultMarkdown += "**Last Sync**: $lastSyncDate`n`n" - $ResultMarkdown += "## Directory Synchronization Accounts role members`n`n" - $ResultMarkdown += "| Display Name | User Principal Name | Enabled |`n" - $ResultMarkdown += "| :----------- | :------------------ | :------ |`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: Hybrid identity is enabled but using $($EnabledUsers.Count) enabled user account(s) for synchronization.`n`n") + $null = $ResultMarkdown.Append("**Last Sync**: $lastSyncDate`n`n") + $null = $ResultMarkdown.Append("## Directory Synchronization Accounts role members`n`n") + $null = $ResultMarkdown.Append("| Display Name | User Principal Name | Enabled |`n") + $null = $ResultMarkdown.Append("| :----------- | :------------------ | :------ |`n") foreach ($user in $EnabledUsers) { - $ResultMarkdown += "| $($user.DisplayName) | $($user.UserPrincipalName) | ✅ Yes |`n" + $null = $ResultMarkdown.Append("| $($user.DisplayName) | $($user.UserPrincipalName) | ✅ Yes |`n") } - $ResultMarkdown += "`n[Migrate to service principal](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/RolesManagementMenuBlade/~/AllRoles)" + $null = $ResultMarkdown.Append("`n[Migrate to service principal](https://entra.microsoft.com/#view/Microsoft_AAD_IAM/RolesManagementMenuBlade/~/AllRoles)") } $TestParams = @{ diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24572.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24572.ps1 index 637e96170614..0630bcb09e7a 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24572.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24572.ps1 @@ -24,19 +24,19 @@ function Invoke-CippTestZTNA24572 { $Passed = $AssignedNotifications.Count -gt 0 if ($Passed) { - $ResultMarkdown = "✅ At least one device enrollment notification is configured and assigned.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ At least one device enrollment notification is configured and assigned.`n`n") } else { - $ResultMarkdown = "❌ No device enrollment notification is configured or assigned in Intune.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ No device enrollment notification is configured or assigned in Intune.`n`n") } if ($EnrollmentNotifications.Count -gt 0) { - $ResultMarkdown += "## Device Enrollment Notifications`n`n" - $ResultMarkdown += "| Policy Name | Assigned |`n" - $ResultMarkdown += "| :---------- | :------- |`n" + $null = $ResultMarkdown.Append("## Device Enrollment Notifications`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Assigned |`n") + $null = $ResultMarkdown.Append("| :---------- | :------- |`n") foreach ($policy in $EnrollmentNotifications) { $assigned = if ($policy.assignments -and $policy.assignments.Count -gt 0) { '✅ Yes' } else { '❌ No' } - $ResultMarkdown += "| $($policy.displayName) | $assigned |`n" + $null = $ResultMarkdown.Append("| $($policy.displayName) | $assigned |`n") } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24824.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24824.ps1 index bb08a44adbd4..d42e8ad8602a 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24824.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24824.ps1 @@ -118,9 +118,9 @@ function Invoke-CippTestZTNA24824 { # Build result markdown if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: Conditional Access policies block noncompliant devices across all platforms.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Conditional Access policies block noncompliant devices across all platforms.`n`n") } else { - $ResultMarkdown = "❌ **Fail**: Conditional Access policies do not cover all device platforms.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: Conditional Access policies do not cover all device platforms.`n`n") $missingPlatforms = [System.Collections.Generic.List[string]]::new() foreach ($key in $PlatformCoverage.Keys) { if (-not $PlatformCoverage[$key]) { @@ -128,19 +128,19 @@ function Invoke-CippTestZTNA24824 { } } if ($missingPlatforms.Count -gt 0) { - $ResultMarkdown += "**Missing platform coverage**: $($missingPlatforms -join ', ')`n`n" + $null = $ResultMarkdown.Append("**Missing platform coverage**: $($missingPlatforms -join ', ')`n`n") } } - $ResultMarkdown += "## Compliant device policies`n`n" - $ResultMarkdown += "| Policy Name | Platforms |`n" - $ResultMarkdown += "| :---------- | :-------- |`n" + $null = $ResultMarkdown.Append("## Compliant device policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Platforms |`n") + $null = $ResultMarkdown.Append("| :---------- | :-------- |`n") foreach ($detail in $PolicyDetails) { - $ResultMarkdown += "| $($detail.Name) | $($detail.Platforms) |`n" + $null = $ResultMarkdown.Append("| $($detail.Name) | $($detail.Platforms) |`n") } - $ResultMarkdown += "`n[Review policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)" + $null = $ResultMarkdown.Append("`n[Review policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)") $TestParams = @{ TestId = 'ZTNA24824' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24827.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24827.ps1 index 7a6e0bd833c9..6f5413e480c4 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24827.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA24827.ps1 @@ -128,9 +128,9 @@ function Invoke-CippTestZTNA24827 { # Build result markdown if ($Status -eq 'Passed') { - $ResultMarkdown = "✅ **Pass**: Conditional Access policies block unmanaged apps on both iOS and Android platforms.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("✅ **Pass**: Conditional Access policies block unmanaged apps on both iOS and Android platforms.`n`n") } else { - $ResultMarkdown = "❌ **Fail**: Conditional Access policies do not cover all mobile platforms.`n`n" + $ResultMarkdown = [System.Text.StringBuilder]::new("❌ **Fail**: Conditional Access policies do not cover all mobile platforms.`n`n") $missingPlatforms = [System.Collections.Generic.List[string]]::new() if (-not $PlatformCoverage['iOS']) { $missingPlatforms.Add('iOS') @@ -139,19 +139,19 @@ function Invoke-CippTestZTNA24827 { $missingPlatforms.Add('android') } if ($missingPlatforms.Count -gt 0) { - $ResultMarkdown += "**Missing platform coverage**: $($missingPlatforms -join ', ')`n`n" + $null = $ResultMarkdown.Append("**Missing platform coverage**: $($missingPlatforms -join ', ')`n`n") } } - $ResultMarkdown += "## Compliant application policies`n`n" - $ResultMarkdown += "| Policy Name | Platforms |`n" - $ResultMarkdown += "| :---------- | :-------- |`n" + $null = $ResultMarkdown.Append("## Compliant application policies`n`n") + $null = $ResultMarkdown.Append("| Policy Name | Platforms |`n") + $null = $ResultMarkdown.Append("| :---------- | :-------- |`n") foreach ($detail in $PolicyDetails) { - $ResultMarkdown += "| $($detail.Name) | $($detail.Platforms) |`n" + $null = $ResultMarkdown.Append("| $($detail.Name) | $($detail.Platforms) |`n") } - $ResultMarkdown += "`n[Review policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)" + $null = $ResultMarkdown.Append("`n[Review policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)") $TestParams = @{ TestId = 'ZTNA24827' diff --git a/Shared/CIPPSharp/CIPPTestDataCache.cs b/Shared/CIPPSharp/CIPPTestDataCache.cs index 9f0f639a1d00..496a631922d9 100644 --- a/Shared/CIPPSharp/CIPPTestDataCache.cs +++ b/Shared/CIPPSharp/CIPPTestDataCache.cs @@ -103,25 +103,38 @@ public static void Set(string key, object? value) if (_cache.ContainsKey(key)) RemoveEntry(key); - // Evict LRU entries until we have room - while (Interlocked.Read(ref _currentBytes) + sizeBytes > _maxBytes) + // Take the LRU lock once for both eviction and insertion. The previous + // implementation took _lruLock per eviction iteration plus another time + // for the AddLast, which under write contention turned every Set() into + // a stream of short critical sections instead of one bounded one. + var entry = new CacheEntry(value, sizeBytes, DateTime.UtcNow + _ttl); + lock (_lruLock) { - string? victim; - lock (_lruLock) + while (_currentBytes + sizeBytes > _maxBytes) { if (_lruOrder.First == null) break; - victim = _lruOrder.First.Value; + var victimKey = _lruOrder.First.Value; + if (_cache.TryRemove(victimKey, out var victim)) + { + Interlocked.Add(ref _currentBytes, -victim.SizeBytes); + if (_lruIndex.TryGetValue(victimKey, out var vnode)) + { + _lruOrder.Remove(vnode); + _lruIndex.Remove(victimKey); + } + Interlocked.Increment(ref _evictions); + } + else + { + // Defensive: dictionary already pruned, drop the LRU node anyway. + _lruOrder.RemoveFirst(); + _lruIndex.Remove(victimKey); + } } - RemoveEntry(victim); - Interlocked.Increment(ref _evictions); - } - var entry = new CacheEntry(value, sizeBytes, DateTime.UtcNow + _ttl); - if (_cache.TryAdd(key, entry)) - { - Interlocked.Add(ref _currentBytes, sizeBytes); - lock (_lruLock) + if (_cache.TryAdd(key, entry)) { + Interlocked.Add(ref _currentBytes, sizeBytes); var node = _lruOrder.AddLast(key); _lruIndex[key] = node; } diff --git a/Shared/CIPPSharp/bin/CIPPSharp.dll b/Shared/CIPPSharp/bin/CIPPSharp.dll index 79dae0140c352bbf55f95f7c5f9466d8def44454..92d07b054ef993dece27dd1fc7244bf175c1ed68 100644 GIT binary patch literal 41472 zcmeIbdwi7Doj?A0o|$>(lFTGClPkH90KtJ=NC3GgD1mTOB1nROw=g6F3?%b}nS_gw zkSJ=cqNP>qt*x|bwXJP!tF5;c+UnM=*4ozXx>XjccGq3o+U<5%w_bkl&*wbPToSO| z?e6cje|!h#^L%dSb3W&D&iS14oOx!_e(@*BK}1fxKm3sBJGk;|k-(1zBM=8BJ{F*F zd7rQSj<)Lg+Rkn9R84=<+?tH`)pSP_33H%kbF3ygn5c;-YT8zJ)byD>u_nLYH^nl& zW+~As%|XMzxacXnwi7g|CQoZ7x&s_Talgur)!=;;Z=yoMb>%lR*njoXh`{HUgEn2w zs{B85wMb^+bt&wwX5=VQKL=v$>jY6gc#EJ{Iac;jbPbUwOYQ-^B8zSsi0v5wee^Z} z$fT{7-=O4GL$s+WnM!tp65oyj;fCIYch+kW+-pk4dQB*@uXF-$wskk&S+7Mzi?c{U zKf!Uui+$6{Mc=uQ=wn-m5XJx2t6JYzLgbr{YbjqZ(j#TK>W(31>&_wJ5`~)`)QTEV zPtY+4SmD!+rrAcrj7T{viWNhkIRT7@(ny8yxx68^P4tIvddc+C=|8FIJCJ{G&9rsv z?!7pxxLE~-4Hi37+N@^wboRkbo8bdO6)Z}^hyJod0k~9GqN?|WsBS0XU2y}0M(5O` z`n(TH7&j-v#OLcuRU`94THQ_;>oKtP7*~3c8g>*BskN??tm|a!I>oxyS=Xt2{Yg4< zTnv%pQo3u13+Vyp5Es$|t|2a@2aF*pBys&B+T@V98t?&eJ^Rz(81ruew8FzC4e>`s zcefQ?uEW^JP@>z=6*+^=@8e1hfo23~!}7>9;jO=3<3!j}b_AxXYm|q%c z6h1p?>|6o6LblG*co!x?ew!E16z@COR++i{s_B5uDYEV{uMVc zuoW2}Gm2-SIXs$Y^EETSnT=5@+|2xT$v7;4l`-?1)s1}&bLTggH^g@K{4RRvmBl%u zLJE$b-(31M^Lw6_J-@m9pVN12@K5z!dVZe?Wsb}o>pIuE&a0rwDF)B~O&vB)|0RoFuoK#s*CD7oNy z=FEQ@(8>i|gRzgH^u+uxu}Gy$uGNs{NN1=Fq9z)9V(eq6GQw^vBd}-2K87kIbXggJ zJvH_*R2gBpl@Zu;V;@7w2*@A{f$RIYC5Axr9H7V`=L(-&X6$2VE&@;)M2)wO+B80gNR!6r5NVO*5X*L^S!OHhwewS^Rpw%5 zTkFzA$T@CVm#)CLtxo40ZpoiBZnnbKNpRiBd};uPZi}@dc|Z zzs^|{Sq{>~UM$Tci?@<_rRI6+az0-s`pi{wtv1_nOHWk7yl2`S7J&$Pg@b^(anEJJ*V!s|5Nq-&-Yu|NA}zQzwy)w z>74J!f+33F4#zi?!psa&@laB(`xO~M(yb4XP?3)G9J)v+@}jJs>g36_4#4MV3>e9q zbn34#F9O{V9L9?2Onwg>BekB38p)T_Vpg8$VC4-;dBRxbJa;mlQ79QMV}--uC~Nui ze##3%@-w0%HJ(D}Yohbl*>(2=Gx@_Tg_-W;Pd`#2IE))dXY$=l8IP>@ z4#GGjACEDgk@OnrMmS6J>Mu2tExDZYjO4mp&O9TzFPAgbNIsd%DKe7(k;~Cb^4wCt zi!MR^{2H!>gL>)qVI)>(U1RCg;f+8(Fb*RoDWJZ(5tf($zNy2w!E-vGjMCZ4pUfz; zL@D!n+`)?Nu3Yt7Gqzb`H6+~ZA@n2Rt}LiKoUVW~;0hQ4H}6~x z6Hf3BEdu+p2J3|6!sjxadtk*kv%y>n#_t@auq+5vBMHo*VbH}&t`O3GQ2HU{JBhBEKzk$Va0f^;@ufLEKzlB z&(%4S(P7E(j$HBQGj*{<)x|SyxHnhv3mHY0L|h!~zFeJqGIg;eg2wqG1{5cjN8GHR z$ko4>_2Cu9081iRGwUN*{OX5!Mh%;}+V`_VF*5g#k|=jwcsbzloREE(RJE54t_ zC=4-6BA8V5DJc3k1Sj6 z1dNEy5Cn{AHbao}th)%7TDK12W`jnQ%ap8jV{tgNR2@^+H5yapro%+e@R>*;d@AWU zCA-Yo=uXz5biFG%AD7S9mAg)$F6fJQ?t^~1tW1f3vi)F@P zUIo_F1I_8mPFL@NhO~Oh+U={7a`9n!W&m$PJh zvu3p<^-V76Fu%$dr}=fhi1VE8*?IFDV3`Mj9cW6spzgykPN*Plzri+%;Bc!N@(#?% z6sW-c28{5>7D2I(8*j1iE6FR-B{KOE6!U774Lj?X2A$?LjJn*8nfmc|Bjk!)3tD37 z*}ie+6j2BFW8j7iRcC>_bVFWNIAP>Eh#S9gJU{4caD)6f$XDFcH8`Tv6k5AWeZ0V9 z0D+U2H3syO>BIGCfj)dApsXo}fP7#)f{3K9hn>0+mY6pHJQo zFegT05-~ziUqilO-h}Ja!`2^P{qv0@PGEN1P z?h#DlE?6^rC0L7%Nt;Y;XMCwoG04tFe$Ga6X17{ZNm5hE_6T+)7^hrWn&A4<1Xr0R zxYjhm72Cx0VU<^~W$}&hOj6{nOmbYNU!af7#MRLo!NTnunZ-PbBicYA#PPsHN`}t_={2#?`YaSn zsqeVY(_pwGSTTLC95@RaR#zJ4w_)_c(&MTsdc$pE`Nb`l5sAWRzGs-5-GV73jWX|s(5}(pi5vkr%)y9! z9$4ye6!A+t!_JbhE9k^5;HJ*Q*dS?@`2~>kKbb?C_kb5Rq=ujoa+~*pZdiF>9_$9) z=67H;7P$|+sS+}%cvQ^F#bnT$6g{r<(ReV4AR(Yi}%=SGSq=5^& zKK$@Q)kLrP09QX%s`ps+a^L9YmmmmxRlfzj=9fWY`nq`fsvbhn)%@+DH&$x-Xr+vi z9$a~hqz8sHuv2O{Cg1f*brdx6GP^4Ln z#U=>`=XKxoCW(ZkgXzf(-izg*U(KzTltmt5#R<|`(`O!#A@>ci;wUWB0`rg!$t19F z83Tg<`!MSavv32}7@uXBXu1gOiZ(8~I7_kjl2TPG`cS98h9kus!*gJSmz0ILX2(+a zu2(S+aH9WBcw>GGAiu<4Kj`t^5q1V~dyJic`7pSc8m^+qBfuIvgRYTtVJhfK9%3=3 zwQpn*Gn~mUr1g!UQ53;M@HI3AjpQHGimj|@BwZf6*$7u$PvJ>)v*G8%Sz-J1$RwQEN0x zwufEEXS$0b@TdO9?T+miD+Ajv0e^z73>QTTL<*LBs+6Fhi{~BMqtjHHwwi}Jzw+qt zUF7Drao)pPNTuAK2B%{}i>^-}=BQgiEfzvIpMtv$9^Db?MiZu|+qzC4TnaOd?fPH~ zW_07y+_smb+wLgckZHZu=wF_u(bIKnuCAp?1Ip(L?2Ry}vC2{~brin%h8KyG$AC9Z z80OwCiU{E!=AKqWgO2SVE9&NVp(s6sY!0s5>YX;rL$Tl$tvWfbcgaD$bVr2|bc#_W zPdDXzFe#(RYR|XQ?ZF^MC%V+m#jyH-XdRt(lRc(Z(VDwd+j!r{_r~RDn=Bh@nKZx8 z%`DlTmwFmVz7ckpggx_~LlA=QpyvxnP!(Zs$QyYE(uw=aN=(c{U)UG)-54)zb|-@v z`1Q`9uk?;^UP(9~ir6pvWNnm1E$k2a8@xfkENZ#(V7{zy;eb0B*pTN5=7kE(XHmGZ zJar>_zb=@^+dLy&08y~O{2PdZ1$E}N%qt8QmToYyzcZMHjhq1+90#GV=WG53-hLTD zPw9rRC+G>fZ;YQi4U0^PzXTuIxIQRG#;ph$>S{F`%R*MOgQ1{m4W^RO;0Zcm$yx8@ zxot}@1&weBi|h~KUBi@MC>##D!bPDXVeon$2!=r+p1#QQtm6&2f}zNdfQF3hQpV{@ zy`V}NR;ir!tU-HH*gRv0e`DO-%81vD^9B3a9ej1$sWqL$%|7NwHUgR^qmt6ctL z8SsWV!{vOLF$_D-hR86~oDH>d!rw4KPV;f7U(L*XIfid=z535w7}LaQzKS|Z%;SK3 z67dhX;?wiU30xb`)l2Gp@KAtnnCCl!y3oU)V1< zkP`5!@Gb>@23j^p1;7z`gDr5sI4KE5IE#K6gj3jztujhCmM(b-MIy*2hKenJ;4i`D zM+8)KTP3<&NDn%PuIB6hU4S7B`7npK0+#iCEC@ry*R1$*IauQO>iWLjpm4Z7bO2Y7 zd-$@CiO5{IFf_vSeun%05e4!s{GYh6V!sJAG9S7qy%%~Z3=lkv!iMCtztq2g9vTHn z(!7fiI(1wA76g8M-xUxwY*SbB*WiVX`UP0le#0m>4Iy7BFY;RuL0{M%%nN(;eQeEq zn_1Xhn7;$k=<^ExufS3E_sn|ycnJOM$Ld^yMWjM)aP)oqP9=L z&R2@`5zY_hPdvNK{5P1XE4d@=kNg2xI8YiX;)UZsm?)@^Vn6W?qu56z*P~d4`7X$Y zmBGSro-iW+$>I=p9(bEf1VbCmKjK<`=yHsTx*u;~58eaAe4kZ=!C>SAMgqt4rylpB z0;D-#<7hirs2j=h9O18V93$v9jy9l)lZQWVUW2u}6d z!QYb|{JfA)1wRj)5?{DTnFtpLi%qVfu{>BTr>PR56dwEY)rCt==|?bpYA1r>V3G2` z>bSG^V?%PvzSH*&q0uGHj-qQzHfx z>Rk~x>tWp&y*1WI()TgP^gvc(dI7`bq4J22k+M)(1k>Bs*s7NzHK{3AHiR9eJ>|;u zNHI^j`yD)>SP(AL_w)6G;>8$slG0?#l|}N|WT_r39l~zQc|1HdYc@sB#uWNs0(@rK z(6s<$lr<$ShUrJyA#7FA<*Z-^KsW5qBCJ+mV51dQHqg1O%-pOShtW z2t&(m1=ht9ZpAM#IrV)k2v_U-`T9Ze(wtUQN5X8w;!s$BYjyXl);s>|O#LA2P^aOq; z5!Why1`*c^e&*1~d-X=lB&5->zK_RbL!P>-o>6?BWX$JlHlNcF^Cca99O>zm@DyH8 zWftw{HRXe1jJgWFNR|g&*n+;FXQSLuX<)INYIO@viJSpHrx?Bui`@kTQL_g z)(O*sR^oaIuGpERU-=N`fhxrnGn)duJHhqa&`aQ22f|+SnUBX>2*ZO)p`2}J5Hy-V z@8XK3Ic?{J69;0T3thQ(+CVdZU<|(;`q!b3&l_^C01Zr3yxU>75AVmu@ZW^&P1yKJ z7N5&tV?eKg_s@7Q1Ad0uoABFj({>aV!kPq7ud+G>Z~+E1s&9p^39utM@HAMahWo0^+uHP33n?uGC@j$Xhr zjE%`i1qX3`ZxZm1fn+?fHN_euxNq`!!CTisAG=8P+vMfzR<=QQ8|b$SAY0sPZf0e? z&_CMx&wak$i@`(xsmR`_&T0XdQ=qiD43fw8u7oh2*vmCD)2E9;yU3q{$nD|z4 zi2gQwSEz)V9rxB4^jHB~cv$qW4qsVe&_1#MLkIKc>0HY@8uPy|oCyMJ#pYHi>nUKJ zI|J91f10MExds&#bJ?3puPg7MJB9Pz(9dfPTBg5h1n8W=Tg3r-vxebx;07Jnt}B1a zD*L#`-oA#i27L&=LHGObLS!ZuF}x4$4bdBd=NG*Y_E9Hn`si~~>fvIJ&zFi;R2uY| zNsM0=SW)Sti=kuC4Q0%~DZsvcU1RuD;67vwmi}XiIR{E#2nWbn_Ezx(8YsV^IzT@z zWe=aJ<=X4RtTTevRnx(MyQG?))!Zcpm6kET40a~bcPp!lCsDkt8l|Ry6QY;gT=pH< z579Tv7;k~+2K`;-3*ioW1RGa_uGhKVtNa|3?N$F$8KCn44Y~sU7<6?N$6@Cr#=i-l z1M~}t-g?n|5*|*VfA+JtbA{tAW;=63|H1>6jw9RfxWov=hbOq6Ymy!lqaN^U(d}PYWGzw~@hN&`Y5?Q6l&Y)J2Z40n$I%WiG zUM$p1T8lah>F>%|wv@IAb!RnGopd$3NB=1GZ6F+O))vx_kO$|}My6n8CpJ4^N{dXlgSqWjSHLv#xrrCJ*FEC#&Z zvkY*#m+`+B{I5L^0Ke4xAmHQPhX6n4c^L3VqI18O^&gh9R}20d(cdNdGep1G%lbEa zo3&c{&@&tGS>GeRTDm9yQNWk-_kh!$$M{wGPxylL=)|W1e=qRo0uur!)iD27!IufX z6R?F6;b#FOmCpknn!s?U!27EhpC|lB%U^>1Z}_;D`aG`j4F9W;UNYeX;GT*%VY#CE zRlsjdVEE5f41JXhzg5AoP^1e5KNh}6YoXnNe?{4Emi`g&UuzwCuoEuK3(_RfnO6G< zN@d&pHmtSKCehj8c@)ws3&POqGlwn!-l#D=tX~Fry@PSx z$?zg4bG|G1bQg0>7jwQWxF%)28gsTv*>)*=+|dipOy>yTmCl<04cBM$44PZ`hz~ii z^g;N4oA!+?&bNU-Q^e94;XRr`H~JY4RUHL?_aw%@S-|io2J6fZKZ;iWt3( zD6bH(7_qITwLT-imgWGmPF{WqW?e!4ni|eb*LY%{cI>J*SX5=88h4GG6jgR#*?io) z?h)!4y1n9A!$D75)KULhmy_PJs5e5_VkO>QOL$%5DX(nTU3kJsQ3obeyIgdIMfFUW znD3%1E$Zf~DWE=XQBBp;Kz-7p9w=`Db%&zh%YQmt^kbovFL`tj8HVT?Vqfy9X0oE# zmwalTqH6D}ZP)!Y&!TFpt6hFtq$p{hpEd}kY!=YH7R5FT=@pA&n}u{9CqK0Bweog7 zNNX+X?W$^5kS?~UyU@NMZMLYBXkU=FThu!h(?BIH>h6jrP*+%#UOfxcl@_(FVjid) z6a_!KQNt@jDL>2U2UAsR+0O}dGq$W83HEaW{X1@2*w5!6tDp}o>KFcn`4!}zresf6 zO?FjMfkpkWsx7~g$}H-8HP=!V)mYREHRtD7(KL(NUOd@VO)VBRSlpIhO=nqDLDky) ziL}_FCRbew>U>3^4cIGDuTY2S{)zGY8hXW|UKeT=c9%ql=^3G(u&50}O{rHhZw;5b z*`h{7=4?>1xgr~|sLO?V-J*)z36!g8RGJsoq(B{hXUve>!clsCSHe@@LWsi+Z!{vHY{>q1jySR(hxG zuRy&m)ZwzP2zB|HEW5@N_fK}6MO!Q?sHSnYTgA^W>LY4_d!jusLh7v@1$0XY8KfFi)tw6a_cPW?J6!8wWzx$Fx6*KcUCgB z*P{MVz&5`l)L~lgVPEb)hkZFrBOw=Ld-!$))OU;X{1;KpB1J8(4*HRgg}RmAEUWZi zLO)(2G8qMzP-7cY*LdbnSnIlk&a|lWaQ1TvonukOkX=g4Eb0u%E~V8L_1p5buFGh> zMd^@TMw={Zp0U8VoVHn1yHV@EoOW7N2(m8PYf*KObL$CrtHs(=!(Jhk^x0 z5B)B}}M{W1C{i@Mld@83diTU5V$4ybo6>Ke$llD1Su z_YTOmlFy>NwQF75C}dIPwHod{D=g}8@nV0RCR@}OidTVZP?XH7?R2gsTV8N2ZKvfH zwWgpD6#oVV_vRebu!A;O)Kb*2gQ6Dodqkp_;ufV>O!fCt(xP6hSnKMeD=g{+&Y}9~ zDvL7ED+#(wQQ}L2z9y8KV+ndZNA^9TuA)&|?@!RP7PV1Z@9(Dh@njZ(Gz{t=qqk-nFPZ^@M+j^kp20tLPR-w||)O zEGpmG?LR<87PZNl@Q+fJMLh@El{D3&X1lumSJ4cM+UrXAucmnx^)O`DP^(4d!PdzMQwmD*Hg@*-gNBs-#}Mb)I#TV{zG)oq7FH4_TNZ%TU5~X zIsYfGBrB>i|zM)pZMF4Q$1&gGw?Us)9A@=wuk zEsAsb&Ga7@#km}(AQ!TgYdlSpjyZ23k42pe*)0@Ql#GH~X^K#46x>S9IkI^|U4?HQ ze#w6;wJMosV(4b$)3nl}et{ABX}Zv&7P-If|1@p1s1~83ib8#lKz5xaJL#V6x`XZ% zO4WV`eKDVi|98a_Cr7$4EdOQVIjTh{556Zl+l7od|K ztm4zBnbmxU{$Tl7ecnInR0nCUoeq@M>3x)S(3i!|8)E0Z3bu0(pqmVo@=!1E+_LPC z;*5v3pEGb=snZ6j_geU%(G$YS#$T4&gVIKpYPc^bEYxvdP*~`|9YJBC6L$xNIOP0h zL17`^5fm1>ad%Ky=)rx&e@B13=U>I4f=;bCdvnk$#T?IXgX5yx!O`e;_@Gl%qIx^_ z&^m2|j)Q7|D=G4NIqN(vQBwMk<21>Zo>Rfn^DGNGEGQq8wG6HzsndrwT$cIhHN?b@ z=PZdHbH>N~f1mR=2K)24`1Y6J4nClimqv_d!~ZFMwoQef%5uH*uY%)81-H@eg*P!f z9JHDbpXfQG09mXMZ!g`452AvA2A*cSugpi&01K%_;Cz9t0#^#WP~b*^Q9#UaKm)7J zePzXTo#2N7E9iECF9O!kNx-S7?Y^=Gs+EjdE15K3&VusgENCaBIL)NjsxJe)y<#g) zei^S6ya@MvyMZ&@DR({7^s}pPq}g(UG+R!PW)q(v&6X3S*>Zw3TTYM`h@Ayur%muS z!B+{sO7Kp>I|W}LwX_LbC9qTAemRNSPka(}P)?!_5}!mJBtD5cNPH4?koY9(An{4m zK{<&!C?`=ji2WPH{>_5lEcgv#`DTIB^fz&O@D+)}e4V2)Ute2vm3B;fY{JL2`T8q? z!+>9{Wq3*DZGhd?42SWl+kCwpeen>=enz`ccNX2FJw%^}W~)|S_*K9Swci394Ik4s z!g3*P)cb0$rD?h+{8Q~)DD}EFO~1Y3r`i+1-_yQN{z9+*oZvqb{1sYIworeaW=(9@ z-=gxuEV7LA4z`oD>0Twg!wYW+P6zi-l`dR%`nbhW-$f1%(Hz}HOrBj9kL%HhTGwpt4>fCDNA#oadz_tuUkd$ha4rm711WpkDe#!K zuKeQ~`*w$`NBO2NFT3B>EBJtx2z}Qzpj}@3UDreU@#^PYC&gNic4*SCUFG^~wZ8{^ zx#(T!+)(Q>_QTtDy+_(|M1RqJ4eTUBzlQWy>{fcTAK?(4tyLSn($XG{BRL?jS0H;h z!|0J(9?*yUbBtqJMfn2bQT^j`O@FpJ>ApvL;Vy~EU84CFZB=Np>nqxCtK0Q*{hjdD?ysQKZAQ8NCh)tofgDi zX$#8Uakps2p2@CO?F07*z-LV~s8xHfDClX`77ETg#kYv1-=e6#7-!_m9m@d!&hY@f zEORi5x*qnt0(hnK6-4YA&+8a(fA4uq+gtq`PgHYMz2kXP?<@KMIvknTkvS*3I2umx zh1wRE-y7GTEv|C>8_HID-;=RAO%Gy5Pt)HIt%c@qLNmPY>Rpr0^zw{27p0yGE%gQ+ z_m!>kMzt4e)&YL6=2CAQb#3;(sV73)MB3*)0A=fdv|K@;;iH_JlXF&tbII;^-7!Tnd3XG#Y?ZHHpK8YV?Nq) zp0C{Tg@TKHt&WYjN1LX1N@n{lvKdEXlkY9!etC-)PQ1-{i@0CP_1iE)0{9eT6weDj zL>~iuSm2|8IzF>Fh!t9=ZxPWvoiqxJ=#kIvNY2R>hW2&c6>Xj?%YzSUBme+JF~epS$fPl4tHn(*n* zxquaPIpAd43Rq9~0?wju0iI1y0WP8E0M8eG2VkpMTSDFV%HUG5xrx6+UJw<2x9~@V zGb)^`gmVy@KPb3c_(z2Q1>qkR&N1P95BGLIDEOY(JSm*lg!7tkG>u!~&{)Tzaa(GI zGg&y3h0`jWCBj)EoTzZRh0`sZQQ=%AoU4R$5D>m;+=?S2{es4=J1U%G!ug(XP6+3u za9$G*>0CCcMCi!@PxpV0?EZXn!p-?wE`Cj+$3;B;6WGrd5`N(`cc961wUvA$6(G; z<9c!xd|xw_}2z5d5S_2`6Ca z7o42Hn&7p9FA}`f&z3g{9u@wG;G=@yE%*_^j|zTF@DqZc6r2KLKX4~qS5PB(ZGh`q zB={zQBLeRhcvRpq(K#VF6^Mlb@lfzu!50bMT5u=bQm{$zsPKCLe^4+YoKfN2E%;G^ zCj`DOI#ei@3wcy)g)*vzQ(Gt=3f?OCCc%4v|Da%0ICl%@sK8?=yR+bg;3q};x@c06 z?bisb4YJN6!CM7x5*QUYB5+jTks#N84Enbed>{A^3Qh`N3$e9Yfvp0gA=Vrfcp`Ka za0)Z55x7X;CV?XY?-qDe;0b|LB>Dmu3EU)bMBv>5j|w~?kcwG`e-_d0H3N_-C}=XpQ<>{buJ6 zoi957*4gel4n`S}~bg_VqdasuPKCcFUnVAan6XI8(=c=>U_D{EP&u9|f^ zCNTbf72{tP{WnDay$aU9r-Jp3>em2!EB=Y`z^?&csQMk?uWSAQxVM<`v#Z_(-0f$4 zMKSY3llTjQ7u51s>541z0Jj(gfU^ZZx19O!S26#~6Bs^N$7rw&zR4|E^F2bpAo?x4D~vpDFMS_wW3wm-vLng|{33bo7!J(2HKu(OZ7NJp87B zjvfmF2GL_Wp4Te|EX6+^eOeA!j(*kg>0%XN1%9_i$NhILU=>XUtj6~(bzo;;R7?TH zIS3}-u8NeF=aax5_1`R(f+yZaDV+&? z8K6$Nv4)2fhPP$8&D0f%gIGl)&MuMkXMBcniPipy7<619%eO|I@GwS_gapzG!$3 zWjFNEPfavB0I1Ul_Es9sOS*wy zNipD80qS@z2cKWiHGn!@3x72{Nf`%z9sJes#LW)i*TY{8-~Q|aehB{R^dLOd=&OJ_ zeGQ&!^mRZTd)h(Z-vHF{JkD;w@4!bL^L`)TWAIC-C*V;5t<|Ohc53xF54s3=A#DIY zA8j^qf^j|FM$ggD=oNaM{zxC-2_&cH)+)5s+IlUi4Ql(eUueJAbiGQSs?X7v>Ff2~ z`j_>y95*@ccRb?wvBT&5lGE)fcAepBc3tGU+x0cqwq9`@ej{kHeG_jT{TcuRfLe67Cke1|X!oFjaCmHql9&M|Ss zoPNc8?_xHL<*krsvvT#`LdmgucgYELuHL;hb(Tdma|WD@h~}_GNv_B-bW?;@Gj_Uq`Z$jrCi=e-!I|vKKe4AspIREQXCE9dTYtO z^n`Yd;`(3H-TDvdxc)r-v;HFRpW-RTpVKzS%XE$7ujw<6AL1#+H|c!mo3z_Cq&;8^ zY2PrOr+eJ@YEQVor+GckYY%#Ut%beMYe(_^SMSRd_Ab;H`cTb+bLMw-&1>X{H6s zV*_pR=+=ap8i;qN&e@z6bi@)pt*O0&@ zshDhF<{TDxCZpXu)|h5*N31v2JrFk&tfge4H8)3VZ7em|J0L0|V3pZ|jrU&h#mKz0 z5!9DR^_$6orOBk36hk6pHI75e>EZx^t#djRr2*fed)jtcc4p4a(aLETs|+Gkv?`w1 z5$jnMPYqmvAKl;7LKh77_C`1NGP-hUVz4ikw5ip}o>(&0lfztMCbq=44uahoP3>si z9gPoI>P*9ibSmTRv4L%7&*H&&FRZZfC1ziLG^y~VI|rk^@qxWrytF+_L%A|wa@ma2 zY-k|Xmle60rwiRoTA3J_1+f+5(%pMw9ysSr3?>y$zHI60 zYE77ly?y3js&j9Dta;O^xHG|BHkjzf<^0&*i=w@Qu{F_n5+owpJ(x_!5|}A6Sfk0k zU~?44fS-)*9E_zTlo|b&Gf!s|0?X#bX(?}&fXmA z>gpJXA}6&blhM5^6Y&A=x{ml2v4tpP)pfe!;18HPVhOH|wa1QtmQ5KOEpt!j0|Zt* zr&9qx8AD=8q+(nZ7ns}CwL2N@pG#X~16}Jnm(5EX0X9BeA^Wh#SO;fql5**?^G{P2 z{1l=zm|B8V!7hN&ZuSiJ#?GMyYm)I@(Sg{?J`9gOj66isOti%YqVe9;IpSZZIS}n_ zU#xJ>$w(R&D+Tvus%2Q*qNP|S`d~CiZyb{~m#8UYS#OW-0meoZcIKJQt!*>UoVR#s zbMx#ub7w7?Gk5Xg<};Tqn>DLt=A30qm!7#~&ayepOBT2vIk|rIT(t2c0C6wkQW8Ca@1Kkp>rF;63YEn$bGN?o-n#4R$4T#2GR^V1I z9mi$eRWLbLb1ZKlmWU<>R`yW0qz@qMJuQz7tWLJ}^iXGVFJF*JV@doe5p_PfMR_|8+KqxE1x}Rs8SObQg?;L{c&<#Hn$PtwM#y&bn7dC=taAXH z-)=L}lNxW*8jALaA6KOsz^4^U3O}vXnhxWP^3u9^FY?iNf5hNN$#^StigHVLsr}L^ zM934eY_32qizQE?urg)M z#P^K1yCmAZE!NXIa7rn!HIh?h^Q3;JK(c-rvySh1G)EV6o%f64Hm2Gj1 zg%sbU@K*a&H!rjn9fUPrQrL=Uq6ezH+N?>M{TD?0VuVU#oluYfu>Y|hm`bR7%T@6$ zF)nTI4J>1{QdW14L#!RH#S_qo4dl=qnY56@cYz75RJYl$LeG<2>J)?(+HnsO6-w>R zSz%>rWdfGao2$2|67YTX=IwA#NOY(wkF~?XV96TLBI#CxTksCtab-nHx(qjh@kF$D zCGO*KpAk*Puu15R@l^#^d21yLuo8gSBA|qt2q zurf(AM%Ko*bYO!uuy-xC;;9T@Zq=5m?KltIED>+L;%c&^@1lFub|X{Nb{b>8XRv!9 zBe0_@<7H2lz%C&hz{K8632Oofe;~d&&iDBlDUV_qyXh&WsPxEIIa@JV8OcZy*otB( zD|{E46cn{8%aG_JI) z8#{}0o(VSJEVK>|KnFMMZP?cI^7?0^mDvKdSQ|PSBn6YDeL2RIjTT81P{IacRO?M&bD`T zb*_mfvv1DQEM5xid$Tmt-fN~A={vJDCv#(#W?382G@X50mS&EJ}Ou zx+{*o?`h6svD^-Hb!}FMv($yuA(_zHIm%WHA;5r3X0z1ZD2Y{gOB6@uawyPmVpWOt zs8MGtbnr>RK#Ipinw2#yVbxyQvd)pgMe>yc1fb}3K%V+Yn$Wg(Td6g%B=Y$#$awms zQ+rZP`P|M(Zv?TYQHfoSx0!0{R!}bOCXyAH)6-O_QV+`>oYr>K2zO312ZM2X`aNY-9?ijUm!Xb&2v}UEk)9Sl(JSeCGWD%`!hx{#4Qol zoeW#HFzJruEQ>?eKCD2ytWL;EYSgtOwwKdNT0gzZY2w{Lie$mJI!|?&eHukwu`vv| zkrt*~$NK@*yV5#E^1cvjx3Zg)HVJzZ?9=R{Efqcc$7k-5TAK-Y#S<;8xS^Qv*>=pCKRwX+dscSuIODqACfjqaWMZ zG)t{<_VU+c9XCizvvK<-*XHp68Ou|h#m<#{pPCJCto++(cUtHj-Z8u(;T39d&JEJr>)G zoe=gqM{4P^_8)6H)IBVwB5&9z^8uXH)Y{u??&fZ`dl)IJ8y}SnBysz~`@-e10ecLg zgJ1<*-t!Fd(v-fXU%h1uS|O+LnKen)Ad;@e-SyHvSc&-d7mEZf-2=cau+~7%j51AE zCv6U5sy=>37h{-OM;d}=&MVYT$P!40KH-OJamSETdDnS=M1|G*0lWmEVt<#f6RsUOjrKa{DY1JH(Ke|B- z2Q2BzM0Zj?pORj)@~}NHvXTov)3HB?Rhnw9$yl@IU3zORvrzigmhgy>b3VddPoKh= zmvuU*Ms4P_kh-E-1S|70r%nq+H;e2<+Lzl%!X3z}XbMBwc_|HYUnY-{llvh<5(ZD8&=0ZD3Wbpouvl871sXMY4-t*Lrc8D~+>19pj)XxI6{OPYw!scO62@Nyz@Z`j zF|?~>gIwh@R1}AuK|KDJEuU%0JHd~N?S9mO9WfrfOX45n8EdoB+9n>UTLEbbni=_Q zOOBs>fVBbCmcnCvCPI*1=b6y58riDaXj`(qw%fH9<#xgfm(H#yD@>zdWnw9uAAqy* zoT_X?E5ILXfWy)b{@9S&A>qJhm$4hqeY^|J-#Ec4Ta?#CbxR`im@`u@&`3 z(XMSD-I~1RX+4_OggN$Sm72w#Z$*p}r?k$Kk9$2Ku~ZSJJnjiqhFbnB<#}?@IR`tGC&<5@nSRRB9`s$2m zy2g)e@8mx5MrD8Qg-nm8J*ndw?R3oko>HsTjyxy+Zur4DZ~)H*^NhD+_>sQM=P|9c zQxdDK(pUD#`iPt{Ert3vBbJZmjRCTR!g@BKjgsP_YH^98Y^0=6cjZT!Rs6 zif#=o@eHq#Uhe@^UaGR$TZ*%wL$#`})M&}{K&CZBSNznNaqCl$o;Uh_*_3PU`mvLo zHJave)R5)^5)3jK5Og79@av97SIqWG8dGs)aHHQ%;IR5dM-6%!l=`BkT;jP=$`S z18*nZt`Jw3t6~|~t*gVjI<2cK;LfqAhvAb$$6qN3yU?0K@kLsp(4yi{5uD7p5XRrw zLDdG{?h>wkyx6MBu&(Zqdz=M5Z0KRPj=w)#9J){sR0TMo!BKomTnmnz^b0j|l7p{H zs7LFZ1_VyG7F>X5$AcqB+|3$(uCy9|wgD_>4Tbvh-Ht$@I5_ACZ43lB)`8$?i^pbo zYaHkdZimyY2XwS`WYiJx`CK)+R#|2ghoX7_Kb7u)2DC&f>YCE@_7%&qISp0Apao*+pCcCBx)LEm!LhU5Baz6AE=517*He z4e`j+>T)2>^giIOFo1#G`g_1P=^Jp zR=aXlE7+}CwH7MGUj<3qX1_dMA3`8rholyZ;NcVSsdlh09L1b=6r|gsRQ-jRQR(J- zU{x$+wGrXAT?U#X2;@*cyOkbaz&DL2g$yj;cvz(>QG$XR109hW-Az1u(cC|?I7XQi z_aa7tYQ%GCGdJSDVuHt?$K%6DN4(o%%^*F13E^`)Lv6m`$jhNNCw}=n;KE;vEA(Ny zV4Q~9Jd#QZ!M@`-)F5-_YH%*Iytu5O2I(ryR<4%3f%&R?xbDlK?#buefbQd$ zCtTyn2{fmX0LLn$f`+4ozI-%%RV5F(H<5%%^Avil`c^?ht1Q&>r0Nv0rLNmlU@l2TVR{08=Why%Bz8! zVw&f*Ua7+kIORdW{eE6G{Dp3JrCt>p`2*_wLnwx&g6Gy{Mh!6&X+9JS;Ja`-i;&Jj zBfk!W-9~8SUD$nJs|=0u+rBCO-^p@`cx)&zF| zJaNIg#HbPqY#r-~%i?(1kC9*FKa?vgH@J(dtd3r#CQGQ!BrIs0C%hnjsZ zAHyIrhD0fyHIPz6&9INC@KvQHw3SnlB0WBbTk~L8VJ_Sr9JwVl@)>jj5FL>Q%h#;y zypj}FYi^&;ItygY8Trh}r`&a(P+4fBxF1l(QN_KOp}f@a{E$~@ zHmaTtaM;*46>#OH;$4^KvBO-jFif00c%@F&?xg z1{#S10S|tMpX(nDj$VyniXYbDAG;rU0CpT2BJhB$=m-PXq2TBt9-cg{M1g1E$V<`% zFCmf0Vmk6DVh1kz6HDvJqnOqbBYa7ehmHi1-Qnor3&3)PTE46+66)Y0wm5nVmxK-U z*-I`#usU~1TdE4AgdFp3onhFJ3rQZ*l; zFQWi0SRsDPss_zbKUixl7AGA|qKySmcLF#VI2pJY7!2IV6%*HK9(Cc}DkwE)WMi8z z3~S<}gF_n`^yL$Bfbun*xc3|tHh25v_LZH^HBI4uRd5v(-Q zO`v=Z40=eP89Z;5W++}+Z% zxo7U&S=}?wY?;}-xqEKQ%z4c%{Cmt!9_BMOYGnIOxt~zgpZ@!Mgg;j^<&;IzUpBJ7 zK1-NLhyfy@UT|JQ0vgU^i17pi zi(KZzI@pkECgHaUYI4o|Kk6@z0RO!a`TIK~xu#?=;L|J`$7j72cyBn0zvr$79J7GW z2E2$m@ZGwLup+Mowi2gy7l2*~xC}VIo^<~90~xP8+~lRM^26Mqc|fZvucQpy@YZ0J z&(N0P42w?~`7;_hor7$Ol*)X-#pl5G=^>XV`nq!fMm6}PK0(66nWwO{Ms%9dZuM_A z&KwZ~{5GWzW%*o=KRw}d%e`okbtVY;5_q#lPASz{UO#vXq>Sw?pCPAk_Qof-{W-p! zs%$foBma_yYUxEN%V&q`G!&+(1t+D=={MIEfIde=ox}6FYHyZ@Y*ob7}QBO0`k{+6cu`wV0)(zMKx*K#9y}20})0^?0i+{5~&%~!I zEr87;oh$e}P^>3Ev5#|F?vGaV0iWRZL9Z7Ps=$KkV!C{*U_i L|J?dtjllm06&OgG literal 41984 zcmeIbd3+q@kvCr5Gt)EE(v0TNY2CJE#@2!43mc5FZ21BkACir^B9Eo9En8B{Gm;Nv z%Mu102xlPNB$$u|maqv4*EV<A}5 zz;aVJnr0deXUED}P_!5VE%@DRD2Y`HpYt1{+o}ROt$13=wC~pR?a%*I&D3@4K6P1E zcB>i!8*Fwaw^hUJX>5a+Ho*qCDqNU^4Fjb&1z}QMu`1q-P~8sryZlB5jqWLh^?C0V zGj7#F#Y1%^s*-sTt!{^ho-8WnW`_{chTGoDH7i zT)Jl;XVQc2eVj=TdiHT9J!tHcOybu^P$!r8)qoA~>v?y9W6Zq;&~^{2G{hd|-CeeK zxeQ~Np?J5UGd7jg4|Ac0KnspD8wmSj^;(~@Fi4bq$j&KPR7WU(`LTGO-G88fXJS?r9J z)=Xwk<38d>*KkW8X-6NaNcR!8?62t~Z0dj1N9szIlk+2b-Htr&OAMU8#JJt(%5-se zAmXaqfx%vWBLhc{aa(SJF7A)IBksB#0TyK72r_O9mg~|UQBTBEw*%e3{6+?jAmgX> z;#sH;_vYDr&5Unmqn8RdGrlnjFun_*<&^Qw;>Iw;-0{u%4bh!Fz6+l`wlJqxNXGHw zn{!*zE5?oQIa>Dk=KOz6+p)s`sO{3@`y2?fXXe<~x%Ty3`+A;zoo8Rqx3Ba0%0APn zjRyf<>A}xB;Z|0O!Ot3b@Uuo9{H&1&KWo&3-hHf55Bm0rMvk#>Ko_C_+5nTF#DW)@ zGxvEwI~H&W#xO%^iMfBvNh(}&sfJWX8bd`8HPF}+W0;|e2)pcvz?K=q3{^zvv?Bsr zY78?}5n-tv5!iBLn4v@jM34)B>%&|VL!h+)C?ZI!@VREjFhdKInH@n?fBPy7=T}YR zmk-^U(_5r+<9iENPxTgCI#adN#1N^|_!uHp8XrTXMiN78-I;1xi%_l;wlcM{(Dvzh zX+2_&S7Odp=(knrn8P*sYx*r$?LX?b9CNq<|C*S?mH%sE4twLTiaG3`|1jp%Rj7^- zH6ljcjwpKYjSQUN#JC;BDs|}-QE$Xsx1*2+890KBpAyB8*F9|_)+K^q&_YHr1OcPS zVF&_7vBMAqj1q?-$WDD^F)Fo>#r4z@MwzchGKROzbL*vt5~Y?Ry{^bw#+T}QM*$|(GxGCKZ~^(fA% zN4cs-`RPmGxP|J<>#y~bt-sh3Kb=7_G_vk>|4H4aZ-f8;%lIE@gVHm%!T-LWbi#s* z_etRh#oEz>_YGpiM5t(AO0Ihq8AC9vkC0F?ScQ(x?YJHbcR%v#DfD>Lx&**v)vJQ-X$8lz=N6Hp8=*X!u z_5I%194uTKq9a(ExRZBfve&nJlTT#yM@Ony;GePtrhAjGe4s#h1d9WA@{gH3K0B5s zVVcQ(%rugIBV7r1NnZUGMzSTBbD@!3m&=)BB!_c3Q;g(Sayf-Y^7pwMy*SS+<$LII zl+Uk`S{SI8Y#RZ?UDsGLWduFe{ho0k6P#MzCFTyC4iITlkKQFsCSC z9!nm&Nfkq`hOb-`}WG!vu{|gSbgtPh-cq^g&6xr z6yn{tTacoCJLS4psf-~|ar|N`)@7G(((_>ROM*(_ds^{(nl$qWtjTV+d$_jU$W}MX zDKQtcjcm%5xi=%jDI+K)Tg82xyjfImO3ckFCbA&(W((e*(ZeaKtVFKN0~r}k8G+tx z6(7%(#VM++Ni4V}SMU=VJ)EM-+L|l#U`B>hMtX9S4`s^Y6jjz_7TlIA_)taL^F z=gNFCBf}{py}8K`XUgIfRThu5k-l8PPh|u-CFWsg_vgy|O{OePiNSg^W`rq&X#q2v$F`BUk1zmO(0HIAsKp zF1r=~mXncyWKM};P}Qe^nJEk#Glhwn7pFtG#4v{Hr~OX4u7xgcO2<+dH@m{PQ5425 zQy80WVZ_+u5gk`vB_$~gL36YQ$>Nw4Mxr@dn@e)<%}V8xvPdp2i{vs>7{BHym&tqd zv-ryEe?h>AISfI-nCdVDxzJwSbE>_hk2bpro~8g|NV3*T0}*Qz-R2&1dUaxC3 zrpTtiLJTo2#0_&zgi0jQb^63oNpv@QlXcKo?@7+Z1)0i(?>T*@36dB@`<<@5@>meY z#1NxQoM*Y|A7L{%VLgWUWj2)VZ$zD9qrj)|a@(jY=B>3cZDbnPHA2=Hk{Zt_7P}hB z*h9t+;A&V;B5ScLb`6Mam*#n=x7cc2*0o?w+25Ql>`dkEZ%B(MdrHKfI>Y)5bn`aZ z0M6xDO8`(O54EFC1?f6923%6NMvuFATeD`@B=rT(>9Rh{7q|5}zKHQWEwJ&{Q(#%w z0o&h{HbJd5(VzG>g5xtsC7i>xYRKDvb|ynP)~2XFh4C-?v2hmtW^rDzE}qF3r&tG( zH|nlm9ClkDX4K<#P1ldFG$NkZ^`NDWzHOIIpDg0w-T-dIP-PZ)OE%^`6uPaCO64fK&)0A+n~Gm!U;r{R&* zP0&*}$|)AO=KdC7s=v4_aCzU!b;cm0NRE$0C46)%gc|Y<>o#1c9CuAQPRK%R7f~ts zYsqyENS?p9Fbgl`pO12B(i^bOF;#yNOqxe9@w;HnY?WYQA7fG{3(Foeh1E-L)GFYp zg|&~a?*E`nDXDl{3@aD(Qyvs)g3C)2Tx6QyQqu$%>=4sNRQ$OM+D3UKDH2N;bCmru z?P7g&I9;T;n>LCm*F0|e8VqmjL7>O>p9=}QDGlo|l&&e6 zW;7T&Z?la3=Vek0*AzQ#rla-FzLb~6-0p5F(TECj?R5>!Lxbjd8(qcqhSxfTvP_o| zi$iI?Z-lGef*~Z8vK~TW!)2<(`n6J}sHZq;ggp^M)rzZOeG+1g`tdfYiBQ(VU`5^N z?gqNMN7X3oR;HtTyJGw2Fv{J*w?T?-zR%^0eG2pl`#knHz*1j<}S%@Zs`ctr`h)G_O@feRBBGQpH7$;sff$xN+L!X5l#7> z+sAqWI+V}J8k-YY1hw#)q~8^#cx-Nq(j@d z^bo;+H^MR_oVWq=g=s4$nkETOL6W6&(d!z0!w#1m}t-l91>Ir)aV_yW;*b(-OUI0~LPx2;C zMm#o07cj$}{A60*2pfelj9#;$DQqO)O$)ZNppo?WoSX~6s5kv38v=gc%{|kvl~`Od z%Eo24r|PqWh<@XfzMsM{bgs&y9k4ZN>E=NBYfMm*hX|xS9!}-`VdrUrqM8v zLccZh4C_m{Tq(O^~%P-b^n?F|+(^pHEF(KR90K8-mFidn0QC6R}34 zcw5wSe7d(VhOSb7^ETJE%anp`mxDh+SBeW`VMztcJ4JF(*n_~O#^4m1x{5;;zw+p| z`^n37;}~`$IHg>l2DfWMi>^-_VXxaxEfPYvj=sShEt zl{YTVt$T60?yiyzncCZx{?QpKJx#aA>ROsOsBA9BItTrn%3x>eC~PrD7KoAm0K9R+ z2)A}&Oo+e;x3nS}bS&jqP`93iptKMoGq_&6b=qtTMT1|Y>g4F$BZubF9OXvXElO2- zx+>2>rSu}ZK2N3VgHFt0S*=10yA24Zujk(4^r;oJ`hHb6Uc>QzcmnDsu~|)d*7ID= z;%#}UuLH?9qTb@DZ_W#FLf9MjeG(HxdDI{A$1wcN>b<4K7S^_A)C`+9_mnhylVNoH zdUx0?xi^|u9L&BKseCg4+ms|%1saF%WM@5dc(mDdA@L7q`>+XGB=i` zZbs|Zh4Xl!XhaK;6fUs-2}$9CI$qHk(NH*4vVm802D7k|GoZt|!KOdL*Zd9a7bEN| z*%0-GePQp-Jr_)U|NZxi1I4&~;(4zKake~SsHocCU?D? z$F`G#v1ddhST@38X2axgBpMBSqJ@z{Ves4>3`aq|h~(I}S;imngd?$+fMV{=%4M7> z*LPGd!_JjcpVg>O3JYPZ)^F~ywlLzidia91>2|()ozR+&;Z`5>W0wM27NZi<$BXUY zw5&L9rG$}=^}I8MKsw&@h7)=_0j)ifln(#aFw^L$`{Zg^`}sOD7bAvuK1Q9-dKtAG z`4||oGQI=IdjP9&w7D3#JMFFe>NI`eU+ESwnaphMH{$1N-!B?X0@5q8L z^(qvM&P4vyYmCie?4KD!`@2&Z4dy7%$_nE-h4-$D`O14&LBLqzFa!Z(slyNi3ZtNleSF9Lt+HBpXN|`cWt_1rM+gW<#iEp`l* zo0fyTF^-d#`EMrWW?yTUjteKGjyvW4kP-A@?$4NesT^SlLd@o!^9e`pq%FsL!DX5} zFBjH_Q}BXm%)J^JM=-;>8)lDS0(0Y>8NJ)x5R)4;4YhLO-!MT=_i@Ny#cBC+EZ^XH z;}@K{1}y7GD5Kc=F(98fyooFBa>ssxYvToaah)kQj&$$7AqeYx2LU6R9z(hHZ(!*} z`Y3jJihc%jBWaomOpFxip*|<8^#7)Qs^MeY;DBFz1DKIuW*1{MZ;f_sbtb(x_ zj8!r=lQFJBY!+kHz>bHE;erkmisW8k>TS^b(B<4D)w>L?Q`_&~AR(X+BjmdqwyLZ3@8Cs^`gxf0{tu&AP(;i~ zUhEwZVQlWgc~PG}%-XEqG7BpY>vupJO~2s32adddVAhw8N6^jz%-qH2W2C6Xk3PH? zndcUuZS>)NK-J+Clrn)cz7j;3Xnr`q`n*!>k5E%rd~Y-m`xCHeu!J9=thfHmL_vKV zYmL7!iZw>^5@aj4-UHdNJRFMV2_yDCCr7X>C zfM_@zj=2~K9?ze0+>ZjVLNOcfR6RCafJIz^QxA56s!cc;4y5p8oN0LgZ1)&UdjXO? zFwOD-n{wKuaK3nOir;bm?ri7hMNH-VJSNZAmr^KSE}=)TlH@|>f#Q?3DHu0FJIp)T7+IFAx(x{X)MetOZ0HbzH5L{*51DaO%UH5UtYp^7XwUtYfljQ57p? zm1ezHnw{78(FtlimvI6fVHEo&fUwG$Q&T@FTw_%*S{p8p)W#4OO8deO#~I0Waq%-Qy-ZWn+zU?kJN=*GgZf63cyua zl~S9%;uw!y>ns3!_ZP#17r^a%3gHRXRM63iy$axWQB_zTrX=1GoUdh*Ghhd5Og^|Gic?4H6rTqf z^ZA<1=K#fgiPkqFjvb3m=J`}ckxU-%6%}*mDwOE~7uKNf<2wINo<+w(0bzb>M%mYz^s|(j4TZ8KtA=i&<9SCR6 zXFeWtA^3+%Ae^md2sG+Lf5bJ4tE1ApU5o{T#tPTMa9l$bi?iCVa>=%T8ZB(UaG{3DmtXmK z@0{7x+%%(kMhjLigwI>_0=|R3F$ojP#(JW+CIN3BO!o9|NwLHVY@B?4@Yc0c2PW)^M~ewW~i7=zb*M1Ou6^g?*o1cNq47~USbqpX0Q z!qUaSDaVqiK}#d+iVV6c%i9H=npNzwlY7xNQ3 zmr|@V|2M)}D)3^_`GDk|U%)ai2X{@li~hZo>+*6D=Uq{{YeGA1DPzw0(SMj|P)g6o zS>j;uvEm?|sAaejxIur`c1`%2o%eN(t^Ex08q^@Ae=kr1&m4oz2Hk=BM(Cj;#_uoe zEHr5hI!*eDhO|hP zcx)ES7?~}jR1tgZBKXsw z{K5@@Ps73>eJ;Qr+fem-Wsv3qqK8N+sVbJ-G>J9*9JLG5cg44hMe=&Fye!C;*9qtA zMXcw}$m`sVDG%|6do6eE5S1fh%}rC4h*NVBWf7Z@)*@+szGDmOtCE(7eUt|3{px== z<|2+xqA^hYwM>;#lcbq7Or3>KGI83+gG^1sXQ`OFRjBE-7G;L$kupwOOq+$eUUYWQ zwQL@ta;kl*9g{njCZlay^gisG7a?GWV z&}Xvp-AsQkX^%0#eOz-N3*0C)Ns_xlBy&WwFEUv5u94=?r zDe%rJ#-|AXkqKu(|4EZexh#)MTpu_asmsf!18%N351ReeX9GT1&hTHV82+Jx;X?x7 z6HdM0YYHFMTIh*j8}bg6t^|Bz;yS=TMK?h{Br-J4F5{ zM~Zs@{~Toa;Yd&3n$cGyKERJheb`0>^BMj+kKs!K8_a&-`2sH%_<7;~TmISLHwc`T z$1-;b{(!jy_z{7X0oHRz-Y{Sx{Gidf!s!&rVU#sPd(yZDseWxC&8Am9AI>YLU34Sh zr5eKr1m7?C3H?rRek7dP^dRtaG=^W*KLhx*i}ADF3=g=O^J~E`@i6Cp4|D!Sa82?? zHRjwQd9RYZdG4=*)9L;$;Ah+?0cUvL#?bBy`2f30--n$KY3}?C#}E8K5vM*G-K}AM zK<_eWdsPJdt&zPs%Q9bz=0StY#qj&C30R}8aZd*PlzS>**wX~K$1?+vOM%B+ za{xaqoIiQklfQ5+%+KyowKRl&#eTamzZm2CVx%t0+k{@%1`pTLn7IL*1R%>?37I1l z&tF}`(ds(i9ln*=mT$1BwZXaAUvE@YX|ilCw%ogg`Z^t~IA*x$d7JumpwHu`KikyC z=t0cg+iD4~>wMnImAZ$LHq}=?*W;n9Y-(-!j(iVYZBtiQ?F4m$O<`>h>Z3Mw*Mw0} z_bLju{K@5^?+B%A$)jr~a(Q1Twk4lx5OtVhTk@%SGE>+2x+kvG12o5`3aaON0<=I; zQojIg5K8GRpikKp>kQE`n_`_Ix{yO4biO=cr5>iWHg&vet|v^F+0+fFUzj%8)Jv#e zn6}x}Pb-E&C2i{FicwHk+0@%r*Mhp*rq);70P03X!Ojtsa7-v=XBqwD6t+dxeFEKq zMXpo#3G_Q`bJ)&zQHOGR&!)`aE&1i-ovP9vubS_vpaPpZTy2zmlff)LF&zJyq0VQ`3v@%&($zZR#(T59e3YLYoR#{T--_6oon%R71T&-9~rT zd?~+%zGGA06>28drG%O1DWUGLsUOdu?iw z_XyISwy6thp9fWegI3nkH}M(wB)Z?G78ZRse=@n7Robts|0RD4Jz!I9Wk1ZHN-qd? zC*2-+Gk+TW!KTjg{w{wy?QY?Gchb{k`GIq3IZjcT`bJp@)Q5$-t#r3gzZ2>@-`#=v zo^$Cvn|dg4Xa2e5#m11^@2#SPbRHGhlveye{&`etQ@@&c%sr24Z0h}qg@JiARZ-&W z^J$(?chb6U=Ad2wW(C~{J<(& zfa3;EyS939U=5ApkVtyLq#x$D6U|d;Ev2DA2i>hG-&?g8fqK}c-Y;JT>a#X=i_s41 zMVqRs;Ivn5>fJKVchaVgS8>{}Z0ZKl@<*GxzLL{C=S%6n`Vi}E6Y4hF>0?`F&R2H+ zFmeefH*a9jm-ZK58CXxBwyEaouD}NR@B&WbKG7ezoUT~Jl=92v^f{rf^PN@R=eeAo zv8g#YRJxp=wJ8_UuAqOisUoCZL9g1>8x#6GSJE+?dK)KFSJF>xDq*ZLK19E=sjG}s z;6wB~o4NpLo%EhftwmZVdE3~s>wFI&Z6g)f)FVjSNToLQZdsovPBk`VqP#dwwW;G( z$K0E!#io8%wL7qh&b6r<%8$9bXrWDgtbBK%i!QRM`p_z)o7!#aoKPyzO;^~|^_9om z3F@|~yDE1F64d9UdG`f2(~wPl!uw%RBR2H~q-~+=Z0egx+d{Y46iw{&Y^A$wDui>a zt@NNxU0i%epobo{sg1=S2lb?)WK?aV=j^n71qW#xybwQVLQEHQ}a;5 zcKWGJorEWP>ECSXZFr)Wes5D>tLXFe(fc;_ZJa0ek#DhTl^JN2eyUZJ*wRlkg}R-d zE_^J|PfKk|i#{C~pmjp2ewL(oR=y;w4KEFeu)my z^+?-IV>a~*{hNWS=%7u#1{mRQ;}nEw@mfO%=O;61bIWY-*ePw}IQJ!KR*Z zQ}Cm-%%IL`GzvQHy z5b6NFlcG3yFa2Dl`9>m-828a{Z0eint@qI%ZOZai1n(nl4cFm1-$tSQib8o4k=A0T zg?#fpAE!k^snS1AtJ0DN&Vw!y>HvL<>Vtebq|x0t?9pjGA0N_l=>tZM>an}liSK^m z*+oD%-G`$f57kuj?*2ZU`Q*y)!KzTFC{lCzt;nm>FOkeagMk@-LlZ}5vN?#y#s0vZ zG{>dg6&&ANxQR6v|^37jjiRp4@g zYXn{@Fb=3u51@hBC|OoS*9(3dU^(3*@MXXnIte%hr6tQ6s8%9vtwh+VI_GTx#QB7r zDCNtE(hf)(c-Zgd>MH>cR&2rdDKTClcp>(TyMQy?Av?xaeQNd1G*ix@X39C#OgV>| zDd$i#+PJ9Z`Rd`g}0nH)Wp?6O_NUiz}(O0ymkn2sYRXDK+R-frnAzUT~6SSG!*YG*GX>LayDc8ir=k!|qo7xxh2-!}@i#UvtmX zo{qfeep37P#D50-m$GB-qx$O;Pk{d|@aJjk3+DoMmxVoT+O4%K^%dGkWUgn0);9?! zFZ$t$^F4lj5@LOa*m;Haju>NcB*W6bK-VON? za1I6!A(gGYLf}zt@`M{Sw(Sd^UeWMPJzDmxXHf9H+KZ7lJbSfu6W{QBQ-7iQUp*&9 zTd#&s)*5@Y0}~D78d$heFVnw0vBW6T?<}e`dbPiJry7Ux$owJ6+hPn#y?UjNgV0Hk zVIS@lI4F>9TL?Z|epLT-V7YNr`$gF*<7NF1h24P9PS|GrT>D1B4&$42WYR9c6O;A> zz5x72)CG4+;I~7Drr%oo2sq?>9FVm=rax8wBqSdxIA+{0lCNt=CcSUGu6-%u_ug;Y zwsF!H@57S%Y4LWe{>AERy`L8UKP=vOSR|j)9*@lTJf*!+&9(k;;oaV+kn3^OeGl-5 zwOJJpdA|$}_pvJ{e#P4>I`;}36!@ILpX={Nj(LBsA1wQ&_c!{d3jYk8d?DWpQu+&0 z?yHcT>pmu&Kk9t0bWHe%X}$NT_BuUUy~sCJ^OarbYth=UifYvweXD`j)}SA1hl{#= zty;a{yuN&kIQ1#ISzky`(V%MypvV0fX)Yd>ag^otzX^D?`%QSV)c+QGV1xf1ZAA0!#mF;~`ZfO{ zSaqBBnATtL4dCA`e9^z$b(Z%Pf4=@`^)LN}y07ege}&E-sL(k|?9ko~RGT|&Z7tq9 zbFxS_h~!L>oGFqV@ve67K#8l&u&cXlojFe|Z_|0iwdp+iK22QT9a=0n-*X!*+3UVd zn^1@uP224Iuz8y{rSu@JK$Ljgh@&p|o2{0$5Mcf+m_l69LboM!-dMF5pGN zZwG7@ZHuUj`U@6|&W*S~)L#%6ewXk^g)=6c1H!omlCwh(3jdJsKPmhp!Z|9OXR*ti z9eP%Do)pfHh4W+KXd2hTrLl}lOWUvz_`FMfrkVh6?jshX0p87 zWJ_uVZxz@koVeg)0uKp1D)6L0ciw%pCRCfp8d~$X_2c0DuwYE!A%RB)o|IHApHpk{ zxewIlv-|?VTfzTDL0mXv0uKr2NIq*mDx4F7pA?*MvW9jEaIPA`YXhQ9@K(V$3LY1k zQNhQA|DfQ91V1A9QNf=Jd>%Sa2jit-{|Z_^7}K1s)N2 zLf}b}p#s)ZBe1qW>=e9J@Qs4U3+^LVXjJeq;Xf$&A;FIbenQ}z;LHwDNOTIU4RH^z z4RH@&Ae`0^m$FgtxZvG@hycPlB%C9HpAdKwd4E{&rpQoOY8{qZ3%)>LYnWv=3LY2O z4Vl@YQNhOq9ujy|@}7jeE2KqOTdlxWfo%~Pzk-he-=2R+U`_M@_XmL+1&#`QP~Z`P zCj?RfeQqV7I9=aBJe~JTS&#?Pk{>rZWK7G_$8dW zK;TA!qXKJ6S+jLvGjW!2+ zyz@}f-M$u_t*j5u2CT1Se6*bL?(*{h@2ldImzwIuj8C`_aL+`RiB_{rYdPb;tYZ8g zk$*+xe^J5mAFW{dcdAwaURKcoI4gK5;8&`y1bnkL4mi7*@u^h_z`qGF?kQ%zYmx=H zcw&n2id}%W8^eGT1rJVO{x7STe@{8X`zjfp7h;)tuy7)4=q|n)@a+P&ZGmv!kzC)c;M9YnGu!f+V*gho zcO&&hkxY5-2i`5P(064}wGyB8c<}b(k3U`O2lS(rbhK6gFc06EprggYfMK+wj+QF| zEWw|SHZ21zL%ZtuTwWz$Ilf;<$1c4Vu#zSLR^cm>n;F2{0ChYoGYj|`Akla>N*#*=FrzENN~@U3(a@E$-N_XSo0-wvqb`MXuX z`v7%3W4Q*n1&A*Q!#7H3IEiQnp2TMsHLQ%*0Uv}d8Vv#Jc=F~_;JW~I+6`OqoB*Is zSHTtytE4M|?}aTI##$%v5!{>6Xg?rMPqE(8aKh3B{Ax-7KLDuXiJ&dOuLIQSAne7H z27o$FVYUOm0Z_*?K7GJ%1k~~5js^G`SgO%y0d=fxQ@}q5h!a6rs$mB*1pF{8)#(}7 zsMD8VolakdT{?Xg7KLcNHWhG#R*!yg8SpS&0emj%Y~h^aBXl>tfZg~pdXwIz_wekK zTk~q=+A8f5EvXG@!`ctD|D)-8r9MTUtuN6p(Rb;O>*u;|aXsq#d)Iedru$>==iL)M z&7Osxi#$6#U-Ep*^Ut1;aj$XG_=7R%-Rr%@`*+?~z2&}1zG=R&f3km$zsG--|HuB{ z`Tb_IIomwn>@kPU+sy}`A@l?HD4%#`zdnHzJ2Q*%Me}T&qMgc{jg#wBd5_7tUvAzK zIrYrt-H#K-Q}ZssiR-Dn=f>$fV*UUp#r*vwUijDx-+D35{1|V3L`f4P&O}5Gun3~+ zclcvGxc)@+7<4zDqUZDQW_~Of*QyeiTt8jnma9?fk?S8s9+4Q&%JZ4|J0-aAIG6k; zy(0NdS|s`L?NgE;PZ^eQev`JA@XTP+$r2nV;@U3xD+u3BhF7(AmFTV3_K6>AZMqip zWAtV1dFs*si5}EnqT~9@^b7qp;NPQW*AHl`>kYcj^-uJG>m_>5^((r_U8CLSuEP6Q z^ij`#?aRh~?Rn#6`l9y{?IrJ%+E2Zo*M95$f|lp|BHk}*m;1h_z3=-ZnrYtB#9&)bd`rKT8tmyx&EJ$xXixNax2E>=cg^nx z50ZAKGv%=*T4r_vYwg-G)RRoCu)4OhO2uRa(`R#XM>5{EeYIuvwkLWMU4uPVKTD}J zkvb)7iPazqhA9(V9%g_srVZLMwXu zw){>?pGhQRKKr?$85 ziuVlK;!H!w+C;p2Re$fE_JMdmNa+Ebv@$Wc)#_e2)YA*4T-GA1Zy=sj_~IQy@!p=n zJz2c8dRszSHfV9)OdVL!V4}}<3OjfDnY=rlmiG_NK(gHoXA-m};9y|sX2@|c68f#R ziEi}JuEF`U(DRhX`LemQv$fyq-_vIer8@QuB$_v#jyoOPB}4sPxLlOjvp(KClvo|_ zNrHrfyM~fU^s2TV>Dlq*9*`b5}Axa1L!r40f*TSTZNA1lai4 zhVj7?ry4k8qvT8HoqLA7;HTiFq0}No4mJUdl~(sqZ(=^pTb=CL86Qk6??dt)NaKsRsm4f*))G{ot(PGRgeNdVsH;&1Yi`0;@wXclt z2F6MiHfvUMYuof$a~3XcZk{>&oEeK|pR;gb^Q9>-5ZAkuJxG)(VqJeKzB#d~pVN91sF$55L$lGBBzw?- z`n%D4IOD(o9Js_vq6aXoT(-I$k=W|p2}Y`OAeAP#dOU7>Fppe?#Cci7{GQIvB|V8= zaCyzJE*0OB$l~$HSA9B_#a-Nw4+JH$IZM!$5m>WX?L%E%iBu|wx3cFHMk~VjPFc!i zXSLn6HqmG8jAMvoY+T+CMo+g4plp76t&`2fsL5KHWT&(z5CAh;m-Xb3yt3jRmo2hZ zMd55#hc&PaE4yqK(^-0Rhwc3Bot+CYwDD0Y+vBGW*YOfEM#r-krANT{H03-V4yQ>w zwd;=;Ii=5!7g%Bq^>>eFXL`!`G@dZVbK&*17+jh;UhkrK*VaUL>)>fqd9IO|Dm_=G0EMmPo=nRI z2M5#wocd%PPRmog?q8L>WGiAW*6LjeS`_c^N?4(LJ(0b?q2+Bo=n^U3yzrX)bekGli*~|PFOh6nyuTZwJn5`X zS_2oy`x1mg6CDr`Kd}9YZm8;4JJJRtkeTG&?Ar z$X>kIf>f%@8c;33V_nJ=gauY&GZGg{Ez((Fd1`q-G@(&fZB{v8`>IXbV4jd@SCt=A zh>by$-LEC3OZ9QV+p+b^@{}|gHi$j_@!sXw{bR!sPbIKc=uPldIagV0$3QXI!Lfu` zm0UM~Ne((C&{JDt)6kYJiR8khwM&{sT!QshOJ}DzL{=6(y@{kOu#gjj0dw@GA?Q$< zxiK?5XJeb;j4L9O)QU;FZ&UA{j-J769pl<6lcOV^gy)ywSN9UTtmO7gf>X+}o^C8< zGJL0qC8)!C1R)Hw@rld(H_M6#iSgbuN;w@?#%e~%+QjB|tg{C9ti_rKe=>INp{1vO6mw>%1W&y&NkwbuN(MIjUvB z*uN)}V2>2x5B6;8;gf+(DtCA3jB&ArarN~`6WG|F;8%4?2>)k>nFHXH%(;Ub5)E`0k#*p+4sfipzpG5LK}Q z0-pBW5=`=#C(#oW8{0C6;K|xv@n@J$AI1-5Ht{+$zasTg5bsXC!B*m}WXF-!vn=PfT+% z+r%`>UKXe6>`h{tdFmc9oshdlOtZ6gh-u!b8^m;i><`nFEOOHnk8!m-OtaFP!!#$o zH%xP6Yv@o;6l?A8UW7$Kk``gm({i<;M}zL{!AkrL=fjxw2Rl1Asl#IGMAVU3N*xP_ zHg86*P3oeSwX#5jN6`P0_DWY+YQ>hs+F)}W$NzF-F<@b?OLVJF=m@m)F~(quwWV2E zy%buV`8Mm+8(c(lIo$w?P5{V}0O1$X_8Ks?I+4WWu@h-Ln$q(~QbmB=wn;BfvC>jA zg&f8+)zqb+Tvl2LbYQlooP(&9DW^YY-HJgd!@@V40eiDaEU|ML9Tx8`ctLA#746ZF zW4jzidMl|$YKBA@cLbLttv(0GT!MhOwS9YHm&3{llWJ#@u7fipN`qMg?7tqIvN%
TDYQ={$9;v21j1?|BG2Hk-FF-OkYwjLd zq;j^!B9zNvd$RY&%3FNm=_u|@*d?&26E>)m=fpH6wQ`P`R9ZTmh#4x9h;ke4?2{uX z;x<^>(~tEroaZcI(8z4xO6j8o%2Oj$eaM#zeS)VV#ehvQIMKE1GN;+02=WI16so@y}XFby~6PGP`| zbYi+)c(tKgS1PMWUe98(S9){eEMXmlWutR4r@ZIfFG<-=lSOaEL0KjuJKZ-kiQ65k zpLZ=Q5ukdo0Oc4oJu7~wW{vMouP?G@e7ow-p@)d3k3ispbV6?EStV-$cvV7N`v6wg zX_lH4o%yoKJ}r@&X5-H0+~M&6IhDsXIUUc*!|Cj641sKxJX)Ix41WM(ayrAV3@rvi(2W3J+DJEjp@yc>XPV9$~q8D*HRN;(|) zRNZVxbE2C%$1{v(&RNvb%9el*8-tZL)?ux%b|sQLqOh6jA0)XI%t5>tWS4!vk~(?$ zoWl{h*6zNZ{?)zl{333lvj|CDyxVfp5@}Sr zsoV)s24iVBh}%1h&}{)#LMQHHk7J6;w*HhO)15|D``dgardB9v(HxTRJ;8^Ac4~*c zrGcFk?izAlN!VQVDU5kp zr<|($W==h+GoD4TFwb}D)Kg@$$PUDR*}D?jI)8bk#I4-tsK*qEH?Qb)Nbk# zDE4Qc%&IEnaMWy*QYW{{$-MTm4{IfZEXEv0$JRvu!adIU09w+yZzZ>%;R1EtWOeTm zZFodL^*%;9hlf#rtW>rmxfgHTPlZAY1*FpAj>2|4M#mqhz;P%37&=9= zLN0O%3hIHLAv`jeoj%=8?*Kn8x(8535@jt0#0UQI&S;yFmbUO1;xeRSah;Vu(@t*( zH;(rZ(iTCgJEzQ9kg_Y;s_N)ya;$dhwHEnyKnq)(T~3yp#-d8aVi-RNV|#Lnauh8C z|5OF+mX+Y2>N3YA?D*_FPUX3cccOaSzTHyIQ=7$c!(u#pIRGhkIc^XG;&8^!n{g_; zgKb%Xv@SeFc?oppT07g8Xtg`RPH1wZGkVU+xOGMoBrTLSAXak2a_49Qrw14}YYOkp zsGlQAMch!Q=Agw+`l*@*M9UzwW;$jQGz>u}&b08iJ)?U$jv&(1e@oL@TTpHs_1gNu zwaHtWmZPan7-N4`t{H6k7WkFO`QWk6S|Jp_czlejXe&DLcn5(cKMw zyTo5y&LDpy@F`y9mfI{XnZWbNv+*3V(@Ny!K4;+zSwARGTDt6vEp~Pje9ZmiuWaE9 zxJ`Os#{gP~!w1`*lwQZ-;bEvixSu>s_e#< z`{NeWkK2yJYuYFHYMYDkY&F$&Nk1NfhRu?fM{y%4l~=XChOfR!AU%#2>gBdNE1oX# z1M54fPpnbVpIaf*qG?O&xI`x$v%RO4YIQu%fxioOa10#8v(!A|9Up$6Epxd~E9sQ@ zYKyd$)3ZJxW=u_?yiM>WM_vy2?8nnu>Dr5rPVh9qzTRTXcH?U_*!lsYi`ro^>){yL zgt*LYrM%Mz9@nD@&(d>r;V8vrsi@n8mTku~@|*DNyoyCsv>Mj%1jW6M<#_s_Oa6N$ zw)nU3*3dTb<#*ig^G7wSvH>yTRHyrxssy(tRtme)V=!m*HeXT1r$;HOxr`{`t(jiUVR?%pW0e{| zqE0sEt=Z7{7bU|h8USEGP>*VU{J2wOtS(qt5h}trF0%Tl9(6_W^LZ#j$J>Rs z8*fj9i^~Rc3MH%+h8}W|Qphpco>eca?sG`UkJy;oJhla-*@tu<4(UYc7qbJ$Azba;;z;XM0r z@zoI1Xh_9|Em2}a+q^~L(H9}XJ%BN=VQd>`K+Y%yP|CGBlCDS5!3iN_6>noPJcGPb-nq(g&Us#CZs z73`H-l@|qht)d~T zj&QeQGSHlaU=C$Ewe*=mb1DxC=~(7?Sg8t82?aHVw6`@rY6lXULgV__|5dfky_W_a}V$TBy+ z;6CWV4+e%zj3jj7$TFWqnNU=daq>EX3YuE?`FtMq$MpuAApn z73_Fe;(>PYO$7v4JEjcC^`#Yzrxp` z=z-^Xf;g(NhOYo-1}o6#g$>2y@$l$3VY_W(n;)SlGBy?&o2xoTkiE{fsrFJM+hYZ)_7B*D=rQr|7r5Ro-)ArA+#i0; zJlFTaaP<8b8{b&th6~8;1~8W5Xbj)t3KT1RcQ^C#15NL{cQf{{_ynZ8c}RYZqJ}>W zN%-bnd<&f6(+%CHS|4p^L@veqN{$?uT1<7tjAWQBqh=c$xzgdRW+{%l_7n;Np;R|2 zpef--g!`3l1pIKC3MZUR#;MB)Z-B@=B-tx78^bgq{U)nWLCK~+$>3#{e2uZgoT|nn*01}IyD zIwm574uddqFzsU63x)}Chhq$fUBnCYFm!iW!RBz$8-jRis#tvGj)$dLmmaAJ`b1@r zR}>hrTnf4ZI|4xF*+5l zVgyHUdje-P$ZKQ?)*N1aCP)>`eTX1f_%TF(LG)0=ic!->3wZo;49_H~xhE@wpi008 zWmv5sH1Q&UV^D<&1WYs1?2hpoA3$TEGjK6*Gw?7l7nvZjj{cPzp)cX*Df!G0p~{GBb$P52ecj$mQ>VOM^Q3rpC$EkmKqkpFRJyw%|8-@JBR5qb4EhTV}V+=$bRPWlneV^i7GDmN}aeT{Gs) znzLz6qB}8r_RLLP-Ltw{X5xFY-8=-QYt+d4n{w}n)t~tTkA&aOnta+U>Ca}_A5JC= zGI#;LP%juSP5}+$(f#-Z0>3RRr=xAQyykNgBZC@}1WWjMq3+?sadL91(8kCEI#?SK=NL_2HTFWFjF? zVsf7M>!2ev5#J$EldIzYQU7ZmP`~erFIpYVEke2q?yQaHv)nSgHypw5*r@KvEZ{Q% z*Hb$_sJI?;`C4Ghae{a;=;eS*fb;7s?q9zry_q|sywnvZd-CG$u0n#%z@-gu4O;m; zZwb!R__UMn7s*K>((r2mfSH?Zd?xLjWFi5t&$B59J*bg=K8f^2ux7QKn5uKd0r2KY9>-cf&rabSkWY~Za%?+Y z-ev?({w5Sv)Ah*9=a}kb7OJTQr?$=MHp_Dua<9)$<1@BM>77Xhi_)%Jp;Pu;M+`E0K4I<>1fFWD5e?fg2Wu$;bCeHXg))q zjX!SjE>N=oTR_RrBtU}OqZMtyr^$Vg>4k@C&?9qOCSwP;&;O;b&#K?0yZ_w(OYQrA KX8o^v;Qs>($pmiz From 7caadb22cd4a5525adf2959f158301836e8ba0ce Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 30 May 2026 14:42:49 +0800 Subject: [PATCH 76/90] fixes --- Shared/CIPPSharp/CIPPSharp.csproj | 2 ++ Shared/CIPPSharp/bin/CIPPSharp.dll | Bin 41472 -> 41472 bytes 2 files changed, 2 insertions(+) diff --git a/Shared/CIPPSharp/CIPPSharp.csproj b/Shared/CIPPSharp/CIPPSharp.csproj index 826586ef6d2c..9bd6e19ccacd 100644 --- a/Shared/CIPPSharp/CIPPSharp.csproj +++ b/Shared/CIPPSharp/CIPPSharp.csproj @@ -11,5 +11,7 @@ false false false + bin\ + false diff --git a/Shared/CIPPSharp/bin/CIPPSharp.dll b/Shared/CIPPSharp/bin/CIPPSharp.dll index 92d07b054ef993dece27dd1fc7244bf175c1ed68..f74704b1943c28aa883a83925eb5d7353c3e74b8 100644 GIT binary patch delta 1795 zcmXw)PiP}m9LIlenzY?*nwNIjT?#8?tWv~(O`EpCgJej(C=0s^77--Vq&ckOrN@Gk zAypQ;2g@kSHi9!&5%Fdg^dijABGQA1hrQ?_L-*j#gL?6xzV`tW=9BOH{C@M^8#2T6 zPo4g$^TF-Hr3aON4)0u3Umt$CrP`5FOU2D^MXp7z^HlVj`a*peDd$r0PESNd+0n-6 zhVw&B{W)5w*B+U@r5~3)Stu>Z>+)N)b@PcJD?cMG`O)wt`P}doLr>n6Te>A3ar6uH zw`JF4AK$_EF(GDv**BeMI>smXV|Cg=t}YOb64!gqQ(lfY~=Rn^X2O5~|0_US5!*GSmrbjL9+Q7;)@r%3*VkJ+0u_ z>0HdB$qOZq+@jj1-zRsRfpm3;o<2Ur4`3Xi5(X(bBj->}?3t5Y=)q0HGwqW@n7|B< zC+tRZvJ`AC6e2k`9LjgPifO`j!Cpqc5ZIrXK?=t(hf=gv1*_17P1uHgxL>pz{j4~Y zpY$nte-blkO*(lXQ~DGWPaVi}I-jytHN6*@pTe{qRHyA?F8MLo#CYU3xesHQz!YXM zhw1|JKo9ybgbB=GZWx&ByJ$}+T(p~T$sXLq_~Z~KFoQWP%napM?apuqn7pff7{UZ* zRCBT_+0JUo-V<(VDDMQi$-txF!_XFVLQYF|a~XL|&Z)|*^=g*s$S%1__Gax2ZL*K= zlS6V$PRK#ZAY(8l=VU3fKzS$!x=MD-cCjY84f`;LDa_~@lSMOwW{YH(+$4K?D0g)` zVBk~e8lLGsCd9<#6prCTdZfbC6?<>13imd~t*}M1M{bk5CZFjL6Jt`CRRa5;jTz*W zAJQswc7ZB%=WI`t?7=qlVIPJtnX}7hrvI)!HTg{Ecr|ZlbD;S+bbK}8@2j!x4vGht#xXRUSp-< zN7wKD8@+fmKY!c2UjF}lYIF6_y<%Q7?;br{d{maCI+}}bs!{plpPlPHS(Bcu%7!`C zr6D!*3h0`&BXu+C7`rVirn+s8nsLjfS~vBY`L3CI&2;t5v0@HCdis-Jocj$k{qO2< GdFnrkf_C8m delta 1795 zcmXw)L1-gY7{|XiP1?e0o8Bcks=&dNWdLDPQ|h?X3;B zoKZ#nHC$M&JTZG!KP7vzP*{>T#pk3hzZkwGUm3n;=*io1Ro5jD(JwIH zkZUIU_yFI*gqSX7-%O7503YM;;uB1YIhL-OIcCkwnzlYgouZVo8&4=ZG@E2cL_Sy+5MIEEYm<)4@5yxJp91iE$%W{rg z&c!U6yioASb-E4aeRAM*rKtmEI`|OZg@ZOKrbx&sIfH6q&s^+64{jQcv`-FU3{yCs zum{b^lCwFNizGJe%g?%usli6h-bN?awr>N91dd?_C2zY5mZ1x4umL-8KW`8EWxg-> z^a*)?5;JK{GI>+-`UEqWx+&{Ao3dRsz1KExxoKOJr|o7g`68@gJaU8Ffdd%B1g0>9 z>I~~Z5Be~KF-&1**f#e!V=u_f*h9Ev4{l<7atLFX!VKol_T_i&p5+NJ`91B!5XLa2 zn~_z)PL>PynQ#kz`LL~P6dny9hPI(&a#FB|OUYw$MptI7SF;5wumcA$fhjX%vS?P&9FgpjYh+LNz=zH5@7ITOgFn^5^3;DC CPI>wO From 11e613aab2d6886caa68e5ceb80f39e0a25293f0 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 30 May 2026 15:55:25 +0800 Subject: [PATCH 77/90] cleanup --- .../CIPPDBCache/Push-ExecCIPPDBCache.ps1 | 15 +---------- .../Activity Triggers/Tests/Push-CIPPTest.ps1 | 17 +----------- .../Public/Invoke-CIPPTestCollection.ps1 | 27 ++----------------- 3 files changed, 4 insertions(+), 55 deletions(-) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 index 522d40cecc12..31f46a51e535 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/CIPPDBCache/Push-ExecCIPPDBCache.ps1 @@ -44,20 +44,7 @@ function Push-ExecCIPPDBCache { # Build the full function name $FullFunctionName = "Set-CIPPDBCache$Name" - - # Cache the resolved command per process so back-to-back HTTP-driven refreshes - # don't repeat the module command-table walk. - if (-not $script:CIPPDBCacheFunctionLookup) { - $script:CIPPDBCacheFunctionLookup = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) - Write-Information "[CacheInit] CIPPDBCacheFunctionLookup initialized in PID $PID" - } - if ($script:CIPPDBCacheFunctionLookup.ContainsKey($FullFunctionName)) { - Write-Information "[CacheHit] CIPPDBCacheFunctionLookup PID=$PID Key=$FullFunctionName Size=$($script:CIPPDBCacheFunctionLookup.Count)" - } else { - Write-Information "[CacheMiss] CIPPDBCacheFunctionLookup PID=$PID Key=$FullFunctionName Size=$($script:CIPPDBCacheFunctionLookup.Count) - resolving via Get-Command" - $script:CIPPDBCacheFunctionLookup[$FullFunctionName] = Get-Command -Name $FullFunctionName -ErrorAction SilentlyContinue - } - $Function = $script:CIPPDBCacheFunctionLookup[$FullFunctionName] + $Function = Get-Command -Name $FullFunctionName -ErrorAction SilentlyContinue if (-not $Function) { throw "Function $FullFunctionName does not exist" } diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 index 302276834eb2..d7224755ad9a 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Tests/Push-CIPPTest.ps1 @@ -12,14 +12,6 @@ function Push-CIPPTest { Write-Information "Running test $TestId for tenant $TenantFilter" - # Per-process cache of resolved test function commands so that a flat orchestrator - # firing thousands of activities doesn't repeat the module command-table walk - # for every task. - if (-not $script:CIPPTestFunctionLookup) { - $script:CIPPTestFunctionLookup = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) - Write-Information "[CacheInit] CIPPTestFunctionLookup initialized in PID $PID" - } - try { if ($TestId -like 'CustomScript-*') { $ScriptGuid = $TestId -replace '^CustomScript-', '' @@ -30,14 +22,7 @@ function Push-CIPPTest { } $FunctionName = "Invoke-CippTest$TestId" - - if ($script:CIPPTestFunctionLookup.ContainsKey($FunctionName)) { - Write-Information "[CacheHit] CIPPTestFunctionLookup PID=$PID Key=$FunctionName Size=$($script:CIPPTestFunctionLookup.Count)" - } else { - Write-Information "[CacheMiss] CIPPTestFunctionLookup PID=$PID Key=$FunctionName Size=$($script:CIPPTestFunctionLookup.Count) - resolving via Get-Command" - $script:CIPPTestFunctionLookup[$FunctionName] = Get-Command $FunctionName -Module CIPPTests -ErrorAction SilentlyContinue - } - $TestCommand = $script:CIPPTestFunctionLookup[$FunctionName] + $TestCommand = Get-Command -Name $FunctionName -Module CIPPTests -ErrorAction SilentlyContinue if (-not $TestCommand) { Write-LogMessage -API 'Tests' -tenant $TenantFilter -message "Test function not found: $FunctionName" -sev Error return @{ testRun = $false } diff --git a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 index 052242225f8d..e03b78f77a3f 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 @@ -53,21 +53,6 @@ function Invoke-CIPPTestCollection { GenericTests = 'Invoke-CippTestGenericTest*' } - # Process-scoped cache of Get-Command lookups so each (tenant × suite) invocation - # doesn't pay the module command-table walk again. The CIPPTests module is loaded - # once per worker process and its exported function set does not change at runtime. - if (-not $script:CIPPTestSuiteFunctionCache) { - $script:CIPPTestSuiteFunctionCache = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) - Write-Information "[CacheInit] CIPPTestSuiteFunctionCache initialized in PID $PID" - } - if (-not $script:CIPPTestCustomFunctionResolved) { - Write-Information "[CacheMiss] CIPPTestCustomFunction PID=$PID - resolving Invoke-CippTestCustomScripts via Get-Command" - $script:CIPPTestCustomFunction = Get-Command -Name 'Invoke-CippTestCustomScripts' -ErrorAction SilentlyContinue - $script:CIPPTestCustomFunctionResolved = $true - } else { - Write-Information "[CacheHit] CIPPTestCustomFunction PID=$PID Resolved=$([bool]$script:CIPPTestCustomFunction)" - } - $SuiteStopwatch = [System.Diagnostics.Stopwatch]::StartNew() $SuccessCount = 0 $FailedCount = 0 @@ -77,7 +62,7 @@ function Invoke-CIPPTestCollection { # Custom suite: Invoke-CippTestCustomScripts now requires a ScriptGuid parameter. # Enumerate distinct enabled script guids from the DB and call once per guid. if ($SuiteName -eq 'Custom') { - if (-not $script:CIPPTestCustomFunction) { + if (-not (Get-Command -Name 'Invoke-CippTestCustomScripts' -ErrorAction SilentlyContinue)) { Write-Information 'Invoke-CippTestCustomScripts not found — skipping Custom suite' return @{ SuiteName = $SuiteName; TenantFilter = $TenantFilter; Success = 0; Failed = 0; Total = 0; TotalSeconds = 0; Timings = @(); Errors = @() } } @@ -177,16 +162,8 @@ function Invoke-CIPPTestCollection { } # Standard suites: discover functions by name pattern via Get-Command. - # Cache the function list per suite so repeated activity invocations in the same - # process don't pay the module command-table walk again (item 7). $Pattern = $SuitePatterns[$SuiteName] - if ($script:CIPPTestSuiteFunctionCache.ContainsKey($SuiteName)) { - Write-Information "[CacheHit] CIPPTestSuiteFunctionCache PID=$PID Suite=$SuiteName Size=$($script:CIPPTestSuiteFunctionCache.Count)" - } else { - Write-Information "[CacheMiss] CIPPTestSuiteFunctionCache PID=$PID Suite=$SuiteName Size=$($script:CIPPTestSuiteFunctionCache.Count) - resolving pattern '$Pattern' via Get-Command" - $script:CIPPTestSuiteFunctionCache[$SuiteName] = @(Get-Command -Name $Pattern -Module CIPPTests -ErrorAction SilentlyContinue) - } - $TestFunctions = $script:CIPPTestSuiteFunctionCache[$SuiteName] + $TestFunctions = @(Get-Command -Name $Pattern -Module CIPPTests -ErrorAction SilentlyContinue) if ($TestFunctions.Count -eq 0) { Write-Information "No test functions found for suite $SuiteName (pattern: $Pattern) — skipping" return @{ From dd8952e4e9b458a33ba232d09d5103df9267016f Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 30 May 2026 18:57:49 +0800 Subject: [PATCH 78/90] reduce memory --- .../Push-SchedulerCIPPNotifications.ps1 | 182 +++++++++--------- .../HTTP Functions/Invoke-ListLogs.ps1 | 111 ++++++----- 2 files changed, 150 insertions(+), 143 deletions(-) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 index 956e9b9965af..dd95f3bd9c55 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-SchedulerCIPPNotifications.ps1 @@ -17,84 +17,86 @@ function Push-SchedulerCIPPNotifications { $severity = $Config.Severity -split ',' if (!$severity) { - $severity = [System.Collections.ArrayList]@('Info', 'Error', 'Warning', 'Critical', 'Alert') + $severity = @('Info', 'Error', 'Warning', 'Critical', 'Alert') } Write-Information "Our Severity table is: $severity" - $Table = Get-CIPPTable - $PartitionKey = Get-Date -UFormat '%Y%m%d' - $Filter = "PartitionKey eq '{0}'" -f $PartitionKey - $Currentlog = Get-CIPPAzDataTableEntity @Table -Filter $Filter | Where-Object { - $_.API -in $Settings -and $_.sentAsAlert -ne $true -and $_.Severity -in $severity - } + $LogTable = Get-CIPPTable $StandardsTable = Get-CIPPTable -tablename CippStandardsAlerts - $CurrentStandardsLogs = Get-CIPPAzDataTableEntity @StandardsTable -Filter $Filter | Where-Object { - $_.sentAsAlert -ne $true - } - Write-Information "Alerts: $($Currentlog.count) found" - Write-Information "Standards: $($CurrentStandardsLogs.count) found" + $PartitionKey = Get-Date -UFormat '%Y%m%d' + + # Server-side: sentAsAlert + severity (small fixed set). API is filtered client-side + # because the API list is open-ended and OR-expanding it can exceed the OData filter limit. + $sevOr = ($severity | ForEach-Object { "Severity eq '$($_ -replace "'", "''")'" }) -join ' or ' + $LogFilter = "PartitionKey eq '$PartitionKey' and sentAsAlert eq false and ($sevOr)" + $StandardsFilter = "PartitionKey eq '$PartitionKey' and sentAsAlert eq false" + + $Currentlog = @(Get-CIPPAzDataTableEntity @LogTable -Filter $LogFilter | Where-Object { $_.API -in $Settings }) + $CurrentStandardsLogs = @(Get-CIPPAzDataTableEntity @StandardsTable -Filter $StandardsFilter) + + Write-Information "Alerts: $($Currentlog.Count) found" + Write-Information "Standards: $($CurrentStandardsLogs.Count) found" # Get the CIPP URL $CippConfigTable = Get-CippTable -tablename Config $CippConfig = Get-CIPPAzDataTableEntity @CippConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" $CIPPURL = 'https://{0}' -f $CippConfig.Value - #email try + $LogsByTenant = @($Currentlog | Group-Object -Property Tenant) + $StandardsByTenant = @($CurrentStandardsLogs | Group-Object -Property Tenant) + + $MarkSent = { + param($Entities, $TargetTable) + if (-not $Entities -or $Entities.Count -eq 0) { return } + $batch = [System.Collections.Generic.List[object]]::new() + foreach ($e in $Entities) { + if ($e.PSObject.Properties.Name -contains 'sentAsAlert') { + $e.sentAsAlert = $true + } else { + $e | Add-Member -MemberType NoteProperty -Name sentAsAlert -Value $true -Force + } + $batch.Add($e) + if ($batch.Count -ge 100) { + Add-CIPPAzDataTableEntity @TargetTable -Entity $batch -Force + $batch.Clear() + } + } + if ($batch.Count -gt 0) { + Add-CIPPAzDataTableEntity @TargetTable -Entity $batch -Force + } + } + try { if ($Config.email -like '*@*') { - #Normal logs - if ($Currentlog) { + if ($Currentlog.Count -gt 0) { if ($config.onePerTenant) { - foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) { - $Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant) - $Subject = "$($Tenant): CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))" + foreach ($g in $LogsByTenant) { + $tenant = $g.Name + $Data = $g.Group | Select-Object Message, API, Tenant, Username, Severity + $Subject = "$($tenant): CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))" $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' - $UpdateLogs = $CurrentLog | ForEach-Object { - if ($_.PSObject.Properties.Name -contains 'sentAsAlert') { - $_.sentAsAlert = $true - } else { - $_ | Add-Member -MemberType NoteProperty -Name sentAsAlert -Value $true -Force - } - $_ - } - if ($UpdateLogs) { - Add-CIPPAzDataTableEntity @Table -Entity $UpdateLogs -Force - } + & $MarkSent $g.Group $LogTable + $Data = $null; $HTMLContent = $null } } else { - $Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity) + $Data = $CurrentLog | Select-Object Message, API, Tenant, Username, Severity $Subject = "CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))" $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter 'AllTenants' -APIName 'Alerts' - $UpdateLogs = $CurrentLog | ForEach-Object { - if ($_.PSObject.Properties.Name -contains 'sentAsAlert') { - $_.sentAsAlert = $true - } else { - $_ | Add-Member -MemberType NoteProperty -Name sentAsAlert -Value $true -Force - } - $_ - } - if ($UpdateLogs) { - Add-CIPPAzDataTableEntity @Table -Entity $UpdateLogs -Force - } + & $MarkSent $CurrentLog $LogTable + $Data = $null; $HTMLContent = $null } } - if ($CurrentStandardsLogs) { - foreach ($tenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) { - $Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $tenant) - $Subject = "$($Tenant): Standards are out of sync for $tenant" + if ($CurrentStandardsLogs.Count -gt 0) { + foreach ($g in $StandardsByTenant) { + $tenant = $g.Name + $Data = $g.Group + $Subject = "$($tenant): Standards are out of sync for $tenant" $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL Send-CIPPAlert -Type 'email' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' - $updateStandards = $CurrentStandardsLogs | ForEach-Object { - if ($_.PSObject.Properties.Name -contains 'sentAsAlert') { - $_.sentAsAlert = $true - } else { - $_ | Add-Member -MemberType NoteProperty -Name sentAsAlert -Value $true -Force - } - $_ - } - if ($updateStandards) { Add-CIPPAzDataTableEntity @StandardsTable -Entity $updateStandards -Force } + & $MarkSent $g.Group $StandardsTable + $Data = $null; $HTMLContent = $null } } } @@ -104,67 +106,61 @@ function Push-SchedulerCIPPNotifications { } try { - Write-Information $($config | ConvertTo-Json) Write-Information $config.webhook if (![string]::IsNullOrEmpty($config.webhook)) { - if ($Currentlog) { - $JSONContent = $Currentlog | ConvertTo-Json -Compress + $ChunkSize = 500 + if ($Currentlog.Count -gt 0) { $Title = "Logbook Notification: Alerts found starting at $((Get-Date).AddMinutes(-15))" - Send-CIPPAlert -Type 'webhook' -Title $Title -JSONContent $JSONContent -TenantFilter 'AllTenants' -APIName 'Alerts' -SchemaSource 'Logbook Notification' -InvokingCommand 'Push-SchedulerCIPPNotifications' -UseStandardizedSchema:$([boolean]$Config.UseStandardizedSchema) - $UpdateLogs = $CurrentLog | ForEach-Object { $_.sentAsAlert = $true; $_ } - if ($UpdateLogs) { Add-CIPPAzDataTableEntity @Table -Entity $UpdateLogs -Force } + for ($i = 0; $i -lt $Currentlog.Count; $i += $ChunkSize) { + $end = [math]::Min($i + $ChunkSize - 1, $Currentlog.Count - 1) + $chunk = $Currentlog[$i..$end] + $JSONContent = $chunk | ConvertTo-Json -Compress + Send-CIPPAlert -Type 'webhook' -Title $Title -JSONContent $JSONContent -TenantFilter 'AllTenants' -APIName 'Alerts' -SchemaSource 'Logbook Notification' -InvokingCommand 'Push-SchedulerCIPPNotifications' -UseStandardizedSchema:$([boolean]$Config.UseStandardizedSchema) + & $MarkSent $chunk $LogTable + $JSONContent = $null; $chunk = $null + } } - if ($CurrentStandardsLogs) { - $Data = $CurrentStandardsLogs - $JSONContent = New-CIPPAlertTemplate -Data $Data -Format 'json' -InputObject 'table' -CIPPURL $CIPPURL - $CurrentStandardsLogs | ConvertTo-Json -Compress - $Title = "Standards Notification: Out of sync standards detected" - Send-CIPPAlert -Type 'webhook' -Title $Title -JSONContent $JSONContent -TenantFilter 'AllTenants' -APIName 'Alerts' -SchemaSource 'Standards Notification' -InvokingCommand 'Push-SchedulerCIPPNotifications' -UseStandardizedSchema:$([boolean]$Config.UseStandardizedSchema) - $updateStandards = $CurrentStandardsLogs | ForEach-Object { - if ($_.PSObject.Properties.Name -contains 'sentAsAlert') { - $_.sentAsAlert = $true - } else { - $_ | Add-Member -MemberType NoteProperty -Name sentAsAlert -Value $true -Force - } - $_ + if ($CurrentStandardsLogs.Count -gt 0) { + $Title = 'Standards Notification: Out of sync standards detected' + for ($i = 0; $i -lt $CurrentStandardsLogs.Count; $i += $ChunkSize) { + $end = [math]::Min($i + $ChunkSize - 1, $CurrentStandardsLogs.Count - 1) + $chunk = $CurrentStandardsLogs[$i..$end] + $JSONContent = New-CIPPAlertTemplate -Data $chunk -Format 'json' -InputObject 'table' -CIPPURL $CIPPURL + Send-CIPPAlert -Type 'webhook' -Title $Title -JSONContent $JSONContent -TenantFilter 'AllTenants' -APIName 'Alerts' -SchemaSource 'Standards Notification' -InvokingCommand 'Push-SchedulerCIPPNotifications' -UseStandardizedSchema:$([boolean]$Config.UseStandardizedSchema) + & $MarkSent $chunk $StandardsTable + $JSONContent = $null; $chunk = $null } } - } } catch { Write-Information "Could not send alerts to webhook $($config.webhook): $($_.Exception.message)" - Write-LogMessage -API 'Alerts' -message "Could not send alerts to webhook $($config.webhook): $($_.Exception.message)" -tenant $Tenant -sev error -LogData (Get-CippException -Exception $_) + Write-LogMessage -API 'Alerts' -message "Could not send alerts to webhook $($config.webhook): $($_.Exception.message)" -tenant 'AllTenants' -sev error -LogData (Get-CippException -Exception $_) } if ($config.sendtoIntegration) { try { - foreach ($tenant in ($CurrentLog.Tenant | Sort-Object -Unique)) { - $Data = ($CurrentLog | Select-Object Message, API, Tenant, Username, Severity | Where-Object -Property tenant -EQ $tenant) + foreach ($g in $LogsByTenant) { + $tenant = $g.Name + $Data = $g.Group | Select-Object Message, API, Tenant, Username, Severity $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'table' -CIPPURL $CIPPURL $Title = "$tenant CIPP Alert: Alerts found starting at $((Get-Date).AddMinutes(-15))" Send-CIPPAlert -Type 'psa' -Title $Title -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' - $UpdateLogs = $CurrentLog | ForEach-Object { $_.SentAsAlert = $true; $_ } - if ($UpdateLogs) { Add-CIPPAzDataTableEntity @Table -Entity $UpdateLogs -Force } + & $MarkSent $g.Group $LogTable + $Data = $null; $HTMLContent = $null } - foreach ($standardsTenant in ($CurrentStandardsLogs.Tenant | Sort-Object -Unique)) { - $Data = ($CurrentStandardsLogs | Where-Object -Property tenant -EQ $standardsTenant) - $Subject = "$($standardsTenant): Standards are out of sync for $standardsTenant" + foreach ($g in $StandardsByTenant) { + $tenant = $g.Name + $Data = $g.Group + $Subject = "$($tenant): Standards are out of sync for $tenant" $HTMLContent = New-CIPPAlertTemplate -Data $Data -Format 'html' -InputObject 'standards' -CIPPURL $CIPPURL - Send-CIPPAlert -Type 'psa' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $standardsTenant -APIName 'Alerts' - $updateStandards = $CurrentStandardsLogs | ForEach-Object { - if ($_.PSObject.Properties.Name -contains 'sentAsAlert') { - $_.sentAsAlert = $true - } else { - $_ | Add-Member -MemberType NoteProperty -Name sentAsAlert -Value $true -Force - } - $_ - } + Send-CIPPAlert -Type 'psa' -Title $Subject -HTMLContent $HTMLContent.htmlcontent -TenantFilter $tenant -APIName 'Alerts' + & $MarkSent $g.Group $StandardsTable + $Data = $null; $HTMLContent = $null } } catch { Write-Information "Could not send alerts to ticketing system: $($_.Exception.message)" - Write-LogMessage -API 'Alerts' -tenant $Tenant -message "Could not send alerts to ticketing system: $($_.Exception.message)" -sev Error + Write-LogMessage -API 'Alerts' -tenant 'AllTenants' -message "Could not send alerts to ticketing system: $($_.Exception.message)" -sev Error } } - } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListLogs.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListLogs.ps1 index 48389b215549..2cff4d234fbe 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListLogs.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ListLogs.ps1 @@ -96,7 +96,6 @@ function Invoke-ListLogs { $EndDate = if ($Request.Query.EndDate ?? $Request.Query.DateFilter) { ConvertTo-CIPPODataFilterValue -Value ($Request.Query.EndDate ?? $Request.Query.DateFilter) -Type Date } else { $null } if ($StartDate -and $EndDate) { - # Collect logs for date range $Filter = "PartitionKey ge '$StartDate' and PartitionKey le '$EndDate'" } elseif ($StartDate) { $Filter = "PartitionKey eq '{0}'" -f $StartDate @@ -108,70 +107,82 @@ function Invoke-ListLogs { $PartitionKey = [TimeZoneInfo]::ConvertTimeBySystemTimeZoneId([DateTime]::UtcNow, $TzId).ToString('yyyyMMdd') $username = '*' $TenantFilter = $null + $ApiFilter = $null + $StandardFilter = $null + $ScheduledTaskFilter = $null $Filter = "PartitionKey eq '{0}'" -f $PartitionKey } - $AllowedTenants = Test-CIPPAccess -Request $Request -TenantList - Write-Host "Getting logs for filter: $Filter, LogLevel: $LogLevel, Username: $username" - $Rows = Get-AzDataTableEntity @Table -Filter $Filter | Where-Object { - $_.Severity -in $LogLevel -and - $_.Username -like $username -and - ([string]::IsNullOrEmpty($TenantFilter) -or $TenantFilter -eq 'AllTenants' -or $_.Tenant -like "*$TenantFilter*" -or $_.TenantID -eq $TenantFilter) -and - ([string]::IsNullOrEmpty($ApiFilter) -or $_.API -match "$ApiFilter") -and - ([string]::IsNullOrEmpty($StandardFilter) -or $_.StandardTemplateId -eq $StandardFilter) -and - ([string]::IsNullOrEmpty($ScheduledTaskFilter) -or $_.ScheduledTaskId -eq $ScheduledTaskFilter) + # Severity stays client-side: Azurite/Azure Table OData has been unreliable + # on long OR chains. Per-partition row counts are small enough that this is fine. + if ($StandardFilter) { + $SafeStd = ConvertTo-CIPPODataFilterValue -Value $StandardFilter -Type Guid + $Filter = "$Filter and StandardTemplateId eq '$SafeStd'" + } + if ($ScheduledTaskFilter) { + $SafeSched = ConvertTo-CIPPODataFilterValue -Value $ScheduledTaskFilter -Type Guid + $Filter = "$Filter and ScheduledTaskId eq '$SafeSched'" } + $AllowedTenants = Test-CIPPAccess -Request $Request -TenantList + Write-Host "Getting logs for filter: $Filter, LogLevel: $LogLevel, Username: $username" + if ($AllowedTenants -notcontains 'AllTenants') { $TenantList = Get-Tenants -IncludeErrors | Where-Object { $_.customerId -in $AllowedTenants } } - foreach ($Row in $Rows) { - if ($AllowedTenants -contains 'AllTenants' -or ($AllowedTenants -notcontains 'AllTenants' -and ($TenantList.defaultDomainName -contains $Row.Tenant -or $Row.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $Row.TenantId)) ) { - if ($StandardTaskFilter -and $Row.StandardTemplateId) { - $Standard = ($Templates | Where-Object { $_.RowKey -eq $Row.StandardTemplateId }).JSON | ConvertFrom-Json - - $StandardInfo = @{ - Template = $Standard.templateName - Standard = $Row.Standard - } + $ReturnedLog = Get-AzDataTableEntity @Table -Filter $Filter | Where-Object { + $_.Severity -in $LogLevel -and + ($username -eq '*' -or $_.Username -like $username) -and + ([string]::IsNullOrEmpty($TenantFilter) -or $TenantFilter -eq 'AllTenants' -or $_.Tenant -like "*$TenantFilter*" -or $_.TenantID -eq $TenantFilter) -and + ([string]::IsNullOrEmpty($ApiFilter) -or $_.API -match "$ApiFilter") -and + ($AllowedTenants -contains 'AllTenants' -or $TenantList.defaultDomainName -contains $_.Tenant -or $_.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $_.TenantId) + } | ForEach-Object { + $Row = $_ + if ($ScheduledTaskFilter -and $Row.StandardTemplateId) { + $Standard = ($Templates | Where-Object { $_.RowKey -eq $Row.StandardTemplateId }).JSON | ConvertFrom-Json + + $StandardInfo = @{ + Template = $Standard.templateName + Standard = $Row.Standard + } - if ($Row.IntuneTemplateId) { - $IntuneTemplate = ($Templates | Where-Object { $_.RowKey -eq $Row.IntuneTemplateId }).JSON | ConvertFrom-Json - $StandardInfo.IntunePolicy = $IntuneTemplate.displayName - } - if ($Row.ConditionalAccessTemplateId) { - $ConditionalAccessTemplate = ($Templates | Where-Object { $_.RowKey -eq $Row.ConditionalAccessTemplateId }).JSON | ConvertFrom-Json - $StandardInfo.ConditionalAccessPolicy = $ConditionalAccessTemplate.displayName - } - } else { - $StandardInfo = @{} + if ($Row.IntuneTemplateId) { + $IntuneTemplate = ($Templates | Where-Object { $_.RowKey -eq $Row.IntuneTemplateId }).JSON | ConvertFrom-Json + $StandardInfo.IntunePolicy = $IntuneTemplate.displayName } + if ($Row.ConditionalAccessTemplateId) { + $ConditionalAccessTemplate = ($Templates | Where-Object { $_.RowKey -eq $Row.ConditionalAccessTemplateId }).JSON | ConvertFrom-Json + $StandardInfo.ConditionalAccessPolicy = $ConditionalAccessTemplate.displayName + } + } else { + $StandardInfo = @{} + } - $LogData = if ($Row.LogData -and (Test-Json -Json $Row.LogData -ErrorAction SilentlyContinue)) { - $Row.LogData | ConvertFrom-Json - } else { $Row.LogData } - [PSCustomObject]@{ - DateTime = $Row.Timestamp - Tenant = $Row.Tenant - API = $Row.API - Message = $Row.Message - User = $Row.Username - Severity = $Row.Severity - LogData = $LogData - TenantID = if ($Row.TenantID -ne $null) { - $Row.TenantID - } else { - 'None' - } - AppId = $Row.AppId - IP = $Row.IP - RowKey = $Row.RowKey - StandardInfo = $StandardInfo - DateFilter = $Row.PartitionKey + $LogData = if ($Row.LogData -and (Test-Json -Json $Row.LogData -ErrorAction SilentlyContinue)) { + $Row.LogData | ConvertFrom-Json + } else { $Row.LogData } + [PSCustomObject]@{ + DateTime = $Row.Timestamp + Tenant = $Row.Tenant + API = $Row.API + Message = $Row.Message + User = $Row.Username + Severity = $Row.Severity + LogData = $LogData + TenantID = if ($Row.TenantID -ne $null) { + $Row.TenantID + } else { + 'None' } + AppId = $Row.AppId + IP = $Row.IP + RowKey = $Row.RowKey + StandardInfo = $StandardInfo + DateFilter = $Row.PartitionKey } } + $ReturnedLog } return [HttpResponseContext]@{ From 518855cc2af7527eea203002c643ef0a687caeb0 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 30 May 2026 23:27:13 +0800 Subject: [PATCH 79/90] test optimisation --- .../CIS/Identity/Invoke-CippTestCIS_1_2_2.ps1 | 14 +++++-- .../CIS/Identity/Invoke-CippTestCIS_2_1_5.ps1 | 4 +- .../CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 | 10 +++-- .../CIS/Identity/Invoke-CippTestCIS_4_2.ps1 | 4 +- .../Identity/Invoke-CippTestCIS_5_2_3_4.ps1 | 16 ++++++-- .../CIS/Identity/Invoke-CippTestCIS_8_1_1.ps1 | 12 +++--- .../Identity/Invoke-CippTestCISAMSEXO111.ps1 | 8 ++-- .../Invoke-CippTestCopilotReady015.ps1 | 16 ++++---- .../Identity/Invoke-CippTestEIDSCAAS04.ps1 | 4 +- .../Invoke-CippTestGenericTest004.ps1 | 26 +++++++++---- .../Invoke-CippTestGenericTest005.ps1 | 8 +++- .../Invoke-CippTestGenericTest006.ps1 | 8 +++- .../Invoke-CippTestGenericTest007.ps1 | 11 ++++-- .../Invoke-CippTestGenericTest008.ps1 | 11 ++++-- .../Invoke-CippTestGenericTest011.ps1 | 10 ++--- .../ORCA/Identity/Invoke-CippTestORCA104.ps1 | 9 ++--- .../ORCA/Identity/Invoke-CippTestORCA108.ps1 | 37 ++++++++++-------- .../ORCA/Identity/Invoke-CippTestORCA113.ps1 | 9 ++--- .../ORCA/Identity/Invoke-CippTestORCA239.ps1 | 20 +++++----- .../ZTNA/Devices/Invoke-CippTestZTNA24540.ps1 | 30 +++++++-------- .../ZTNA/Devices/Invoke-CippTestZTNA24550.ps1 | 34 ++++++++--------- .../ZTNA/Devices/Invoke-CippTestZTNA24552.ps1 | 34 ++++++++--------- .../Identity/Invoke-CippTestZTNA21773.ps1 | 14 ++++--- .../Identity/Invoke-CippTestZTNA21782.ps1 | 26 ++++++------- .../Identity/Invoke-CippTestZTNA21797.ps1 | 6 +-- .../Identity/Invoke-CippTestZTNA21858.ps1 | 33 ++++++++-------- .../Identity/Invoke-CippTestZTNA21868.ps1 | 38 ++++++++++--------- .../Identity/Invoke-CippTestZTNA21869.ps1 | 21 +++++----- .../Identity/Invoke-CippTestZTNA21877.ps1 | 25 ++++++------ .../Identity/Invoke-CippTestZTNA21886.ps1 | 25 ++++++------ .../Identity/Invoke-CippTestZTNA21896.ps1 | 23 ++++++----- .../Identity/Invoke-CippTestZTNA21992.ps1 | 29 +++++++------- .../Identity/Invoke-CippTestZTNA22128.ps1 | 31 ++++++++------- 33 files changed, 322 insertions(+), 284 deletions(-) diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_2.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_2.ps1 index 4cfb17c8f705..c0d3ab48c41e 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_2.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_1_2_2.ps1 @@ -21,11 +21,19 @@ function Invoke-CippTestCIS_1_2_2 { return } - $EnabledShared = @() + $UsersById = @{} + $UsersByUpn = @{} + foreach ($U in $Users) { + if ($U.id) { $UsersById[$U.id] = $U } + if ($U.userPrincipalName) { $UsersByUpn[$U.userPrincipalName] = $U } + } + $EnabledShared = [System.Collections.Generic.List[object]]::new() foreach ($SM in $SharedMailboxes) { - $User = $Users | Where-Object { $_.userPrincipalName -eq $SM.UserPrincipalName -or $_.id -eq $SM.ExternalDirectoryObjectId } | Select-Object -First 1 + $User = $null + if ($SM.UserPrincipalName -and $UsersByUpn.ContainsKey($SM.UserPrincipalName)) { $User = $UsersByUpn[$SM.UserPrincipalName] } + elseif ($SM.ExternalDirectoryObjectId -and $UsersById.ContainsKey($SM.ExternalDirectoryObjectId)) { $User = $UsersById[$SM.ExternalDirectoryObjectId] } if ($User -and $User.accountEnabled -eq $true) { - $EnabledShared += $User + $EnabledShared.Add($User) } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_5.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_5.ps1 index 25d6134d0eb0..121a32a793ab 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_5.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_5.ps1 @@ -20,10 +20,10 @@ function Invoke-CippTestCIS_2_1_5 { EnableSafeDocs = $true AllowSafeDocsOpen = $false } - $Failures = @() + $Failures = [System.Collections.Generic.List[string]]::new() foreach ($key in $Required.Keys) { if ($Cfg.$key -ne $Required[$key]) { - $Failures += "$key = $($Cfg.$key) (expected $($Required[$key]))" + $Failures.Add("$key = $($Cfg.$key) (expected $($Required[$key]))") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 index 7cb90eabd3c9..17868db8b874 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_2_1_9.ps1 @@ -14,12 +14,14 @@ function Invoke-CippTestCIS_2_1_9 { return } - $Sending = $Accepted | Where-Object { -not $_.SendingFromDomainDisabled -and $_.DomainName -notlike '*onmicrosoft.com' } - $Failed = @() + $Sending = $Accepted.Where({ -not $_.SendingFromDomainDisabled -and $_.DomainName -notlike '*onmicrosoft.com' }) + $DkimByDomain = $Dkim | Group-Object Domain -AsHashTable -AsString + $Failed = [System.Collections.Generic.List[object]]::new() foreach ($D in $Sending) { - $Cfg = $Dkim | Where-Object { $_.Domain -eq $D.DomainName } | Select-Object -First 1 + $Cfg = $null + if ($DkimByDomain.ContainsKey($D.DomainName)) { $Cfg = @($DkimByDomain[$D.DomainName])[0] } if (-not $Cfg -or $Cfg.Enabled -ne $true) { - $Failed += [PSCustomObject]@{ Domain = $D.DomainName; Enabled = $Cfg.Enabled } + $Failed.Add([PSCustomObject]@{ Domain = $D.DomainName; Enabled = $Cfg.Enabled }) } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_4_2.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_4_2.ps1 index c96bb67f1dce..964af9fc3390 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_4_2.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_4_2.ps1 @@ -21,11 +21,11 @@ function Invoke-CippTestCIS_4_2 { return } - $Failures = @() + $Failures = [System.Collections.Generic.List[string]]::new() foreach ($P in @('androidForWorkRestriction', 'androidRestriction', 'iosRestriction', 'macOSRestriction', 'windowsRestriction')) { $r = $DefaultPlatform.$P if ($r -and $r.personalDeviceEnrollmentBlocked -ne $true -and $r.platformBlocked -ne $true) { - $Failures += "$P : personal enrollment NOT blocked" + $Failures.Add("$P : personal enrollment NOT blocked") } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_3_4.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_3_4.ps1 index 6a9dfef02f6b..d8d2e8b8df21 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_3_4.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_5_2_3_4.ps1 @@ -14,12 +14,20 @@ function Invoke-CippTestCIS_5_2_3_4 { return } - $Members = $Users | Where-Object { $_.userType -eq 'Member' -and $_.accountEnabled -eq $true } - $NotCapable = @() + $Members = $Users.Where({ $_.userType -eq 'Member' -and $_.accountEnabled -eq $true }) + $RegById = @{} + $RegByUpn = @{} + foreach ($R in $Reg) { + if ($R.id) { $RegById[$R.id] = $R } + if ($R.userPrincipalName) { $RegByUpn[$R.userPrincipalName] = $R } + } + $NotCapable = [System.Collections.Generic.List[object]]::new() foreach ($U in $Members) { - $R = $Reg | Where-Object { $_.id -eq $U.id -or $_.userPrincipalName -eq $U.userPrincipalName } | Select-Object -First 1 + $R = $null + if ($U.id -and $RegById.ContainsKey($U.id)) { $R = $RegById[$U.id] } + elseif ($U.userPrincipalName -and $RegByUpn.ContainsKey($U.userPrincipalName)) { $R = $RegByUpn[$U.userPrincipalName] } if (-not $R -or $R.isMfaCapable -ne $true) { - $NotCapable += $U + $NotCapable.Add($U) } } diff --git a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_8_1_1.ps1 b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_8_1_1.ps1 index 25c9ddca3b03..2183391c5117 100644 --- a/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_8_1_1.ps1 +++ b/Modules/CIPPTests/Public/Tests/CIS/Identity/Invoke-CippTestCIS_8_1_1.ps1 @@ -14,12 +14,12 @@ function Invoke-CippTestCIS_8_1_1 { } $Cfg = $Client | Select-Object -First 1 - $Enabled = @() - if ($Cfg.AllowDropbox) { $Enabled += 'Dropbox' } - if ($Cfg.AllowBox) { $Enabled += 'Box' } - if ($Cfg.AllowGoogleDrive) { $Enabled += 'GoogleDrive' } - if ($Cfg.AllowShareFile) { $Enabled += 'ShareFile' } - if ($Cfg.AllowEgnyte) { $Enabled += 'Egnyte' } + $Enabled = [System.Collections.Generic.List[string]]::new() + if ($Cfg.AllowDropbox) { $Enabled.Add('Dropbox') } + if ($Cfg.AllowBox) { $Enabled.Add('Box') } + if ($Cfg.AllowGoogleDrive) { $Enabled.Add('GoogleDrive') } + if ($Cfg.AllowShareFile) { $Enabled.Add('ShareFile') } + if ($Cfg.AllowEgnyte) { $Enabled.Add('Egnyte') } if ($Enabled.Count -eq 0) { $Status = 'Passed' diff --git a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.ps1 b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.ps1 index 18c9d6fc0150..6aeb210c80c4 100644 --- a/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.ps1 +++ b/Modules/CIPPTests/Public/Tests/CISA/Identity/Invoke-CippTestCISAMSEXO111.ps1 @@ -28,10 +28,10 @@ function Invoke-CippTestCISAMSEXO111 { $StandardATP = $PresetPolicies | Where-Object { $_.Identity -like '*Preset Security Policy*' -and $_.ImpersonationProtectionState -eq 'Enabled' } - $EnabledPolicies = @() - if ($StandardEOP) { $EnabledPolicies += 'Standard EOP' } - if ($StrictEOP) { $EnabledPolicies += 'Strict EOP' } - if ($StandardATP) { $EnabledPolicies += "$($StandardATP.Count) ATP policy/policies with impersonation protection" } + $EnabledPolicies = [System.Collections.Generic.List[string]]::new() + if ($StandardEOP) { $EnabledPolicies.Add('Standard EOP') } + if ($StrictEOP) { $EnabledPolicies.Add('Strict EOP') } + if ($StandardATP) { $EnabledPolicies.Add("$($StandardATP.Count) ATP policy/policies with impersonation protection") } if ($EnabledPolicies.Count -gt 0) { $Result = "✅ **Pass**: Preset security policies with impersonation protection are enabled:`n`n" diff --git a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 index 3bb45ac64ef9..94239d79ff02 100644 --- a/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 +++ b/Modules/CIPPTests/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady015.ps1 @@ -20,9 +20,7 @@ function Invoke-CippTestCopilotReady015 { $ActiveUsers = @($UsageData | Where-Object { $_.userPrincipalName -and $_.userPrincipalName -ne 'Not applicable' }) if ($ActiveUsers.Count -eq 0) { - $Result = "No Microsoft 365 Copilot usage was detected in the past 30 days.`n`n" - $Result += 'This tenant either has no Copilot licenses assigned, or users have not yet started using Copilot features. ' - $Result += 'See tests CopilotReady001 and CopilotReady002 to check licensing status.' + $Result = "No Microsoft 365 Copilot usage was detected in the past 30 days.`n`nThis tenant either has no Copilot licenses assigned, or users have not yet started using Copilot features. See tests CopilotReady001 and CopilotReady002 to check licensing status." Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady015' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Microsoft 365 Copilot usage per user' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Copilot Readiness' return } @@ -33,12 +31,13 @@ function Invoke-CippTestCopilotReady015 { $_ -notin @('userPrincipalName', 'displayName', 'lastActivityDate', 'reportRefreshDate', 'reportPeriod', 'id') } - $Result = "**$($ActiveUsers.Count) users** had Copilot activity in the past 30 days.`n`n" + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append("**$($ActiveUsers.Count) users** had Copilot activity in the past 30 days.`n`n") # Build table header from available columns $Headers = @('User', 'Last Active') + $AppColumns - $Result += '| ' + ($Headers -join ' | ') + " |`n" - $Result += '| ' + (($Headers | ForEach-Object { '---' }) -join ' | ') + " |`n" + $null = $sb.Append('| ' + ($Headers -join ' | ') + " |`n") + $null = $sb.Append('| ' + (($Headers | ForEach-Object { '---' }) -join ' | ') + " |`n") $DisplayUsers = $ActiveUsers | Sort-Object lastActivityDate -Descending | Select-Object -First 50 foreach ($User in $DisplayUsers) { @@ -48,12 +47,13 @@ function Invoke-CippTestCopilotReady015 { $Val = $User.$Col $null = $Row.Append(" $Val |") } - $Result += "$Row`n" + $null = $sb.Append("$Row`n") } if ($ActiveUsers.Count -gt 50) { - $Result += "`n*Showing 50 of $($ActiveUsers.Count) active users.*" + $null = $sb.Append("`n*Showing 50 of $($ActiveUsers.Count) active users.*") } + $Result = $sb.ToString() Add-CippTestResult -TenantFilter $Tenant -TestId 'CopilotReady015' -TestType 'Identity' -Status 'Informational' -ResultMarkdown $Result -Risk 'Informational' -Name 'Microsoft 365 Copilot usage per user' -UserImpact 'Low' -ImplementationEffort 'Low' -Category 'Copilot Readiness' diff --git a/Modules/CIPPTests/Public/Tests/EIDSCA/Identity/Invoke-CippTestEIDSCAAS04.ps1 b/Modules/CIPPTests/Public/Tests/EIDSCA/Identity/Invoke-CippTestEIDSCAAS04.ps1 index fda96e4850df..25dfc5584a24 100644 --- a/Modules/CIPPTests/Public/Tests/EIDSCA/Identity/Invoke-CippTestEIDSCAAS04.ps1 +++ b/Modules/CIPPTests/Public/Tests/EIDSCA/Identity/Invoke-CippTestEIDSCAAS04.ps1 @@ -20,11 +20,11 @@ function Invoke-CippTestEIDSCAAS04 { return } - $InvalidTargets = @() + $InvalidTargets = [System.Collections.Generic.List[string]]::new() if ($SmsConfig.includeTargets) { foreach ($target in $SmsConfig.includeTargets) { if ($target.isUsableForSignIn -ne $false) { - $InvalidTargets += $target.id + $InvalidTargets.Add($target.id) } } } diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 index 4d6b19eaeee2..653390b5d916 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest004.ps1 @@ -20,13 +20,25 @@ function Invoke-CippTestGenericTest004 { } $TotalUsers = $Users.Count - $MFARegistered = @($Users | Where-Object { $_.MFARegistration -eq $true }).Count - $MFACapable = @($Users | Where-Object { $_.MFACapable -eq $true }).Count - $CoveredByCA = @($Users | Where-Object { $_.CoveredByCA -like 'Enforced*' }).Count - $CoveredBySD = @($Users | Where-Object { $_.CoveredBySD -eq $true }).Count - $PerUserMFA = @($Users | Where-Object { $_.PerUser -in @('Enforced', 'Enabled') }).Count - $NotProtected = @($Users | Where-Object { $_.CoveredByCA -notlike 'Enforced*' -and $_.CoveredBySD -ne $true -and $_.PerUser -notin @('Enforced', 'Enabled') }).Count - $AdminCount = @($Users | Where-Object { $_.IsAdmin -eq $true }).Count + $MFARegistered = 0 + $MFACapable = 0 + $CoveredByCA = 0 + $CoveredBySD = 0 + $PerUserMFA = 0 + $NotProtected = 0 + $AdminCount = 0 + foreach ($u in $Users) { + if ($u.MFARegistration -eq $true) { $MFARegistered++ } + if ($u.MFACapable -eq $true) { $MFACapable++ } + $isCA = $u.CoveredByCA -like 'Enforced*' + $isSD = $u.CoveredBySD -eq $true + $isPerUser = $u.PerUser -in @('Enforced', 'Enabled') + if ($isCA) { $CoveredByCA++ } + if ($isSD) { $CoveredBySD++ } + if ($isPerUser) { $PerUserMFA++ } + if (-not $isCA -and -not $isSD -and -not $isPerUser) { $NotProtected++ } + if ($u.IsAdmin -eq $true) { $AdminCount++ } + } $MFARegPct = if ($TotalUsers -gt 0) { [math]::Round(($MFARegistered / $TotalUsers) * 100, 1) } else { 0 } $Result = [System.Text.StringBuilder]::new("### Summary`n`n") diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 index 6f25700f1eba..b8f653383985 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest005.ps1 @@ -22,8 +22,12 @@ function Invoke-CippTestGenericTest005 { } $TotalAdmins = $Admins.Count - $MFARegistered = @($Admins | Where-Object { $_.MFARegistration -eq $true }).Count - $NotProtected = @($Admins | Where-Object { $_.CoveredByCA -notlike 'Enforced*' -and $_.CoveredBySD -ne $true -and $_.PerUser -notin @('Enforced', 'Enabled') }).Count + $MFARegistered = 0 + $NotProtected = 0 + foreach ($a in $Admins) { + if ($a.MFARegistration -eq $true) { $MFARegistered++ } + if ($a.CoveredByCA -notlike 'Enforced*' -and $a.CoveredBySD -ne $true -and $a.PerUser -notin @('Enforced', 'Enabled')) { $NotProtected++ } + } $MFARegPct = if ($TotalAdmins -gt 0) { [math]::Round(($MFARegistered / $TotalAdmins) * 100, 1) } else { 0 } $Result = [System.Text.StringBuilder]::new("**Total Admins:** $TotalAdmins | **MFA Registered:** $MFARegistered ($MFARegPct%)") diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 index dee9fb7994df..4b47956736b0 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest006.ps1 @@ -22,8 +22,12 @@ function Invoke-CippTestGenericTest006 { } $TotalUsers = $StandardUsers.Count - $MFARegistered = @($StandardUsers | Where-Object { $_.MFARegistration -eq $true }).Count - $NotProtected = @($StandardUsers | Where-Object { $_.CoveredByCA -notlike 'Enforced*' -and $_.CoveredBySD -ne $true -and $_.PerUser -notin @('Enforced', 'Enabled') }).Count + $MFARegistered = 0 + $NotProtected = 0 + foreach ($u in $StandardUsers) { + if ($u.MFARegistration -eq $true) { $MFARegistered++ } + if ($u.CoveredByCA -notlike 'Enforced*' -and $u.CoveredBySD -ne $true -and $u.PerUser -notin @('Enforced', 'Enabled')) { $NotProtected++ } + } $MFARegPct = if ($TotalUsers -gt 0) { [math]::Round(($MFARegistered / $TotalUsers) * 100, 1) } else { 0 } $Result = [System.Text.StringBuilder]::new("**Total Users:** $TotalUsers | **MFA Registered:** $MFARegistered ($MFARegPct%)") diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 index 3d4d71b98fd3..8347f3c4af64 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest007.ps1 @@ -22,9 +22,14 @@ function Invoke-CippTestGenericTest007 { } $TotalUsers = $LicensedUsers.Count - $MFARegistered = @($LicensedUsers | Where-Object { $_.MFARegistration -eq $true }).Count - $NotProtected = @($LicensedUsers | Where-Object { $_.CoveredByCA -notlike 'Enforced*' -and $_.CoveredBySD -ne $true -and $_.PerUser -notin @('Enforced', 'Enabled') }).Count - $Admins = @($LicensedUsers | Where-Object { $_.IsAdmin -eq $true }).Count + $MFARegistered = 0 + $NotProtected = 0 + $Admins = 0 + foreach ($u in $LicensedUsers) { + if ($u.MFARegistration -eq $true) { $MFARegistered++ } + if ($u.CoveredByCA -notlike 'Enforced*' -and $u.CoveredBySD -ne $true -and $u.PerUser -notin @('Enforced', 'Enabled')) { $NotProtected++ } + if ($u.IsAdmin -eq $true) { $Admins++ } + } $MFARegPct = if ($TotalUsers -gt 0) { [math]::Round(($MFARegistered / $TotalUsers) * 100, 1) } else { 0 } $Result = [System.Text.StringBuilder]::new("**Licensed Users:** $TotalUsers | **Admins among them:** $Admins | **MFA Registered:** $MFARegistered ($MFARegPct%)") diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 index f082405ac151..d3b01114042d 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest008.ps1 @@ -25,9 +25,14 @@ function Invoke-CippTestGenericTest008 { return } - $EnforcedCount = @($PerUserMFAUsers | Where-Object { $_.PerUser -eq 'Enforced' }).Count - $EnabledCount = @($PerUserMFAUsers | Where-Object { $_.PerUser -eq 'Enabled' }).Count - $AdminsAffected = @($PerUserMFAUsers | Where-Object { $_.IsAdmin -eq $true }).Count + $EnforcedCount = 0 + $EnabledCount = 0 + $AdminsAffected = 0 + foreach ($u in $PerUserMFAUsers) { + if ($u.PerUser -eq 'Enforced') { $EnforcedCount++ } + if ($u.PerUser -eq 'Enabled') { $EnabledCount++ } + if ($u.IsAdmin -eq $true) { $AdminsAffected++ } + } $null = $Result.Append("### Current Status`n`n") $null = $Result.Append("**⚠️ $($PerUserMFAUsers.Count) account(s) are still using legacy Per-User MFA.**`n`n") diff --git a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 index ac3e9105eea4..816fe0b820de 100644 --- a/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 +++ b/Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1 @@ -155,11 +155,11 @@ function Invoke-CippTestGenericTest011 { continue } - # Split into categories - $CompliantItems = @($Details | Where-Object { $_.ComplianceStatus -eq 'Compliant' }) - $NonCompliantItems = @($Details | Where-Object { $_.ComplianceStatus -eq 'Non-Compliant' }) - $LicenseMissingItems = @($Details | Where-Object { $_.ComplianceStatus -eq 'License Missing' }) - $ReportingDisabledItems = @($Details | Where-Object { $_.ComplianceStatus -eq 'Reporting Disabled' }) + $ByStatus = $Details | Group-Object ComplianceStatus -AsHashTable -AsString + $CompliantItems = @($ByStatus['Compliant']) + $NonCompliantItems = @($ByStatus['Non-Compliant']) + $LicenseMissingItems = @($ByStatus['License Missing']) + $ReportingDisabledItems = @($ByStatus['Reporting Disabled']) # Helper to resolve and skip unresolvable template items $TemplateSettings = $Template.standardSettings diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 index da2cd06bfc06..2382b39b8301 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA104.ps1 @@ -13,15 +13,14 @@ function Invoke-CippTestORCA104 { return } - $FailedPolicies = @() - $PassedPolicies = @() + $FailedPolicies = [System.Collections.Generic.List[object]]::new() + $PassedPolicies = [System.Collections.Generic.List[object]]::new() foreach ($Policy in $AntiPhishPolicies) { - # Check if HighConfidencePhishAction is set to Quarantine if ($Policy.HighConfidencePhishAction -eq 'Quarantine') { - $PassedPolicies += $Policy + $PassedPolicies.Add($Policy) } else { - $FailedPolicies += $Policy + $FailedPolicies.Add($Policy) } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108.ps1 index f46e2031e374..145e63a7f4e8 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA108.ps1 @@ -14,41 +14,46 @@ function Invoke-CippTestORCA108 { return } - # Get custom domains (exclude default .onmicrosoft.com domains) - $CustomDomains = $AcceptedDomains | Where-Object { + $CustomDomains = $AcceptedDomains.Where({ $_.DomainName -notlike '*.onmicrosoft.com' -and $_.DomainName -notlike '*.mail.onmicrosoft.com' - } + }) if ($CustomDomains.Count -eq 0) { $Status = 'Passed' $Result = 'No custom domains configured. DKIM check not applicable for default domains only.' } else { - $DomainsWithoutDkim = @() - $DomainsWithDkim = @() + $DkimByDomain = $DkimConfig | Group-Object Domain -AsHashTable -AsString + $DomainsWithoutDkim = [System.Collections.Generic.List[string]]::new() + $DomainsWithDkim = [System.Collections.Generic.List[string]]::new() foreach ($Domain in $CustomDomains) { - $DkimForDomain = $DkimConfig | Where-Object { $_.Domain -eq $Domain.DomainName } + $DkimForDomain = $null + if ($DkimByDomain -and $DkimByDomain.ContainsKey($Domain.DomainName)) { $DkimForDomain = @($DkimByDomain[$Domain.DomainName])[0] } if ($DkimForDomain -and $DkimForDomain.Enabled -eq $true) { - $DomainsWithDkim += $Domain.DomainName + $DomainsWithDkim.Add($Domain.DomainName) } else { - $DomainsWithoutDkim += $Domain.DomainName + $DomainsWithoutDkim.Add($Domain.DomainName) } } if ($DomainsWithoutDkim.Count -eq 0) { $Status = 'Passed' - $Result = "DKIM signing is enabled for all custom domains ($($DomainsWithDkim.Count) domains).`n`n" - $Result += "**Domains with DKIM enabled:**`n" - $Result += ($DomainsWithDkim | ForEach-Object { "- $_" }) -join "`n" + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append("DKIM signing is enabled for all custom domains ($($DomainsWithDkim.Count) domains).`n`n") + $null = $sb.Append("**Domains with DKIM enabled:**`n") + $null = $sb.Append(($DomainsWithDkim | ForEach-Object { "- $_" }) -join "`n") + $Result = $sb.ToString() } else { $Status = 'Failed' - $Result = "DKIM signing is not configured for all custom domains.`n`n" - $Result += "**Missing DKIM:** $($DomainsWithoutDkim.Count) | **Configured:** $($DomainsWithDkim.Count)`n`n" - $Result += "### Domains without DKIM:`n" - $Result += ($DomainsWithoutDkim | ForEach-Object { "- $_" }) -join "`n" - $Result += "`n`n**Remediation:** Enable DKIM signing for all custom domains to prevent email spoofing." + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append("DKIM signing is not configured for all custom domains.`n`n") + $null = $sb.Append("**Missing DKIM:** $($DomainsWithoutDkim.Count) | **Configured:** $($DomainsWithDkim.Count)`n`n") + $null = $sb.Append("### Domains without DKIM:`n") + $null = $sb.Append(($DomainsWithoutDkim | ForEach-Object { "- $_" }) -join "`n") + $null = $sb.Append("`n`n**Remediation:** Enable DKIM signing for all custom domains to prevent email spoofing.") + $Result = $sb.ToString() } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 index a6f5d30b681b..af98497bee2a 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA113.ps1 @@ -13,15 +13,14 @@ function Invoke-CippTestORCA113 { return } - $FailedPolicies = @() - $PassedPolicies = @() + $FailedPolicies = [System.Collections.Generic.List[object]]::new() + $PassedPolicies = [System.Collections.Generic.List[object]]::new() foreach ($Policy in $SafeLinksPolicies) { - # Check if DoNotAllowClickThrough is set to true (which means AllowClickThrough is disabled) if ($Policy.DoNotAllowClickThrough -eq $true) { - $PassedPolicies += $Policy + $PassedPolicies.Add($Policy) } else { - $FailedPolicies += $Policy + $FailedPolicies.Add($Policy) } } diff --git a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 index 414b918df87f..33ff5c3246d5 100644 --- a/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 +++ b/Modules/CIPPTests/Public/Tests/ORCA/Identity/Invoke-CippTestORCA239.ps1 @@ -14,27 +14,27 @@ function Invoke-CippTestORCA239 { return } - $FailedPolicies = @() - $Issues = @() + $FailedPolicies = [System.Collections.Generic.List[object]]::new() + $Issues = [System.Collections.Generic.List[string]]::new() # Check Anti-Phish policies for exclusions if ($AntiPhishPolicies) { foreach ($Policy in $AntiPhishPolicies) { $HasExclusions = $false - $ExclusionDetails = @() + $ExclusionDetails = [System.Collections.Generic.List[string]]::new() if ($Policy.ExcludedSenders -and $Policy.ExcludedSenders.Count -gt 0) { $HasExclusions = $true - $ExclusionDetails += "ExcludedSenders: $($Policy.ExcludedSenders.Count)" + $ExclusionDetails.Add("ExcludedSenders: $($Policy.ExcludedSenders.Count)") } if ($Policy.ExcludedDomains -and $Policy.ExcludedDomains.Count -gt 0) { $HasExclusions = $true - $ExclusionDetails += "ExcludedDomains: $($Policy.ExcludedDomains.Count)" + $ExclusionDetails.Add("ExcludedDomains: $($Policy.ExcludedDomains.Count)") } if ($HasExclusions) { - $Issues += "Anti-Phish Policy '$($Policy.Identity)': $($ExclusionDetails -join ', ')" + $Issues.Add("Anti-Phish Policy '$($Policy.Identity)': $($ExclusionDetails -join ', ')") } } } @@ -43,20 +43,20 @@ function Invoke-CippTestORCA239 { if ($ContentFilterPolicies) { foreach ($Policy in $ContentFilterPolicies) { $HasExclusions = $false - $ExclusionDetails = @() + $ExclusionDetails = [System.Collections.Generic.List[string]]::new() if ($Policy.AllowedSenders -and $Policy.AllowedSenders.Count -gt 0) { $HasExclusions = $true - $ExclusionDetails += "AllowedSenders: $($Policy.AllowedSenders.Count)" + $ExclusionDetails.Add("AllowedSenders: $($Policy.AllowedSenders.Count)") } if ($Policy.AllowedSenderDomains -and $Policy.AllowedSenderDomains.Count -gt 0) { $HasExclusions = $true - $ExclusionDetails += "AllowedSenderDomains: $($Policy.AllowedSenderDomains.Count)" + $ExclusionDetails.Add("AllowedSenderDomains: $($Policy.AllowedSenderDomains.Count)") } if ($HasExclusions) { - $Issues += "Anti-Spam Policy '$($Policy.Identity)': $($ExclusionDetails -join ', ')" + $Issues.Add("Anti-Spam Policy '$($Policy.Identity)': $($ExclusionDetails -join ', ')") } } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24540.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24540.ps1 index 8e1e153bb04f..9b0789dea207 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24540.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24540.ps1 @@ -27,14 +27,13 @@ function Invoke-CippTestZTNA24540 { if ($AssignedPolicies.Count -gt 0) { $Status = 'Passed' - $ResultLines = @( - 'At least one Windows Firewall policy is created and assigned to a group.' - '' - '**Windows Firewall Configuration Policies:**' - '' - '| Policy Name | Status | Assignment Count |' - '| :---------- | :----- | :--------------- |' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add('At least one Windows Firewall policy is created and assigned to a group.') + $ResultLines.Add('') + $ResultLines.Add('**Windows Firewall Configuration Policies:**') + $ResultLines.Add('') + $ResultLines.Add('| Policy Name | Status | Assignment Count |') + $ResultLines.Add('| :---------- | :----- | :--------------- |') foreach ($Policy in $FirewallPolicies) { $PolicyStatus = if ($Policy.assignments -and $Policy.assignments.Count -gt 0) { @@ -43,21 +42,20 @@ function Invoke-CippTestZTNA24540 { '❌ Not assigned' } $AssignmentCount = if ($Policy.assignments) { $Policy.assignments.Count } else { 0 } - $ResultLines += "| $($Policy.name) | $PolicyStatus | $AssignmentCount |" + $ResultLines.Add("| $($Policy.name) | $PolicyStatus | $AssignmentCount |") } $Result = $ResultLines -join "`n" } else { $Status = 'Failed' - $ResultLines = @( - 'There are no firewall policies assigned to any groups.' - '' - '**Windows Firewall Configuration Policies (Unassigned):**' - '' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add('There are no firewall policies assigned to any groups.') + $ResultLines.Add('') + $ResultLines.Add('**Windows Firewall Configuration Policies (Unassigned):**') + $ResultLines.Add('') foreach ($Policy in $FirewallPolicies) { - $ResultLines += "- $($Policy.name)" + $ResultLines.Add("- $($Policy.name)") } $Result = $ResultLines -join "`n" diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24550.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24550.ps1 index b8bef5ee4307..f0a06b7eacb9 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24550.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24550.ps1 @@ -17,7 +17,7 @@ function Invoke-CippTestZTNA24550 { $_.platforms -match 'windows10' } - $WindowsBitLockerPolicies = @() + $WindowsBitLockerPolicies = [System.Collections.Generic.List[object]]::new() foreach ($WindowsPolicy in $WindowsPolicies) { $ValidSettingValues = @('device_vendor_msft_bitlocker_requiredeviceencryption_1') @@ -36,7 +36,7 @@ function Invoke-CippTestZTNA24550 { } if ($HasValidSetting) { - $WindowsBitLockerPolicies += $WindowsPolicy + $WindowsBitLockerPolicies.Add($WindowsPolicy) } } } @@ -47,14 +47,13 @@ function Invoke-CippTestZTNA24550 { if ($AssignedPolicies.Count -gt 0) { $Status = 'Passed' - $ResultLines = @( - 'At least one Windows BitLocker policy is configured and assigned.' - '' - '**Windows BitLocker Policies:**' - '' - '| Policy Name | Status | Assignment Count |' - '| :---------- | :----- | :--------------- |' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add('At least one Windows BitLocker policy is configured and assigned.') + $ResultLines.Add('') + $ResultLines.Add('**Windows BitLocker Policies:**') + $ResultLines.Add('') + $ResultLines.Add('| Policy Name | Status | Assignment Count |') + $ResultLines.Add('| :---------- | :----- | :--------------- |') foreach ($Policy in $WindowsBitLockerPolicies) { $PolicyStatus = if ($Policy.assignments -and $Policy.assignments.Count -gt 0) { @@ -63,21 +62,20 @@ function Invoke-CippTestZTNA24550 { '❌ Not assigned' } $AssignmentCount = if ($Policy.assignments) { $Policy.assignments.Count } else { 0 } - $ResultLines += "| $($Policy.name) | $PolicyStatus | $AssignmentCount |" + $ResultLines.Add("| $($Policy.name) | $PolicyStatus | $AssignmentCount |") } $Result = $ResultLines -join "`n" } else { $Status = 'Failed' if ($WindowsBitLockerPolicies.Count -gt 0) { - $ResultLines = @( - 'Windows BitLocker policies exist but none are assigned.' - '' - '**Unassigned BitLocker Policies:**' - '' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add('Windows BitLocker policies exist but none are assigned.') + $ResultLines.Add('') + $ResultLines.Add('**Unassigned BitLocker Policies:**') + $ResultLines.Add('') foreach ($Policy in $WindowsBitLockerPolicies) { - $ResultLines += "- $($Policy.name)" + $ResultLines.Add("- $($Policy.name)") } } else { $ResultLines = @('No Windows BitLocker policy is configured or assigned.') diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24552.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24552.ps1 index 047942d7ef86..39e4712f7d29 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24552.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Devices/Invoke-CippTestZTNA24552.ps1 @@ -17,7 +17,7 @@ function Invoke-CippTestZTNA24552 { $_.platforms -match 'macOS' } - $MacOSFirewallPolicies = @() + $MacOSFirewallPolicies = [System.Collections.Generic.List[object]]::new() foreach ($MacOSPolicy in $MacOSPolicies) { $ValidSettingValues = @('com.apple.security.firewall_enablefirewall_true') @@ -36,7 +36,7 @@ function Invoke-CippTestZTNA24552 { } if ($HasValidSetting) { - $MacOSFirewallPolicies += $MacOSPolicy + $MacOSFirewallPolicies.Add($MacOSPolicy) } } } @@ -47,14 +47,13 @@ function Invoke-CippTestZTNA24552 { if ($AssignedPolicies.Count -gt 0) { $Status = 'Passed' - $ResultLines = @( - 'At least one macOS Firewall policy is configured and assigned.' - '' - '**macOS Firewall Policies:**' - '' - '| Policy Name | Status | Assignment Count |' - '| :---------- | :----- | :--------------- |' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add('At least one macOS Firewall policy is configured and assigned.') + $ResultLines.Add('') + $ResultLines.Add('**macOS Firewall Policies:**') + $ResultLines.Add('') + $ResultLines.Add('| Policy Name | Status | Assignment Count |') + $ResultLines.Add('| :---------- | :----- | :--------------- |') foreach ($Policy in $MacOSFirewallPolicies) { $PolicyStatus = if ($Policy.assignments -and $Policy.assignments.Count -gt 0) { @@ -63,21 +62,20 @@ function Invoke-CippTestZTNA24552 { '❌ Not assigned' } $AssignmentCount = if ($Policy.assignments) { $Policy.assignments.Count } else { 0 } - $ResultLines += "| $($Policy.name) | $PolicyStatus | $AssignmentCount |" + $ResultLines.Add("| $($Policy.name) | $PolicyStatus | $AssignmentCount |") } $Result = $ResultLines -join "`n" } else { $Status = 'Failed' if ($MacOSFirewallPolicies.Count -gt 0) { - $ResultLines = @( - 'macOS Firewall policies exist but none are assigned.' - '' - '**Unassigned Firewall Policies:**' - '' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add('macOS Firewall policies exist but none are assigned.') + $ResultLines.Add('') + $ResultLines.Add('**Unassigned Firewall Policies:**') + $ResultLines.Add('') foreach ($Policy in $MacOSFirewallPolicies) { - $ResultLines += "- $($Policy.name)" + $ResultLines.Add("- $($Policy.name)") } } else { $ResultLines = @('No macOS Firewall policy is configured or assigned.') diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21773.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21773.ps1 index 2f0ed70e02c4..ef373619a5c2 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21773.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21773.ps1 @@ -65,18 +65,20 @@ function Invoke-CippTestZTNA21773 { $Result = 'Applications in your tenant do not have certificates valid for more than 180 days' } else { $Status = 'Failed' - $Result = "Found $($AppsWithLongCerts.Count) applications and $($SPsWithLongCerts.Count) service principals with certificates longer than 180 days`n`n" + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append("Found $($AppsWithLongCerts.Count) applications and $($SPsWithLongCerts.Count) service principals with certificates longer than 180 days`n`n") if ($AppsWithLongCerts.Count -gt 0) { - $Result += "## Apps with long-lived certificates:`n`n" - $Result += ($AppsWithLongCerts | ForEach-Object { "- $($_.displayName) (AppId: $($_.appId))" }) -join "`n" - $Result += "`n`n" + $null = $sb.Append("## Apps with long-lived certificates:`n`n") + $null = $sb.Append((($AppsWithLongCerts | ForEach-Object { "- $($_.displayName) (AppId: $($_.appId))" }) -join "`n")) + $null = $sb.Append("`n`n") } if ($SPsWithLongCerts.Count -gt 0) { - $Result += "## Service principals with long-lived certificates:`n`n" - $Result += ($SPsWithLongCerts | ForEach-Object { "- $($_.displayName) (AppId: $($_.appId))" }) -join "`n" + $null = $sb.Append("## Service principals with long-lived certificates:`n`n") + $null = $sb.Append((($SPsWithLongCerts | ForEach-Object { "- $($_.displayName) (AppId: $($_.appId))" }) -join "`n")) } + $Result = $sb.ToString() } Add-CippTestResult -TenantFilter $Tenant -TestId 'ZTNA21773' -TestType 'Identity' -Status $Status -ResultMarkdown $Result -Risk 'Medium' -Name 'Applications do not have certificates with expiration longer than 180 days' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'Application Management' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21782.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21782.ps1 index dc190d978943..738136e66f24 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21782.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21782.ps1 @@ -16,15 +16,12 @@ function Invoke-CippTestZTNA21782 { $PhishResistantMethods = @('passKeyDeviceBound', 'passKeyDeviceBoundAuthenticator', 'windowsHelloForBusiness') - # Join user registration details with role assignments - $results = $UserRegistrationDetails | Where-Object { - $userId = $_.id - $RoleAssignments | Where-Object { $_.principalId -eq $userId } - } | ForEach-Object { - $user = $_ - $userRoles = $RoleAssignments | Where-Object { $_.principalId -eq $user.id } + $RoleAssignmentsByPrincipal = $RoleAssignments | Group-Object principalId -AsHashTable -AsString + $results = [System.Collections.Generic.List[object]]::new() + foreach ($user in $UserRegistrationDetails) { + if (-not $RoleAssignmentsByPrincipal.ContainsKey($user.id)) { continue } + $userRoles = $RoleAssignmentsByPrincipal[$user.id] $hasPhishResistant = $false - if ($user.methodsRegistered) { foreach ($method in $PhishResistantMethods) { if ($user.methodsRegistered -contains $method) { @@ -33,21 +30,20 @@ function Invoke-CippTestZTNA21782 { } } } - - [PSCustomObject]@{ + $results.Add([PSCustomObject]@{ id = $user.id userDisplayName = $user.userDisplayName roleDisplayName = ($userRoles.roleDefinitionName -join ', ') methodsRegistered = $user.methodsRegistered phishResistantAuthMethod = $hasPhishResistant - } + }) } - $totalUserCount = $results.Length - $phishResistantPrivUsers = $results | Where-Object { $_.phishResistantAuthMethod } - $phishablePrivUsers = $results | Where-Object { !$_.phishResistantAuthMethod } + $totalUserCount = $results.Count + $phishResistantPrivUsers = $results.Where({ $_.phishResistantAuthMethod }) + $phishablePrivUsers = $results.Where({ !$_.phishResistantAuthMethod }) - $phishResistantPrivUserCount = $phishResistantPrivUsers.Length + $phishResistantPrivUserCount = $phishResistantPrivUsers.Count $passed = $totalUserCount -eq $phishResistantPrivUserCount diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 index 5c82edcc4e51..7cba73ae4c14 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21797.ps1 @@ -33,7 +33,7 @@ function Invoke-CippTestZTNA21797 { } $passwordlessEnabled = $false - $passwordlessAuthMethods = @() + $passwordlessAuthMethods = [System.Collections.Generic.List[object]]::new() if ($authMethodsPolicy.authenticationMethodConfigurations) { foreach ($method in $authMethodsPolicy.authenticationMethodConfigurations) { @@ -55,11 +55,11 @@ function Invoke-CippTestZTNA21797 { if ($isPasswordless) { $passwordlessEnabled = $true - $passwordlessAuthMethods += [PSCustomObject]@{ + $passwordlessAuthMethods.Add([PSCustomObject]@{ Name = $methodName State = $methodState AdditionalInfo = $additionalInfo - } + }) } } } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21858.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21858.ps1 index 0038f540e3f1..79006a05d989 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21858.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21858.ps1 @@ -14,14 +14,14 @@ function Invoke-CippTestZTNA21858 { $InactivityThresholdDays = 90 $Today = Get-Date - $EnabledGuests = $Guests | Where-Object { $_.AccountEnabled -eq $true } + $EnabledGuests = $Guests.Where({ $_.AccountEnabled -eq $true }) if (-not $EnabledGuests) { Add-CippTestResult -TenantFilter $Tenant -TestId 'ZTNA21858' -TestType 'Identity' -Status 'Passed' -ResultMarkdown 'No guest users found in the tenant' -Risk 'Medium' -Name 'Inactive guest identities are disabled or removed from the tenant' -UserImpact 'Low' -ImplementationEffort 'Medium' -Category 'External collaboration' return } - $InactiveGuests = @() + $InactiveGuests = [System.Collections.Generic.List[object]]::new() foreach ($Guest in $EnabledGuests) { $DaysSinceLastActivity = $null @@ -34,22 +34,21 @@ function Invoke-CippTestZTNA21858 { } if ($null -ne $DaysSinceLastActivity -and $DaysSinceLastActivity -gt $InactivityThresholdDays) { - $InactiveGuests += $Guest + $InactiveGuests.Add($Guest) } } if ($InactiveGuests.Count -gt 0) { $Status = 'Failed' - $ResultLines = @( - "Found $($InactiveGuests.Count) inactive guest user(s) with no sign-in activity in the last $InactivityThresholdDays days." - '' - "**Total enabled guests:** $($EnabledGuests.Count)" - "**Inactive guests:** $($InactiveGuests.Count)" - "**Inactivity threshold:** $InactivityThresholdDays days" - '' - '**Top 10 inactive guest users:**' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add("Found $($InactiveGuests.Count) inactive guest user(s) with no sign-in activity in the last $InactivityThresholdDays days.") + $ResultLines.Add('') + $ResultLines.Add("**Total enabled guests:** $($EnabledGuests.Count)") + $ResultLines.Add("**Inactive guests:** $($InactiveGuests.Count)") + $ResultLines.Add("**Inactivity threshold:** $InactivityThresholdDays days") + $ResultLines.Add('') + $ResultLines.Add('**Top 10 inactive guest users:**') $Top10Guests = $InactiveGuests | Sort-Object { if ($_.signInActivity.lastSuccessfulSignInDateTime) { @@ -63,20 +62,20 @@ function Invoke-CippTestZTNA21858 { if ($Guest.signInActivity.lastSuccessfulSignInDateTime) { $LastActivity = [DateTime]$Guest.signInActivity.lastSuccessfulSignInDateTime $DaysInactive = [Math]::Round(($Today - $LastActivity).TotalDays, 0) - $ResultLines += "- $($Guest.displayName) ($($Guest.userPrincipalName)) - Last sign-in: $DaysInactive days ago" + $ResultLines.Add("- $($Guest.displayName) ($($Guest.userPrincipalName)) - Last sign-in: $DaysInactive days ago") } else { $Created = [DateTime]$Guest.createdDateTime $DaysSinceCreated = [Math]::Round(($Today - $Created).TotalDays, 0) - $ResultLines += "- $($Guest.displayName) ($($Guest.userPrincipalName)) - Never signed in (Created $DaysSinceCreated days ago)" + $ResultLines.Add("- $($Guest.displayName) ($($Guest.userPrincipalName)) - Never signed in (Created $DaysSinceCreated days ago)") } } if ($InactiveGuests.Count -gt 10) { - $ResultLines += "- ... and $($InactiveGuests.Count - 10) more inactive guest(s)" + $ResultLines.Add("- ... and $($InactiveGuests.Count - 10) more inactive guest(s)") } - $ResultLines += '' - $ResultLines += '**Recommendation:** Review and remove or disable inactive guest accounts to reduce security risks.' + $ResultLines.Add('') + $ResultLines.Add('**Recommendation:** Review and remove or disable inactive guest accounts to reduce security risks.') $Result = $ResultLines -join "`n" } else { diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21868.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21868.ps1 index 78428bf1ca43..0df9b441fcef 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21868.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21868.ps1 @@ -68,29 +68,31 @@ function Invoke-CippTestZTNA21868 { if ($HasGuestAppOwners -or $HasGuestSpOwners) { $Status = 'Failed' - $Result = "Guest users own applications or service principals`n`n" + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append("Guest users own applications or service principals`n`n") if ($HasGuestAppOwners -and $HasGuestSpOwners) { - $Result += "## Guest users own both applications and service principals`n`n" - $Result += "### Applications owned by guest users`n`n" - $Result += "| User Display Name | User Principal Name | Application |`n" - $Result += "| :---------------- | :------------------ | :---------- |`n" - $Result += ($GuestAppOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.appDisplayName) |" }) -join "`n" - $Result += "`n`n### Service principals owned by guest users`n`n" - $Result += "| User Display Name | User Principal Name | Service Principal |`n" - $Result += "| :---------------- | :------------------ | :---------------- |`n" - $Result += ($GuestSpOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.spDisplayName) |" }) -join "`n" + $null = $sb.Append("## Guest users own both applications and service principals`n`n") + $null = $sb.Append("### Applications owned by guest users`n`n") + $null = $sb.Append("| User Display Name | User Principal Name | Application |`n") + $null = $sb.Append("| :---------------- | :------------------ | :---------- |`n") + $null = $sb.Append((($GuestAppOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.appDisplayName) |" }) -join "`n")) + $null = $sb.Append("`n`n### Service principals owned by guest users`n`n") + $null = $sb.Append("| User Display Name | User Principal Name | Service Principal |`n") + $null = $sb.Append("| :---------------- | :------------------ | :---------------- |`n") + $null = $sb.Append((($GuestSpOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.spDisplayName) |" }) -join "`n")) } elseif ($HasGuestAppOwners) { - $Result += "## Guest users own applications`n`n" - $Result += "| User Display Name | User Principal Name | Application |`n" - $Result += "| :---------------- | :------------------ | :---------- |`n" - $Result += ($GuestAppOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.appDisplayName) |" }) -join "`n" + $null = $sb.Append("## Guest users own applications`n`n") + $null = $sb.Append("| User Display Name | User Principal Name | Application |`n") + $null = $sb.Append("| :---------------- | :------------------ | :---------- |`n") + $null = $sb.Append((($GuestAppOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.appDisplayName) |" }) -join "`n")) } elseif ($HasGuestSpOwners) { - $Result += "## Guest users own service principals`n`n" - $Result += "| User Display Name | User Principal Name | Service Principal |`n" - $Result += "| :---------------- | :------------------ | :---------------- |`n" - $Result += ($GuestSpOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.spDisplayName) |" }) -join "`n" + $null = $sb.Append("## Guest users own service principals`n`n") + $null = $sb.Append("| User Display Name | User Principal Name | Service Principal |`n") + $null = $sb.Append("| :---------------- | :------------------ | :---------------- |`n") + $null = $sb.Append((($GuestSpOwners | ForEach-Object { "| $($_.displayName) | $($_.userPrincipalName) | $($_.spDisplayName) |" }) -join "`n")) } + $Result = $sb.ToString() } else { $Status = 'Passed' $Result = 'No guest users own any applications or service principals in the tenant' diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21869.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21869.ps1 index a6463412b080..9c3f471a94f8 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21869.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21869.ps1 @@ -26,25 +26,24 @@ function Invoke-CippTestZTNA21869 { $Status = 'Investigate' - $ResultLines = @( - "Found $($AppsWithoutAssignment.Count) enterprise application(s) without assignment requirements." - '' - '**Applications without user assignment requirements:**' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add("Found $($AppsWithoutAssignment.Count) enterprise application(s) without assignment requirements.") + $ResultLines.Add('') + $ResultLines.Add('**Applications without user assignment requirements:**') $Top10Apps = $AppsWithoutAssignment | Select-Object -First 10 foreach ($App in $Top10Apps) { - $ResultLines += "- $($App.displayName) (SSO: $($App.preferredSingleSignOnMode))" + $ResultLines.Add("- $($App.displayName) (SSO: $($App.preferredSingleSignOnMode))") } if ($AppsWithoutAssignment.Count -gt 10) { - $ResultLines += "- ... and $($AppsWithoutAssignment.Count - 10) more application(s)" + $ResultLines.Add("- ... and $($AppsWithoutAssignment.Count - 10) more application(s)") } - $ResultLines += '' - $ResultLines += '**Note:** Full provisioning scope validation requires Graph API synchronization endpoint not available in cache.' - $ResultLines += '' - $ResultLines += '**Recommendation:** Enable user assignment requirements or configure scoped provisioning to limit application access.' + $ResultLines.Add('') + $ResultLines.Add('**Note:** Full provisioning scope validation requires Graph API synchronization endpoint not available in cache.') + $ResultLines.Add('') + $ResultLines.Add('**Recommendation:** Enable user assignment requirements or configure scoped provisioning to limit application access.') $Result = $ResultLines -join "`n" diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21877.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21877.ps1 index 4d71643c7f4a..994f79eb9bf4 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21877.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21877.ps1 @@ -25,27 +25,26 @@ function Invoke-CippTestZTNA21877 { } else { $Status = 'Failed' - $ResultLines = @( - "Found $($GuestsWithoutSponsors.Count) guest user(s) without sponsors out of $($Guests.Count) total guests." - '' - "**Total guests:** $($Guests.Count)" - "**Guests without sponsors:** $($GuestsWithoutSponsors.Count)" - "**Guests with sponsors:** $($Guests.Count - $GuestsWithoutSponsors.Count)" - '' - '**Top 10 guests without sponsors:**' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add("Found $($GuestsWithoutSponsors.Count) guest user(s) without sponsors out of $($Guests.Count) total guests.") + $ResultLines.Add('') + $ResultLines.Add("**Total guests:** $($Guests.Count)") + $ResultLines.Add("**Guests without sponsors:** $($GuestsWithoutSponsors.Count)") + $ResultLines.Add("**Guests with sponsors:** $($Guests.Count - $GuestsWithoutSponsors.Count)") + $ResultLines.Add('') + $ResultLines.Add('**Top 10 guests without sponsors:**') $Top10Guests = $GuestsWithoutSponsors | Select-Object -First 10 foreach ($Guest in $Top10Guests) { - $ResultLines += "- $($Guest.displayName) ($($Guest.userPrincipalName))" + $ResultLines.Add("- $($Guest.displayName) ($($Guest.userPrincipalName))") } if ($GuestsWithoutSponsors.Count -gt 10) { - $ResultLines += "- ... and $($GuestsWithoutSponsors.Count - 10) more guest(s)" + $ResultLines.Add("- ... and $($GuestsWithoutSponsors.Count - 10) more guest(s)") } - $ResultLines += '' - $ResultLines += '**Recommendation:** Assign sponsors to all guest accounts for better accountability and lifecycle management.' + $ResultLines.Add('') + $ResultLines.Add('**Recommendation:** Assign sponsors to all guest accounts for better accountability and lifecycle management.') $Result = $ResultLines -join "`n" } diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21886.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21886.ps1 index 030a60f394e9..45d331f64871 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21886.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21886.ps1 @@ -25,29 +25,28 @@ function Invoke-CippTestZTNA21886 { $Status = 'Investigate' - $ResultLines = @( - "Found $($AppsWithSSO.Count) application(s) configured for SSO." - '' - '**Applications with SSO enabled:**' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add("Found $($AppsWithSSO.Count) application(s) configured for SSO.") + $ResultLines.Add('') + $ResultLines.Add('**Applications with SSO enabled:**') $SSOByType = $AppsWithSSO | Group-Object -Property preferredSingleSignOnMode foreach ($Group in $SSOByType) { - $ResultLines += '' - $ResultLines += "**$($Group.Name.ToUpper()) SSO** ($($Group.Count) app(s)):" + $ResultLines.Add('') + $ResultLines.Add("**$($Group.Name.ToUpper()) SSO** ($($Group.Count) app(s)):") $Top5 = $Group.Group | Select-Object -First 5 foreach ($App in $Top5) { - $ResultLines += "- $($App.displayName)" + $ResultLines.Add("- $($App.displayName)") } if ($Group.Count -gt 5) { - $ResultLines += "- ... and $($Group.Count - 5) more" + $ResultLines.Add("- ... and $($Group.Count - 5) more") } } - $ResultLines += '' - $ResultLines += '**Note:** Provisioning template and job validation requires Graph API synchronization endpoint not available in cache.' - $ResultLines += '' - $ResultLines += '**Recommendation:** Configure automatic user provisioning for applications that support it to ensure consistent access management.' + $ResultLines.Add('') + $ResultLines.Add('**Note:** Provisioning template and job validation requires Graph API synchronization endpoint not available in cache.') + $ResultLines.Add('') + $ResultLines.Add('**Recommendation:** Configure automatic user provisioning for applications that support it to ensure consistent access management.') $Result = $ResultLines -join "`n" diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21896.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21896.ps1 index 125185f05142..e2b896dad462 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21896.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21896.ps1 @@ -28,25 +28,24 @@ function Invoke-CippTestZTNA21896 { $TotalWithCreds = $SPsWithPassCreds.Count + $SPsWithKeyCreds.Count $Status = 'Investigate' - $ResultLines = @( - "Found $TotalWithCreds service principal(s) with credentials configured in the tenant, which represents a security risk." - '' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add("Found $TotalWithCreds service principal(s) with credentials configured in the tenant, which represents a security risk.") + $ResultLines.Add('') if ($SPsWithPassCreds.Count -gt 0) { - $ResultLines += "**Service principals with password credentials:** $($SPsWithPassCreds.Count)" - $ResultLines += '' + $ResultLines.Add("**Service principals with password credentials:** $($SPsWithPassCreds.Count)") + $ResultLines.Add('') } if ($SPsWithKeyCreds.Count -gt 0) { - $ResultLines += "**Service principals with key credentials (certificates):** $($SPsWithKeyCreds.Count)" - $ResultLines += '' + $ResultLines.Add("**Service principals with key credentials (certificates):** $($SPsWithKeyCreds.Count)") + $ResultLines.Add('') } - $ResultLines += '**Security implications:**' - $ResultLines += '- Service principals with credentials can be compromised if not properly secured' - $ResultLines += '- Password credentials are less secure than managed identities or certificate-based authentication' - $ResultLines += '- Consider using managed identities where possible to eliminate credential management' + $ResultLines.Add('**Security implications:**') + $ResultLines.Add('- Service principals with credentials can be compromised if not properly secured') + $ResultLines.Add('- Password credentials are less secure than managed identities or certificate-based authentication') + $ResultLines.Add('- Consider using managed identities where possible to eliminate credential management') $Result = $ResultLines -join "`n" diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21992.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21992.ps1 index eadb787f29c7..61b276337c13 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21992.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA21992.ps1 @@ -68,40 +68,39 @@ function Invoke-CippTestZTNA21992 { $Status = 'Failed' - $ResultLines = @( - "Found $($OldAppCerts.Count) application(s) and $($OldSPCerts.Count) service principal(s) with certificates not rotated within $RotationThresholdDays days." - '' - "**Certificate rotation threshold:** $RotationThresholdDays days" - '' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add("Found $($OldAppCerts.Count) application(s) and $($OldSPCerts.Count) service principal(s) with certificates not rotated within $RotationThresholdDays days.") + $ResultLines.Add('') + $ResultLines.Add("**Certificate rotation threshold:** $RotationThresholdDays days") + $ResultLines.Add('') if ($OldAppCerts.Count -gt 0) { - $ResultLines += '**Applications with old certificates:**' + $ResultLines.Add('**Applications with old certificates:**') $Top10Apps = $OldAppCerts | Select-Object -First 10 foreach ($App in $Top10Apps) { $DaysOld = [Math]::Round(((Get-Date) - $App.OldestCertDate).TotalDays, 0) - $ResultLines += "- $($App.DisplayName) (Certificate age: $DaysOld days)" + $ResultLines.Add("- $($App.DisplayName) (Certificate age: $DaysOld days)") } if ($OldAppCerts.Count -gt 10) { - $ResultLines += "- ... and $($OldAppCerts.Count - 10) more application(s)" + $ResultLines.Add("- ... and $($OldAppCerts.Count - 10) more application(s)") } - $ResultLines += '' + $ResultLines.Add('') } if ($OldSPCerts.Count -gt 0) { - $ResultLines += '**Service principals with old certificates:**' + $ResultLines.Add('**Service principals with old certificates:**') $Top10SPs = $OldSPCerts | Select-Object -First 10 foreach ($SP in $Top10SPs) { $DaysOld = [Math]::Round(((Get-Date) - $SP.OldestCertDate).TotalDays, 0) - $ResultLines += "- $($SP.DisplayName) (Certificate age: $DaysOld days)" + $ResultLines.Add("- $($SP.DisplayName) (Certificate age: $DaysOld days)") } if ($OldSPCerts.Count -gt 10) { - $ResultLines += "- ... and $($OldSPCerts.Count - 10) more service principal(s)" + $ResultLines.Add("- ... and $($OldSPCerts.Count - 10) more service principal(s)") } - $ResultLines += '' + $ResultLines.Add('') } - $ResultLines += '**Recommendation:** Rotate certificates regularly to reduce the risk of credential compromise.' + $ResultLines.Add('**Recommendation:** Rotate certificates regularly to reduce the risk of credential compromise.') $Result = $ResultLines -join "`n" diff --git a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22128.ps1 b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22128.ps1 index e002c3d2d2f7..e9773a34e7bd 100644 --- a/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22128.ps1 +++ b/Modules/CIPPTests/Public/Tests/ZTNA/Identity/Invoke-CippTestZTNA22128.ps1 @@ -36,17 +36,17 @@ function Invoke-CippTestZTNA22128 { 'fe930be7-5e62-47db-91af-98c3a49a38b1' ) - $GuestsInPrivilegedRoles = @() + $GuestsInPrivilegedRoles = [System.Collections.Generic.List[object]]::new() foreach ($Role in $Roles) { if ($Role.roleTemplateId -in $PrivilegedRoleTemplateIds -and $Role.members) { foreach ($Member in $Role.members) { if ($GuestIdHash.ContainsKey($Member.id)) { - $GuestsInPrivilegedRoles += [PSCustomObject]@{ + $GuestsInPrivilegedRoles.Add([PSCustomObject]@{ RoleName = $Role.displayName GuestId = $Member.id GuestDisplayName = $Member.displayName GuestUserPrincipalName = $Member.userPrincipalName - } + }) } } } @@ -59,26 +59,25 @@ function Invoke-CippTestZTNA22128 { $Status = 'Failed' - $ResultLines = @( - "Found $($GuestsInPrivilegedRoles.Count) guest user(s) with privileged role assignments." - '' - "**Total guests in tenant:** $($Guests.Count)" - "**Guests with privileged roles:** $($GuestsInPrivilegedRoles.Count)" - '' - '**Guest users in privileged roles:**' - ) + $ResultLines = [System.Collections.Generic.List[string]]::new() + $ResultLines.Add("Found $($GuestsInPrivilegedRoles.Count) guest user(s) with privileged role assignments.") + $ResultLines.Add('') + $ResultLines.Add("**Total guests in tenant:** $($Guests.Count)") + $ResultLines.Add("**Guests with privileged roles:** $($GuestsInPrivilegedRoles.Count)") + $ResultLines.Add('') + $ResultLines.Add('**Guest users in privileged roles:**') $RoleGroups = $GuestsInPrivilegedRoles | Group-Object -Property RoleName foreach ($RoleGroup in $RoleGroups) { - $ResultLines += '' - $ResultLines += "**$($RoleGroup.Name)** ($($RoleGroup.Count) guest(s)):" + $ResultLines.Add('') + $ResultLines.Add("**$($RoleGroup.Name)** ($($RoleGroup.Count) guest(s)):") foreach ($Guest in $RoleGroup.Group) { - $ResultLines += "- $($Guest.GuestDisplayName) ($($Guest.GuestUserPrincipalName))" + $ResultLines.Add("- $($Guest.GuestDisplayName) ($($Guest.GuestUserPrincipalName))") } } - $ResultLines += '' - $ResultLines += '**Security concern:** Guest users should not have privileged directory roles. Consider using separate admin accounts for external administrators or removing privileged access.' + $ResultLines.Add('') + $ResultLines.Add('**Security concern:** Guest users should not have privileged directory roles. Consider using separate admin accounts for external administrators or removing privileged access.') $Result = $ResultLines -join "`n" From 7b341600ea66916a6393af4882ed9e2e55a58a51 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 31 May 2026 22:52:53 +0200 Subject: [PATCH 80/90] update openapi spec with generated one --- openapi.json | 38707 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 31588 insertions(+), 7119 deletions(-) diff --git a/openapi.json b/openapi.json index 194c1d67d499..a0c8a4fa087d 100644 --- a/openapi.json +++ b/openapi.json @@ -1,9429 +1,33765 @@ { - "externalDocs": { - "url": "https://cipp.app", - "description": "CIPP Documentation" - }, + "openapi": "3.1.0", "info": { - "version": "1.0.1", - "description": "CIPP-API is an Azure Function App operating as the logic layer for the CIPP platform. It is composed primarily of standard Azure Functions with a handful of Azure Durable Functions handling more complex actions (mostly applying standards and running tenant analysis). API documentation is primarily intended to aid in further development of the CIPP platform. This API will most likely be out-dated and we request users to help us keep this up to date.", - "title": "CIPP-API Documentation" + "title": "CIPP API", + "version": "auto", + "description": "CIPP-API is an Azure Function App providing the logic layer for the CIPP platform. This spec is auto-generated via static analysis of both the API (PowerShell) and frontend (React/Next.js) repositories.", + "x-cipp-docs": "https://docs.cipp.app" }, - "paths": { - "/AddGroup": { - "post": { - "description": "AddGroup", - "summary": "AddGroup", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "selectedTenants", - "in": "body" + "servers": [ + { + "url": "/api", + "description": "CIPP API" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "components": { + "schemas": { + "LabelValue": { + "type": "object", + "description": "Autocomplete/select field. Most dropdowns in CIPP use this shape. Backend typically unwraps via: $x.value ?? $x", + "properties": { + "label": { + "type": "string" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantid", - "in": "body" + "value": { + "type": "string" } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + } + }, + "LabelValueNumber": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "number" + } + } + }, + "GroupRef": { + "type": "object", + "description": "Reference to a group, including type metadata for routing (Graph vs EXO).", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string", + "description": "Group object ID" + }, + "addedFields": { + "type": "object", + "properties": { + "groupType": { + "type": "string", + "enum": [ + "Security", + "Distribution list", + "Mail-Enabled Security", + "Microsoft 365" + ] } + } + } + } + }, + "PostExecution": { + "type": "object", + "description": "Notification channels triggered after task completion.", + "properties": { + "webhook": { + "type": "boolean" + }, + "email": { + "type": "boolean" + }, + "psa": { + "type": "boolean" + } + } + }, + "ScheduledTask": { + "type": "object", + "description": "Controls deferred execution. If Enabled is true, task is queued rather than run immediately.", + "properties": { + "Enabled": { + "type": "boolean" + }, + "date": { + "type": "string", + "format": "date-time" + } + } + }, + "StandardResults": { + "type": "object", + "description": "Standard CIPP API response envelope.", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "string" }, - "description": "Successful operation" + "description": "Result messages, one per operation. Mix of success and error strings." } } + }, + "DynamicExtensionFields": { + "type": "object", + "description": "Per-tenant custom directory extension attributes. Schema varies by tenant configuration and cannot be statically defined.", + "additionalProperties": true, + "x-cipp-dynamic": true + } + }, + "parameters": { + "tenantFilter": { + "name": "tenantFilter", + "in": "query", + "description": "Target tenant domain or 'AllTenants' for multi-tenant operations.", + "required": true, + "schema": { + "type": "string" + } + }, + "selectedTenants": { + "name": "selectedTenants", + "in": "query", + "description": "Comma-separated list of tenant domains for bulk operations.", + "required": false, + "schema": { + "type": "string" + } } }, - "/AddChocoApp": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "Azure AD bearer token. Obtained via MSAL against the CIPP API app registration." + } + } + }, + "paths": { + "/api/AddAPDevice": { "post": { - "description": "AddChocoApp", - "summary": "AddChocoApp", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "InstallationIntent", - "in": "body" - }, + "summary": "AddAPDevice", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "AssignTo", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Adds Autopilot devices to a tenant via Partner Center API\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "autopilotData": { + "type": "string" + }, + "Groupname": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } } } } }, - "/AddWin32ScriptApp": { + "/api/AddAlert": { "post": { - "description": "AddWin32ScriptApp", - "summary": "AddWin32ScriptApp", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ApplicationName", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "installScript", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AssignTo", - "in": "body" - }, + "summary": "AddAlert", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ { - "required": false, - "schema": { - "type": "string" - }, - "name": "InstallationIntent", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Alert.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "actions": { + "type": "array", + "items": { + "type": "string" + } + }, + "AlertComment": { + "type": "string" + }, + "command": { + "$ref": "#/components/schemas/LabelValue" + }, + "conditions": { + "type": "string" + }, + "excludedTenants": { + "type": "string" + }, + "logbook": { + "$ref": "#/components/schemas/LabelValue" + }, + "postExecution": { + "type": "array", + "items": { + "type": "string" + } + }, + "preset": { + "$ref": "#/components/schemas/LabelValue" + }, + "recurrence": { + "$ref": "#/components/schemas/LabelValue" + }, + "RowKey": { + "type": "string" + }, + "startDateTime": { + "type": "string", + "format": "date-time" + }, + "tenantFilter": { + "type": "string" + }, + "count": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } } } } }, - "/RemoveUser": { - "get": { - "description": "RemoveUser", - "summary": "RemoveUser", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "/api/AddAssignmentFilter": { + "post": { + "summary": "AddAssignmentFilter", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "assignmentFilterManagementType": { + "type": "string", + "enum": [ + "devices", + "apps" + ] + }, + "platform": { + "type": "string" + }, + "rule": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } } } } }, - "/ListTeams": { - "get": { - "description": "ListTeams", - "summary": "ListTeams", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "type", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "/api/AddAssignmentFilterTemplate": { + "post": { + "summary": "AddAssignmentFilterTemplate", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecGroupsDelete": { - "get": { - "description": "ExecGroupsDelete", - "summary": "ExecGroupsDelete", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "GroupType", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "displayName", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "assignmentFilterManagementType": { + "type": "string", + "enum": [ + "devices", + "apps" + ] + }, + "platform": { + "type": "string" + }, + "rule": { + "type": "string" + }, + "displayname": { + "type": "string" + }, + "Description": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListRoles": { - "get": { - "description": "ListRoles", - "summary": "ListRoles", - "tags": ["GET"], - "parameters": [ + "/api/AddAutopilotConfig": { + "post": { + "summary": "AddAutopilotConfig", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "allowWhiteGlove": { + "type": "boolean" + }, + "Assignto": { + "type": "boolean" + }, + "Autokeyboard": { + "type": "boolean" + }, + "CollectHash": { + "type": "boolean" + }, + "DeploymentMode": { + "type": "boolean" + }, + "Description": { + "type": "string" + }, + "DeviceNameTemplate": { + "type": "string" + }, + "DisplayName": { + "type": "string" + }, + "HideChangeAccount": { + "type": "boolean" + }, + "HidePrivacy": { + "type": "boolean" + }, + "HideTerms": { + "type": "boolean" + }, + "languages": { + "$ref": "#/components/schemas/LabelValue" + }, + "NotLocalAdmin": { + "type": "boolean" + }, + "selectedTenants": { + "type": "string" + } + } + } + } } } } }, - "/ListUserMailboxRules": { - "get": { - "description": "ListUserMailboxRules", - "summary": "ListUserMailboxRules", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" - }, + "/api/AddBPATemplate": { + "post": { + "summary": "AddBPATemplate", + "tags": [ + "Tenant > Tools" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.BestPracticeAnalyser.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "style": { + "type": "string", + "enum": [ + "Tenant", + "Table" + ] + } + } + } + } } } } }, - "/ExecBECCheck": { - "get": { - "description": "ExecBECCheck", - "summary": "ExecBECCheck", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "GUID", - "in": "query" - }, + "/api/AddCAPolicy": { + "post": { + "summary": "AddCAPolicy", + "tags": [ + "Tenant > Conditional" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "userid", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "CreateGroups": { + "type": "boolean" + }, + "DisableSD": { + "type": "boolean" + }, + "NewState": { + "type": "string", + "enum": [ + "donotchange", + "Enabled", + "Disabled", + "enabledForReportingButNotEnforced" + ] + }, + "overwrite": { + "type": "boolean" + }, + "RawJSON": { + "type": "string" + }, + "replacename": { + "type": "string", + "enum": [ + "leave", + "displayName", + "AllUsers" + ] + }, + "TemplateList": { + "$ref": "#/components/schemas/LabelValue" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } } } } }, - "/ListCalendarPermissions": { - "get": { - "description": "ListCalendarPermissions", - "summary": "ListCalendarPermissions", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" - }, + "/api/AddCATemplate": { + "post": { + "summary": "AddCATemplate", + "tags": [ + "Tenant > Conditional" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "policySource": { + "$ref": "#/components/schemas/LabelValue" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } } } } }, - "/ExecAddSPN": { - "get": { - "description": "ExecAddSPN", - "summary": "ExecAddSPN", - "tags": ["GET"], - "parameters": [ + "/api/AddChocoApp": { + "post": { + "summary": "AddChocoApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Enable", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ApplicationName": { + "type": "string" + }, + "AssignTo": { + "type": "string" + }, + "customArguments": { + "type": "string" + }, + "CustomGroup": { + "type": "string" + }, + "description": { + "type": "string" + }, + "DisableRestart": { + "type": "string" + }, + "InstallAsSystem": { + "type": "string" + }, + "InstallationIntent": { + "type": "string" + }, + "PackageName": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "CustomRepo": { + "type": "string" + } + } + } + } } } } }, - "/ListLicenses": { - "get": { - "description": "ListLicenses", - "summary": "ListLicenses", - "tags": ["GET"], - "parameters": [ + "/api/AddConnectionFilter": { + "post": { + "summary": "AddConnectionFilter", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.ConnectionFilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "PowerShellCommand": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "TemplateList": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } } } } }, - "/AddCATemplate": { + "/api/AddConnectionFilterTemplate": { "post": { - "description": "AddCATemplate", - "summary": "AddCATemplate", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "rawjson", - "in": "body" - }, + "summary": "AddConnectionFilterTemplate", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "name", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.ConnectionFilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "PowerShellCommand": { + "type": "string" + } + } + } + } } } } }, - "/ExecIncidentsList": { - "get": { - "description": "ExecIncidentsList", - "summary": "ExecIncidentsList", - "tags": ["GET"], - "parameters": [ + "/api/AddContact": { + "post": { + "summary": "AddContact", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Contact.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Company": { + "type": "string" + }, + "Title": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "State": { + "type": "string" + }, + "tenantid": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "PostalCode": { + "type": "string" + }, + "StreetAddress": { + "type": "string" + }, + "mobilePhone": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "mailTip": { + "type": "string" + }, + "website": { + "type": "string" + }, + "City": { + "type": "string" + }, + "hidefromGAL": { + "type": "string" + }, + "CountryOrRegion": { + "type": "string" + } + } + } + } } } } }, - "/AddSharedMailbox": { + "/api/AddContactTemplates": { "post": { - "description": "AddSharedMailbox", - "summary": "AddSharedMailbox", - "tags": ["POST"], - "parameters": [ + "summary": "AddContactTemplates", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantid", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Contact.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "hidefromGAL": { + "type": "boolean" + }, + "streetAddress": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "country": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "mobilePhone": { + "type": "string" + }, + "businessPhone": { + "type": "string" + }, + "jobTitle": { + "type": "string" + }, + "website": { + "type": "string" + }, + "mailTip": { + "type": "string" + } + } + } + } } } } }, - "/ListApps": { - "get": { - "description": "ListApps", - "summary": "ListApps", - "tags": ["GET"], - "parameters": [ + "/api/AddDefenderDeployment": { + "post": { + "summary": "AddDefenderDeployment", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Compliance": { + "type": "object", + "properties": { + "AllowMEMEnforceCompliance": { + "type": "boolean" + }, + "AppSync": { + "type": "boolean" + }, + "BlockunsupportedOS": { + "type": "boolean" + }, + "ConnectAndroid": { + "type": "boolean" + }, + "ConnectAndroidCompliance": { + "type": "boolean" + }, + "ConnectIos": { + "type": "boolean" + }, + "ConnectIosCompliance": { + "type": "boolean" + }, + "ConnectWindows": { + "type": "boolean" + } + } + }, + "ASR": { + "type": "object", + "properties": { + "AssignTo": { + "type": "string", + "enum": [ + "none", + "allLicensedUsers", + "AllDevices", + "AllDevicesAndUsers" + ] + }, + "BlockAdobeChild": { + "type": "boolean" + }, + "BlockCredentialStealing": { + "type": "boolean" + }, + "BlockExesMail": { + "type": "boolean" + }, + "blockJSVB": { + "type": "boolean" + }, + "BlockObfuscatedScripts": { + "type": "boolean" + }, + "BlockOfficeApps": { + "type": "boolean" + }, + "blockOfficeChild": { + "type": "boolean" + }, + "blockOfficeComChild": { + "type": "boolean" + }, + "BlockOfficeExes": { + "type": "boolean" + }, + "BlockPSExec": { + "type": "boolean" + }, + "BlockSafeMode": { + "type": "boolean" + }, + "BlockSystemTools": { + "type": "boolean" + }, + "BlockUnsignedDrivers": { + "type": "boolean" + }, + "BlockUntrustedUSB": { + "type": "boolean" + }, + "BlockWebshellForServers": { + "type": "boolean" + }, + "BlockWin32Macro": { + "type": "boolean" + }, + "BlockYoungExe": { + "type": "boolean" + }, + "EnableRansomwareVac": { + "type": "boolean" + }, + "Mode": { + "type": "string", + "enum": [ + "block", + "audit", + "warn" + ] + }, + "WMIPersistence": { + "type": "boolean" + } + } + }, + "EDR": { + "type": "object", + "properties": { + "AssignTo": { + "type": "string", + "enum": [ + "none", + "allLicensedUsers", + "AllDevices", + "AllDevicesAndUsers" + ] + }, + "Config": { + "type": "boolean" + }, + "SampleSharing": { + "type": "boolean" + } + } + }, + "Exclusion": { + "type": "object", + "properties": { + "AssignTo": { + "type": "string", + "enum": [ + "none", + "allLicensedUsers", + "AllDevices", + "AllDevicesAndUsers" + ] + } + } + }, + "Policy": { + "type": "object", + "properties": { + "AllowBehavior": { + "type": "boolean" + }, + "AllowCloudProtection": { + "type": "boolean" + }, + "AllowDownloadable": { + "type": "boolean" + }, + "AllowEmailScanning": { + "type": "boolean" + }, + "AllowFullScanNetwork": { + "type": "boolean" + }, + "AllowFullScanRemovable": { + "type": "boolean" + }, + "AllowNetwork": { + "type": "boolean" + }, + "AllowOnAccessProtection": { + "$ref": "#/components/schemas/LabelValue" + }, + "AllowRealTime": { + "type": "boolean" + }, + "AllowScriptScan": { + "type": "boolean" + }, + "AllowUI": { + "type": "boolean" + }, + "AssignTo": { + "type": "string", + "enum": [ + "none", + "allLicensedUsers", + "AllDevices", + "AllDevicesAndUsers" + ] + }, + "AvgCPULoadFactor": { + "type": "number" + }, + "CheckSigs": { + "type": "boolean" + }, + "CloudBlockLevel": { + "$ref": "#/components/schemas/LabelValue" + }, + "CloudExtendedTimeout": { + "type": "number" + }, + "DisableCatchupFullScan": { + "type": "boolean" + }, + "DisableCatchupQuickScan": { + "type": "boolean" + }, + "DisableLocalAdminMerge": { + "type": "boolean" + }, + "EnableNetworkProtection": { + "$ref": "#/components/schemas/LabelValue" + }, + "LowCPU": { + "type": "boolean" + }, + "MeteredConnectionUpdates": { + "type": "boolean" + }, + "Remediation.High": { + "$ref": "#/components/schemas/LabelValue" + }, + "Remediation.Low": { + "$ref": "#/components/schemas/LabelValue" + }, + "Remediation.Moderate": { + "$ref": "#/components/schemas/LabelValue" + }, + "Remediation.Severe": { + "$ref": "#/components/schemas/LabelValue" + }, + "ScanArchives": { + "type": "boolean" + }, + "SignatureUpdateInterval": { + "type": "number" + }, + "SubmitSamplesConsent": { + "$ref": "#/components/schemas/LabelValue" + } + } + }, + "selectedTenants": { + "type": "string" + }, + "showASR": { + "type": "boolean" + }, + "showDefenderDefaults": { + "type": "boolean" + }, + "showDefenderSetup": { + "type": "boolean" + }, + "showExclusionPolicy": { + "type": "boolean" + }, + "appSync": { + "type": "string" + }, + "ConnectIos": { + "type": "string" + }, + "ConnectAndroid": { + "type": "string" + }, + "ConnectIosCompliance": { + "type": "string" + }, + "BlockunsupportedOS": { + "type": "string" + }, + "ConnectMac": { + "type": "string" + }, + "Connectwindows": { + "type": "string" + }, + "ConnectAndroidCompliance": { + "type": "string" + }, + "AssignTo": { + "type": "string" + }, + "excludedExtensions": { + "type": "string" + }, + "excludedProcesses": { + "type": "string" + }, + "excludedPaths": { + "type": "string" + }, + "Mode": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddDomain": { + "post": { + "summary": "AddDomain", + "tags": [ + "Tenant > Administration > Domains" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddEditTransportRule": { + "post": { + "summary": "AddEditTransportRule", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function creates a new transport rule or edits an existing one (mail flow rule).\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.TransportRule.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AttachmentHasExecutableContent": { + "type": "string" + }, + "AttachmentIsPasswordProtected": { + "type": "string" + }, + "AttachmentIsUnsupported": { + "type": "string" + }, + "AttachmentNameMatchesPatterns": { + "type": "string" + }, + "AttachmentProcessingLimitExceeded": { + "type": "string" + }, + "AttachmentPropertyContainsWords": { + "type": "string" + }, + "ExceptIfAttachmentHasExecutableContent": { + "type": "string" + }, + "ExceptIfAttachmentIsPasswordProtected": { + "type": "string" + }, + "ExceptIfAttachmentIsUnsupported": { + "type": "string" + }, + "ExceptIfAttachmentNameMatchesPatterns": { + "type": "string" + }, + "ExceptIfAttachmentProcessingLimitExceeded": { + "type": "string" + }, + "ExceptIfAttachmentPropertyContainsWords": { + "type": "string" + }, + "IncidentReportContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LabelValue" + } + }, + "actionType": { + "type": "array", + "items": { + "type": "string" + } + }, + "ActivationDate": { + "type": "string", + "format": "date-time" + }, + "AnyOfCcHeader": { + "type": "string" + }, + "AnyOfCcHeaderMemberOf": { + "type": "string" + }, + "AnyOfRecipientAddressContainsWords": { + "type": "string" + }, + "AnyOfRecipientAddressMatchesPatterns": { + "type": "string" + }, + "AnyOfToCcHeader": { + "type": "string" + }, + "AnyOfToCcHeaderMemberOf": { + "type": "string" + }, + "AnyOfToHeader": { + "type": "string" + }, + "AnyOfToHeaderMemberOf": { + "type": "string" + }, + "ApplyClassification": { + "type": "string" + }, + "ApplyHtmlDisclaimerFallbackAction": { + "$ref": "#/components/schemas/LabelValue" + }, + "ApplyHtmlDisclaimerLocation": { + "$ref": "#/components/schemas/LabelValue" + }, + "ApplyHtmlDisclaimerText": { + "type": "string" + }, + "ApplyOME": { + "type": "string" + }, + "applyToAllMessages": { + "type": "boolean" + }, + "AttachmentContainsWords": { + "type": "string" + }, + "AttachmentExtensionMatchesWords": { + "type": "string" + }, + "AttachmentMatchesPatterns": { + "type": "string" + }, + "AttachmentSizeOver": { + "type": "string" + }, + "BlindCopyTo": { + "type": "string" + }, + "Comments": { + "type": "string" + }, + "conditionType": { + "type": "array", + "items": { + "type": "string" + } + }, + "CopyTo": { + "type": "string" + }, + "DeleteMessage": { + "type": "string" + }, + "Enabled": { + "type": "boolean" + }, + "ExceptIfAnyOfCcHeader": { + "type": "string" + }, + "ExceptIfAnyOfCcHeaderMemberOf": { + "type": "string" + }, + "ExceptIfAnyOfRecipientAddressContainsWords": { + "type": "string" + }, + "ExceptIfAnyOfRecipientAddressMatchesPatterns": { + "type": "string" + }, + "ExceptIfAnyOfToCcHeader": { + "type": "string" + }, + "ExceptIfAnyOfToCcHeaderMemberOf": { + "type": "string" + }, + "ExceptIfAnyOfToHeader": { + "type": "string" + }, + "ExceptIfAnyOfToHeaderMemberOf": { + "type": "string" + }, + "ExceptIfAttachmentContainsWords": { + "type": "string" + }, + "ExceptIfAttachmentExtensionMatchesWords": { + "type": "string" + }, + "ExceptIfAttachmentMatchesPatterns": { + "type": "string" + }, + "ExceptIfAttachmentSizeOver": { + "type": "string" + }, + "ExceptIfFrom": { + "type": "string" + }, + "ExceptIfFromAddressContainsWords": { + "type": "string" + }, + "ExceptIfFromAddressMatchesPatterns": { + "type": "string" + }, + "ExceptIfFromMemberOf": { + "type": "string" + }, + "ExceptIfFromScope": { + "type": "string" + }, + "ExceptIfHeaderContainsWords": { + "type": "string" + }, + "ExceptIfHeaderContainsWordsMessageHeader": { + "type": "string" + }, + "ExceptIfHeaderMatchesPatterns": { + "type": "string" + }, + "ExceptIfHeaderMatchesPatternsMessageHeader": { + "type": "string" + }, + "ExceptIfMessageSizeOver": { + "type": "string" + }, + "ExceptIfMessageTypeMatches": { + "type": "string" + }, + "ExceptIfRecipientAddressContainsWords": { + "type": "string" + }, + "ExceptIfRecipientAddressMatchesPatterns": { + "type": "string" + }, + "ExceptIfRecipientDomainIs": { + "type": "string" + }, + "ExceptIfSCLOver": { + "type": "string" + }, + "ExceptIfSenderDomainIs": { + "type": "string" + }, + "ExceptIfSenderIpRanges": { + "type": "string" + }, + "ExceptIfSentTo": { + "type": "string" + }, + "ExceptIfSentToMemberOf": { + "type": "string" + }, + "ExceptIfSentToScope": { + "type": "string" + }, + "ExceptIfSubjectContainsWords": { + "type": "string" + }, + "ExceptIfSubjectMatchesPatterns": { + "type": "string" + }, + "ExceptIfSubjectOrBodyContainsWords": { + "type": "string" + }, + "ExceptIfSubjectOrBodyMatchesPatterns": { + "type": "string" + }, + "ExceptIfWithImportance": { + "type": "string" + }, + "exceptionType": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExpiryDate": { + "type": "string", + "format": "date-time" + }, + "From": { + "type": "string" + }, + "FromAddressContainsWords": { + "type": "string" + }, + "FromAddressMatchesPatterns": { + "type": "string" + }, + "FromMemberOf": { + "type": "string" + }, + "FromScope": { + "type": "string" + }, + "GenerateIncidentReport": { + "type": "string" + }, + "GenerateNotification": { + "type": "string" + }, + "HeaderContainsWords": { + "type": "string" + }, + "HeaderContainsWordsMessageHeader": { + "type": "string" + }, + "HeaderMatchesPatterns": { + "type": "string" + }, + "HeaderMatchesPatternsMessageHeader": { + "type": "string" + }, + "MessageSizeOver": { + "type": "string" + }, + "MessageTypeMatches": { + "type": "string" + }, + "Mode": { + "$ref": "#/components/schemas/LabelValue" + }, + "ModerateMessageByManager": { + "type": "string" + }, + "ModerateMessageByUser": { + "type": "string" + }, + "Name": { + "type": "string" + }, + "PrependSubject": { + "type": "string" + }, + "Priority": { + "type": "number" + }, + "Quarantine": { + "type": "string" + }, + "RecipientAddressContainsWords": { + "type": "string" + }, + "RecipientAddressMatchesPatterns": { + "type": "string" + }, + "RecipientDomainIs": { + "type": "string" + }, + "RedirectMessageTo": { + "type": "string" + }, + "RejectMessageEnhancedStatusCode": { + "type": "string" + }, + "RejectMessageReasonText": { + "type": "string" + }, + "RemoveHeader": { + "type": "string" + }, + "RouteMessageOutboundConnector": { + "type": "string" + }, + "ruleId": { + "type": "string" + }, + "SCLOver": { + "type": "string" + }, + "SenderAddressLocation": { + "$ref": "#/components/schemas/LabelValue" + }, + "SenderDomainIs": { + "type": "string" + }, + "SenderIpRanges": { + "type": "string" + }, + "SentTo": { + "type": "string" + }, + "SentToMemberOf": { + "type": "string" + }, + "SentToScope": { + "type": "string" + }, + "SetAuditSeverity": { + "$ref": "#/components/schemas/LabelValue" + }, + "SetHeaderName": { + "type": "string" + }, + "SetHeaderValue": { + "type": "string" + }, + "SetSCL": { + "type": "string" + }, + "State": { + "type": "string" + }, + "StopRuleProcessing": { + "type": "boolean" + }, + "SubjectContainsWords": { + "type": "string" + }, + "SubjectMatchesPatterns": { + "type": "string" + }, + "SubjectOrBodyContainsWords": { + "type": "string" + }, + "SubjectOrBodyMatchesPatterns": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "WithImportance": { + "type": "string" + }, + "value": { + "type": "string" + }, + "Count": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddEnrollment": { + "post": { + "summary": "AddEnrollment", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AllowFail": { + "type": "boolean" + }, + "AllowReset": { + "type": "boolean" + }, + "blockDevice": { + "type": "boolean" + }, + "EnableLog": { + "type": "boolean" + }, + "ErrorMessage": { + "type": "string" + }, + "InstallWindowsUpdates": { + "type": "boolean" + }, + "OBEEOnly": { + "type": "boolean" + }, + "selectedTenants": { + "type": "string" + }, + "ShowProgress": { + "type": "boolean" + }, + "TimeOutInMinutes": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddEquipmentMailbox": { + "post": { + "summary": "AddEquipmentMailbox", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Equipment.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "domain": { + "$ref": "#/components/schemas/LabelValue" + }, + "tenantID": { + "type": "string" + }, + "username": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddExConnector": { + "post": { + "summary": "AddExConnector", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Connector.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "PowerShellCommand": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "TemplateList": { + "$ref": "#/components/schemas/LabelValue" + }, + "comment": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddExConnectorTemplate": { + "post": { + "summary": "AddExConnectorTemplate", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Connector.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cippconnectortype": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddGroup": { + "post": { + "summary": "AddGroup", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Group.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "membershipRules": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "username": { + "type": "string" + }, + "groupType": { + "type": "string", + "enum": [ + "azurerole", + "generic", + "m365", + "dynamic", + "dynamicdistribution", + "distribution", + "security" + ] + }, + "allowExternal": { + "type": "boolean" + }, + "subscribeMembers": { + "type": "boolean" + }, + "primDomain": { + "$ref": "#/components/schemas/LabelValue" + }, + "owners": { + "type": "array", + "items": { + "type": "string" + } + }, + "members": { + "type": "array", + "items": { + "type": "string" + } + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddGroupTeam": { + "post": { + "summary": "AddGroupTeam", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Group.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GroupId": { + "type": "string" + }, + "TeamSettings": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/AddGroupTemplate": { + "post": { + "summary": "AddGroupTemplate", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Group.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "username": { + "type": "string" + }, + "groupType": { + "type": "string", + "enum": [ + "azurerole", + "generic", + "m365", + "dynamic", + "dynamicDistribution", + "distribution", + "security" + ] + }, + "allowExternal": { + "type": "boolean" + }, + "subscribeMembers": { + "type": "boolean" + }, + "membershipRules": { + "type": "string" + }, + "displayname": { + "type": "string" + }, + "Description": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddGuest": { + "post": { + "summary": "AddGuest", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addrow": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "redirectUri": { + "type": "string" + } + } + }, + "bulkGuests": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "message": { + "type": "string" + }, + "redirectUri": { + "type": "string" + }, + "sendInvite": { + "type": "boolean" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddIntuneReusableSetting": { + "post": { + "summary": "AddIntuneReusableSetting", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "TemplateId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "TemplateId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rawJSON": { + "type": "string" + }, + "TemplateList": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddIntuneReusableSettingTemplate": { + "post": { + "summary": "AddIntuneReusableSettingTemplate", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rawJSON": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "package": { + "type": "string" + }, + "DisplayName": { + "type": "string" + }, + "displayname": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "RawJSON": { + "type": "string" + }, + "json": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddIntuneTemplate": { + "post": { + "summary": "AddIntuneTemplate", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ODataType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "URLName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "ODataType": { + "type": "string" + }, + "policySource": { + "$ref": "#/components/schemas/LabelValue" + }, + "RawJSON": { + "type": "string" + }, + "TemplateType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "URLName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddJITAdminTemplate": { + "post": { + "summary": "AddJITAdminTemplate", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Role.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "defaultDomain": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultDuration": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultExistingUser": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultExpireAction": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultFirstName": { + "type": "string" + }, + "defaultForTenant": { + "type": "boolean" + }, + "defaultLastName": { + "type": "string" + }, + "defaultNotificationActions": { + "type": "array", + "items": { + "type": "string" + } + }, + "defaultRoles": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultUserAction": { + "type": "string" + }, + "defaultUserName": { + "type": "string" + }, + "generateTAPByDefault": { + "type": "boolean" + }, + "reasonTemplate": { + "type": "string" + }, + "templateName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddMSPApp": { + "post": { + "summary": "AddMSPApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AssignTo": { + "type": "string" + }, + "CustomGroup": { + "type": "string" + }, + "DisplayName": { + "type": "string" + }, + "PackageName": { + "type": "string" + }, + "params": { + "type": "string" + }, + "RMMName": { + "$ref": "#/components/schemas/LabelValue", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "selectedTenants": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddNamedLocation": { + "post": { + "summary": "AddNamedLocation", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LabelValue" + } + }, + "includeUnknownCountriesAndRegions": { + "type": "boolean" + }, + "Ips": { + "type": "string" + }, + "policyName": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "Trusted": { + "type": "boolean" + }, + "Type": { + "type": "string", + "enum": [ + "Countries", + "IPLocation" + ] + } + } + } + } + } + } + } + }, + "/api/AddOfficeApp": { + "post": { + "summary": "AddOfficeApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AcceptLicense": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "AssignTo": { + "type": "string" + }, + "CustomGroup": { + "type": "string" + }, + "customXml": { + "type": "string" + }, + "excludedApps": { + "type": "string" + }, + "languages": { + "type": "string" + }, + "RemoveVersions": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "SharedComputerActivation": { + "type": "string" + }, + "updateChannel": { + "type": "string" + }, + "useCustomXml": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddPolicy": { + "post": { + "summary": "AddPolicy", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "AssignTo": { + "type": "string" + }, + "excludeGroup": { + "type": "string" + }, + "AssignmentFilterName": { + "type": "string" + }, + "assignmentFilter": { + "type": "string" + }, + "assignmentFilterType": { + "type": "string" + }, + "customGroup": { + "type": "string" + }, + "RAWJson": { + "type": "string" + }, + "replacemap": { + "type": "string" + }, + "reusableSettings": { + "type": "string" + }, + "TemplateID": { + "type": "string" + }, + "TemplateGUID": { + "type": "string" + }, + "TemplateType": { + "type": "string" + }, + "Count": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddQuarantinePolicy": { + "post": { + "summary": "AddQuarantinePolicy", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Spamfilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AllowSender": { + "type": "boolean" + }, + "BlockSender": { + "type": "boolean" + }, + "Delete": { + "type": "boolean" + }, + "IncludeMessagesFromBlockedSenderAddress": { + "type": "boolean" + }, + "Name": { + "type": "string" + }, + "Preview": { + "type": "boolean" + }, + "QuarantineNotification": { + "type": "boolean" + }, + "ReleaseActionPreference": { + "$ref": "#/components/schemas/LabelValue" + }, + "selectedTenants": { + "type": "string" + }, + "TemplateList": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } + } + } + } + }, + "/api/AddRoomList": { + "post": { + "summary": "AddRoomList", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Room.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "primDomain": { + "$ref": "#/components/schemas/LabelValue" + }, + "tenantFilter": { + "type": "string" + }, + "tenantid": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddRoomMailbox": { + "post": { + "summary": "AddRoomMailbox", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Room.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "domain": { + "$ref": "#/components/schemas/LabelValue" + }, + "ResourceCapacity": { + "type": "string" + }, + "tenantid": { + "type": "string" + }, + "username": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + }, + "DisplayName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddSafeLinksPolicyFromTemplate": { + "post": { + "summary": "AddSafeLinksPolicyFromTemplate", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function deploys SafeLinks policies and rules from templates to selected tenants.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.SafeLinks.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "selectedTenants": { + "type": "string" + }, + "TemplateList": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/api/AddSafeLinksPolicyTemplate": { + "post": { + "summary": "AddSafeLinksPolicyTemplate", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SafeLinks.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AdminDisplayName": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "Name": { + "type": "string" + }, + "PolicyName": { + "type": "string" + }, + "TemplateDescription": { + "type": "string" + }, + "TemplateName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddScheduledItem": { + "post": { + "summary": "AddScheduledItem", + "tags": [ + "CIPP > Scheduler" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Scheduler.ReadWrite", + "x-cipp-warnings": [ + "This endpoint does not use CippFormPage's customDataformatter — CippSchedulerForm calls ApiPostCall directly. The raw form values (with empty fields stripped) are posted as-is." + ], + "parameters": [ + { + "name": "hidden", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "DisallowDuplicateName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "advancedParameters": { + "type": "boolean" + }, + "antiphishing": { + "type": "boolean" + }, + "antispam": { + "type": "boolean" + }, + "backup": { + "$ref": "#/components/schemas/LabelValue" + }, + "ca": { + "type": "boolean" + }, + "CippCustomVariables": { + "type": "boolean" + }, + "CippScriptedAlerts": { + "type": "boolean" + }, + "CippWebhookAlerts": { + "type": "boolean" + }, + "email": { + "type": "boolean" + }, + "groups": { + "type": "boolean" + }, + "intunecompliance": { + "type": "boolean" + }, + "intuneconfig": { + "type": "boolean" + }, + "intuneprotection": { + "type": "boolean" + }, + "overwrite": { + "type": "boolean" + }, + "psa": { + "type": "boolean" + }, + "Trigger": { + "type": "object" + }, + "users": { + "type": "boolean" + }, + "webhook": { + "type": "boolean" + }, + "tenantFilter": { + "type": "object" + }, + "Name": { + "type": "string" + }, + "command": { + "type": "object" + }, + "taskType": { + "type": "object" + }, + "ScheduledTime": { + "type": "integer" + }, + "Recurrence": { + "type": "object" + }, + "parameters": { + "type": "object" + }, + "RawJsonParameters": { + "type": "string" + }, + "postExecution": { + "type": "array", + "items": { + "type": "string" + } + }, + "reference": { + "type": "string" + }, + "RowKey": { + "type": "string" + }, + "RunNow": { + "type": "boolean" + }, + "DesiredStartTime": { + "type": "string" + }, + "DisallowDuplicateName": { + "type": "boolean" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddSharedMailbox": { + "post": { + "summary": "AddSharedMailbox", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantID": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "username": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "addedAliases": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddSite": { + "post": { + "summary": "AddSite", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Sharepoint.Site.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sensitivityLabel": { + "type": "string" + }, + "siteDescription": { + "type": "string" + }, + "siteDesign": { + "$ref": "#/components/schemas/LabelValue", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "siteName": { + "type": "string" + }, + "siteOwner": { + "$ref": "#/components/schemas/LabelValue", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "templateName": { + "$ref": "#/components/schemas/LabelValue", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddSiteBulk": { + "post": { + "summary": "AddSiteBulk", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Sharepoint.Site.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "bulkSites": { + "type": "string" + }, + "sensitivityLabel": { + "type": "string" + }, + "siteDescription": { + "type": "string" + }, + "siteDesign": { + "type": "string" + }, + "siteName": { + "type": "string" + }, + "siteOwner": { + "type": "string" + }, + "templateName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddSpamFilter": { + "post": { + "summary": "AddSpamFilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "PowerShellCommand": { + "type": "string" + }, + "Priority": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "TemplateList": { + "$ref": "#/components/schemas/LabelValue" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddSpamFilterTemplate": { + "post": { + "summary": "AddSpamFilterTemplate", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Spamfilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "PowerShellCommand": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddStandardsDeploy": { + "post": { + "summary": "AddStandardsDeploy", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenant": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddStandardsTemplate": { + "post": { + "summary": "AddStandardsTemplate", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "templateName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddStoreApp": { + "post": { + "summary": "AddStoreApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ApplicationName": { + "type": "string" + }, + "AssignTo": { + "type": "string" + }, + "CustomGroup": { + "type": "string" + }, + "description": { + "type": "string" + }, + "InstallationIntent": { + "type": "string" + }, + "PackageName": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddTeam": { + "post": { + "summary": "AddTeam", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Teams.Group.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "visibility": { + "type": "string" + }, + "tenantid": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddTenant": { + "post": { + "summary": "AddTenant", + "tags": [ + "Tenant > Administration > Tenant" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Config.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "TenantName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "AddressLine1": { + "type": "string" + }, + "AddressLine2": { + "type": "string" + }, + "City": { + "type": "string" + }, + "CompanyName": { + "type": "string" + }, + "Country": { + "type": "string" + }, + "Email": { + "type": "string" + }, + "FirstName": { + "type": "string" + }, + "LastName": { + "type": "string" + }, + "PhoneNumber": { + "type": "string" + }, + "PostalCode": { + "type": "string" + }, + "State": { + "type": "string" + }, + "TenantName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddTenantAllowBlockList": { + "post": { + "summary": "AddTenantAllowBlockList", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entries": { + "type": "string" + }, + "listMethod": { + "$ref": "#/components/schemas/LabelValue" + }, + "listType": { + "$ref": "#/components/schemas/LabelValue" + }, + "NoExpiration": { + "type": "boolean" + }, + "notes": { + "type": "string" + }, + "RemoveAfter": { + "type": "boolean" + }, + "tenantID": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddTestReport": { + "post": { + "summary": "AddTestReport", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Dashboard.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "DevicesTests": { + "type": "string" + }, + "IdentityTests": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddTransportRule": { + "post": { + "summary": "AddTransportRule", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.TransportRule.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "PowerShellCommand": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "TemplateList": { + "$ref": "#/components/schemas/LabelValue" + }, + "PSObject": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddTransportTemplate": { + "post": { + "summary": "AddTransportTemplate", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.TransportRule.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "PowerShellCommand": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/AddUser": { + "post": { + "summary": "AddUser", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Create a new user in a tenant, optionally scheduled", + "x-cipp-role": "Identity.User.ReadWrite", + "x-cipp-scheduled-branch": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "givenName": { + "type": "string" + }, + "surname": { + "type": "string" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "MustChangePass": { + "type": "boolean" + }, + "usageLocation": { + "$ref": "#/components/schemas/LabelValue" + }, + "licenses": { + "type": "array", + "items": { + "type": "string" + } + }, + "jobTitle": { + "type": "string" + }, + "department": { + "type": "string" + }, + "streetAddress": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "country": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "mobilePhone": { + "type": "string" + }, + "businessPhones": { + "type": "array", + "items": { + "type": "string" + } + }, + "copyFrom": { + "$ref": "#/components/schemas/LabelValue" + }, + "userTemplate": { + "$ref": "#/components/schemas/LabelValue" + }, + "Scheduled": { + "$ref": "#/components/schemas/ScheduledTask", + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "Enabled": { + "type": "string" + } + } + }, + "reference": { + "type": "string" + }, + "PostExecution": { + "type": "string" + }, + "mailNickname": { + "type": "string" + }, + "PrimDomain": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "DisplayName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddUserBulk": { + "post": { + "summary": "Bulk-create users in a tenant via CSV import", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "BulkUser": { + "type": "array", + "items": { + "type": "string" + } + }, + "licenses": { + "type": "array", + "items": { + "type": "string" + } + }, + "usageLocation": { + "$ref": "#/components/schemas/LabelValue" + }, + "value": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddUserDefaults": { + "post": { + "summary": "AddUserDefaults", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addedAliases": { + "type": "string", + "description": "Additional SMTP aliases, newline-delimited (server splits on \\n)" + }, + "city": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "copyFrom": { + "$ref": "#/components/schemas/LabelValue" + }, + "country": { + "type": "string" + }, + "defaultForTenant": { + "type": "string" + }, + "department": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "givenName": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "jobTitle": { + "type": "string" + }, + "licenses": { + "type": "array", + "items": { + "type": "string" + }, + "description": "License SKU IDs to assign" + }, + "mobilePhone": { + "type": "string" + }, + "MustChangePass": { + "type": "boolean" + }, + "otherMails": { + "type": "array", + "items": { + "type": "string" + } + }, + "password": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "primDomain": { + "$ref": "#/components/schemas/LabelValue" + }, + "removeLicenses": { + "type": "boolean" + }, + "setManager": { + "$ref": "#/components/schemas/LabelValue" + }, + "setSponsor": { + "$ref": "#/components/schemas/LabelValue" + }, + "state": { + "type": "string" + }, + "streetAddress": { + "type": "string" + }, + "surname": { + "type": "string" + }, + "templateName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "usageLocation": { + "$ref": "#/components/schemas/LabelValue" + }, + "usernameFormat": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/AddWin32ScriptApp": { + "post": { + "summary": "AddWin32ScriptApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AssignTo": { + "type": "string" + }, + "CustomGroup": { + "type": "string" + }, + "description": { + "type": "string" + }, + "detectionFile": { + "type": "string" + }, + "detectionPath": { + "type": "string" + }, + "DisableRestart": { + "type": "string" + }, + "enforceSignatureCheck": { + "type": "string" + }, + "InstallAsSystem": { + "type": "string" + }, + "InstallationIntent": { + "type": "string" + }, + "installScript": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "runAs32Bit": { + "type": "string" + }, + "selectedTenants": { + "type": "string" + }, + "uninstallScript": { + "type": "string" + }, + "applicationName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/BestPracticeAnalyser_List": { + "get": { + "summary": "BestPracticeAnalyser_List", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.BestPracticeAnalyser.Read" + } + }, + "/api/CIPPDBTestsRun": { + "get": { + "summary": "CIPPDBTestsRun", + "tags": [ + "Activity Triggers > Tests" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Tests.Read" + } + }, + "/api/CIPPOffboardingJob": { + "get": { + "summary": "CIPPOffboardingJob", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/api/CIPPStandardsRun": { + "get": { + "summary": "CIPPStandardsRun", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite" + } + }, + "/api/CreateSafeLinksPolicyTemplate": { + "post": { + "summary": "CreateSafeLinksPolicyTemplate", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function creates a new Safe Links policy template from scratch.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.SafeLinks.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TemplateName": { + "type": "string" + }, + "TemplateDescription": { + "type": "string" + }, + "PolicyName": { + "type": "string" + }, + "AdminDisplayName": { + "type": "string" + }, + "EnableSafeLinksForEmail": { + "type": "boolean" + }, + "EnableSafeLinksForTeams": { + "type": "boolean" + }, + "EnableSafeLinksForOffice": { + "type": "boolean" + }, + "TrackClicks": { + "type": "boolean" + }, + "AllowClickThrough": { + "type": "boolean" + }, + "ScanUrls": { + "type": "boolean" + }, + "EnableForInternalSenders": { + "type": "boolean" + }, + "DeliverMessageAfterScan": { + "type": "boolean" + }, + "DisableUrlRewrite": { + "type": "boolean" + }, + "EnableOrganizationBranding": { + "type": "boolean" + }, + "DoNotRewriteUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "CustomNotificationText": { + "type": "string" + }, + "RuleName": { + "type": "string" + }, + "Priority": { + "type": "integer" + }, + "State": { + "type": "boolean" + }, + "Comments": { + "type": "string" + }, + "SentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "SentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "RecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfRecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/DeleteSharepointSite": { + "post": { + "summary": "DeleteSharepointSite", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Sharepoint.Site.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "SiteId": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/DeleteTestReport": { + "post": { + "summary": "DeleteTestReport", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Dashboard.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ReportId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/DeployContactTemplates": { + "post": { + "summary": "DeployContactTemplates", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function deploys contact(s) from template(s) to selected tenants.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.Contact.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "selectedTenants": { + "type": "string" + }, + "TemplateList": { + "type": "object", + "items": { + "type": "string" + }, + "properties": { + "Count": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/api/EditAntiPhishingFilter": { + "post": { + "summary": "EditAntiPhishingFilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "parameters": [ + { + "name": "RuleName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "State", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RuleName": { + "type": "string" + }, + "State": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditAssignmentFilter": { + "post": { + "summary": "EditAssignmentFilter", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "assignmentFilterManagementType": { + "type": "string", + "enum": [ + "devices", + "apps" + ] + }, + "platform": { + "type": "string" + }, + "rule": { + "type": "string" + }, + "filterId": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditCAPolicy": { + "post": { + "summary": "EditCAPolicy", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "newDisplayName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "State", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "newDisplayName": { + "type": "string" + }, + "State": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditContact": { + "post": { + "summary": "EditContact", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Contact.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantID": { + "type": "string" + }, + "ContactID": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "hidefromGAL": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "LastName": { + "type": "string" + }, + "Title": { + "type": "string" + }, + "StreetAddress": { + "type": "string" + }, + "PostalCode": { + "type": "string" + }, + "City": { + "type": "string" + }, + "State": { + "type": "string" + }, + "CountryOrRegion": { + "type": "string" + }, + "Company": { + "type": "string" + }, + "mobilePhone": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "website": { + "type": "string" + }, + "mailTip": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditContactTemplates": { + "post": { + "summary": "EditContactTemplates", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Contact.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ContactTemplateID": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "hidefromGAL": { + "type": "boolean" + }, + "streetAddress": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "country": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "mobilePhone": { + "type": "string" + }, + "businessPhone": { + "type": "string" + }, + "jobTitle": { + "type": "string" + }, + "website": { + "type": "string" + }, + "mailTip": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditEquipmentMailbox": { + "post": { + "summary": "EditEquipmentMailbox", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Equipment.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantID": { + "type": "string" + }, + "equipmentId": { + "type": "string" + }, + "hiddenFromAddressListsEnabled": { + "type": "boolean" + }, + "department": { + "type": "string" + }, + "company": { + "type": "string" + }, + "streetAddress": { + "type": "string" + }, + "city": { + "type": "string" + }, + "stateOrProvince": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "countryOrRegion": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowConflicts": { + "type": "boolean" + }, + "allowRecurringMeetings": { + "type": "boolean" + }, + "bookingWindowInDays": { + "type": "integer" + }, + "maximumDurationInMinutes": { + "type": "integer" + }, + "processExternalMeetingMessages": { + "type": "boolean" + }, + "forwardRequestsToDelegates": { + "type": "boolean" + }, + "scheduleOnlyDuringWorkHours": { + "type": "boolean" + }, + "automateProcessing": { + "type": "string" + }, + "workDays": { + "type": "string" + }, + "workHoursStartTime": { + "type": "string" + }, + "workHoursEndTime": { + "type": "string" + }, + "workingHoursTimeZone": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + }, + "DisplayName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditExConnector": { + "post": { + "summary": "EditExConnector", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Connector.ReadWrite", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "State", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "State": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Type": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditGroup": { + "patch": { + "summary": "EditGroup", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Group.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "groupId": { + "type": "object", + "properties": { + "addedFields.groupName": { + "type": "string" + }, + "value": { + "type": "string" + }, + "addedFields.groupType": { + "type": "string" + } + } + }, + "groupType": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mailNickname": { + "type": "string" + }, + "membershipRules": { + "type": "string" + }, + "securityEnabled": { + "type": "boolean" + }, + "visibility": { + "type": "string" + }, + "allowExternal": { + "type": "boolean" + }, + "sendCopies": { + "type": "boolean" + }, + "hideFromOutlookClients": { + "type": "boolean" + }, + "mail": { + "type": "string" + }, + "AddMember": { + "type": "array", + "items": { + "type": "string" + } + }, + "RemoveMember": { + "type": "array", + "items": { + "type": "string" + } + }, + "AddOwner": { + "type": "array", + "items": { + "type": "string" + } + }, + "RemoveOwner": { + "type": "array", + "items": { + "type": "string" + } + }, + "AddContact": { + "type": "array", + "items": { + "type": "string" + } + }, + "RemoveContact": { + "type": "array", + "items": { + "type": "string" + } + }, + "groupName": { + "type": "string" + }, + "tenantId": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditIntunePolicy": { + "post": { + "summary": "EditIntunePolicy", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "newDisplayName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "policyType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "newDisplayName": { + "type": "string" + }, + "policyType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditIntuneScript": { + "patch": { + "summary": "EditIntuneScript", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "parameters": [ + { + "name": "ScriptId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "IntuneScript": { + "type": "string" + }, + "ScriptId": { + "type": "string" + }, + "ScriptType": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/EditJITAdminTemplate": { + "post": { + "summary": "EditJITAdminTemplate", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Role.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "defaultDomain": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultDuration": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultExistingUser": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultExpireAction": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultFirstName": { + "type": "string" + }, + "defaultForTenant": { + "type": "boolean" + }, + "defaultLastName": { + "type": "string" + }, + "defaultNotificationActions": { + "type": "array", + "items": { + "type": "string" + } + }, + "defaultRoles": { + "$ref": "#/components/schemas/LabelValue" + }, + "defaultUserAction": { + "type": "string" + }, + "defaultUserName": { + "type": "string" + }, + "generateTAPByDefault": { + "type": "boolean" + }, + "GUID": { + "type": "string" + }, + "reasonTemplate": { + "type": "string" + }, + "templateName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditMalwareFilter": { + "post": { + "summary": "EditMalwareFilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "parameters": [ + { + "name": "RuleName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "State", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RuleName": { + "type": "string" + }, + "State": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditPolicy": { + "post": { + "summary": "EditPolicy", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Assignto": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "Displayname": { + "type": "string" + }, + "groupid": { + "type": "string" + }, + "tenantid": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditQuarantinePolicy": { + "post": { + "summary": "EditQuarantinePolicy", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Spamfilter.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "AllowSender": { + "type": "string" + }, + "BlockSender": { + "type": "string" + }, + "Delete": { + "type": "string" + }, + "EndUserSpamNotificationCustomFromAddress": { + "type": "string" + }, + "EndUserSpamNotificationFrequency": { + "type": "string" + }, + "Identity": { + "type": "string" + }, + "IncludeMessagesFromBlockedSenderAddress": { + "type": "string" + }, + "Name": { + "type": "string" + }, + "OrganizationBrandingEnabled": { + "type": "string" + }, + "Preview": { + "type": "string" + }, + "QuarantineNotification": { + "type": "string" + }, + "ReleaseActionPreference": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/EditRoomList": { + "post": { + "summary": "EditRoomList", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Room.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mailNickname": { + "type": "string" + }, + "AddMember": { + "type": "array", + "items": { + "type": "string" + } + }, + "RemoveMember": { + "type": "array", + "items": { + "type": "string" + } + }, + "AddOwner": { + "type": "array", + "items": { + "type": "string" + } + }, + "RemoveOwner": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowExternal": { + "type": "boolean" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditRoomMailbox": { + "post": { + "summary": "EditRoomMailbox", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Room.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantID": { + "type": "string" + }, + "roomId": { + "type": "string" + }, + "hiddenFromAddressListsEnabled": { + "type": "boolean" + }, + "capacity": { + "type": "integer" + }, + "building": { + "type": "string" + }, + "floor": { + "type": "integer" + }, + "floorLabel": { + "type": "string" + }, + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "countryOrRegion": { + "type": "string" + }, + "audioDeviceName": { + "type": "string" + }, + "videoDeviceName": { + "type": "string" + }, + "displayDeviceName": { + "type": "string" + }, + "isWheelChairAccessible": { + "type": "boolean" + }, + "phone": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "AllowConflicts": { + "type": "boolean" + }, + "AllowRecurringMeetings": { + "type": "boolean" + }, + "BookingWindowInDays": { + "type": "integer" + }, + "MaximumDurationInMinutes": { + "type": "integer" + }, + "ProcessExternalMeetingMessages": { + "type": "boolean" + }, + "EnforceCapacity": { + "type": "boolean" + }, + "ForwardRequestsToDelegates": { + "type": "boolean" + }, + "ScheduleOnlyDuringWorkHours": { + "type": "boolean" + }, + "AutomateProcessing": { + "type": "string" + }, + "AddOrganizerToSubject": { + "type": "boolean" + }, + "DeleteSubject": { + "type": "boolean" + }, + "RemoveCanceledMeetings": { + "type": "boolean" + }, + "WorkDays": { + "type": "string" + }, + "WorkHoursStartTime": { + "type": "string" + }, + "WorkHoursEndTime": { + "type": "string" + }, + "WorkingHoursTimeZone": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + }, + "DisplayName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditSafeAttachmentsFilter": { + "post": { + "summary": "EditSafeAttachmentsFilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "parameters": [ + { + "name": "RuleName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "State", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RuleName": { + "type": "string" + }, + "State": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditSafeLinksPolicy": { + "post": { + "summary": "EditSafeLinksPolicy", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function modifies an existing Safe Links policy and its associated rule.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "PolicyName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "RuleName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "PolicyName": { + "type": "string" + }, + "RuleName": { + "type": "string" + }, + "EnableSafeLinksForEmail": { + "type": "boolean" + }, + "EnableSafeLinksForTeams": { + "type": "boolean" + }, + "EnableSafeLinksForOffice": { + "type": "boolean" + }, + "TrackClicks": { + "type": "boolean" + }, + "AllowClickThrough": { + "type": "boolean" + }, + "ScanUrls": { + "type": "boolean" + }, + "EnableForInternalSenders": { + "type": "boolean" + }, + "DeliverMessageAfterScan": { + "type": "boolean" + }, + "DisableUrlRewrite": { + "type": "boolean" + }, + "EnableOrganizationBranding": { + "type": "boolean" + }, + "AdminDisplayName": { + "type": "string" + }, + "CustomNotificationText": { + "type": "string" + }, + "DoNotRewriteUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "Priority": { + "type": "integer" + }, + "Comments": { + "type": "string" + }, + "State": { + "type": "boolean" + }, + "SentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "SentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "RecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfRecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditSafeLinksPolicyTemplate": { + "post": { + "summary": "EditSafeLinksPolicyTemplate", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function updates an existing Safe Links policy template.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.SafeLinks.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "TemplateName": { + "type": "string" + }, + "TemplateDescription": { + "type": "string" + }, + "PolicyName": { + "type": "string" + }, + "AdminDisplayName": { + "type": "string" + }, + "EnableSafeLinksForEmail": { + "type": "boolean" + }, + "EnableSafeLinksForTeams": { + "type": "boolean" + }, + "EnableSafeLinksForOffice": { + "type": "boolean" + }, + "TrackClicks": { + "type": "boolean" + }, + "AllowClickThrough": { + "type": "boolean" + }, + "ScanUrls": { + "type": "boolean" + }, + "EnableForInternalSenders": { + "type": "boolean" + }, + "DeliverMessageAfterScan": { + "type": "boolean" + }, + "DisableUrlRewrite": { + "type": "boolean" + }, + "EnableOrganizationBranding": { + "type": "boolean" + }, + "DoNotRewriteUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "CustomNotificationText": { + "type": "string" + }, + "RuleName": { + "type": "string" + }, + "Priority": { + "type": "integer" + }, + "State": { + "type": "string" + }, + "Comments": { + "type": "string" + }, + "SentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "SentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "RecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfRecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditSpamFilter": { + "post": { + "summary": "EditSpamFilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "parameters": [ + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "state": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditTenant": { + "post": { + "summary": "EditTenant", + "tags": [ + "Tenant > Administration > Tenant" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Config.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantGroups": { + "type": "string" + }, + "tenantAlias": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "GroupId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditTenantOffboardingDefaults": { + "post": { + "summary": "EditTenantOffboardingDefaults", + "tags": [ + "Tenant > Administration > Tenant" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Config.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Alias": { + "type": "string" + }, + "customerId": { + "type": "string" + }, + "defaultDomainName": { + "type": "string" + }, + "Groups": { + "$ref": "#/components/schemas/LabelValue" + }, + "offboardingDefaults": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/EditTransportRule": { + "post": { + "summary": "EditTransportRule", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.TransportRule.ReadWrite", + "parameters": [ + { + "name": "guid", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "guid": { + "type": "string" + }, + "state": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditUser": { + "patch": { + "summary": "EditUser", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userTemplate": { + "$ref": "#/components/schemas/LabelValue" + }, + "givenName": { + "type": "string" + }, + "surname": { + "type": "string" + }, + "Autopassword": { + "type": "boolean" + }, + "password": { + "type": "string", + "format": "password" + }, + "MustChangePass": { + "type": "boolean" + }, + "usageLocation": { + "$ref": "#/components/schemas/LabelValue", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "sherweb": { + "type": "boolean" + }, + "removeLicenses": { + "type": "boolean" + }, + "jobTitle": { + "type": "string" + }, + "streetAddress": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "postalCode": { + "type": "string" + }, + "country": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "department": { + "type": "string" + }, + "mobilePhone": { + "type": "string" + }, + "businessPhones": { + "type": "string" + }, + "otherMails": { + "type": "string" + }, + "AddToGroups": { + "type": "array", + "items": { + "type": "string" + } + }, + "RemoveFromGroups": { + "type": "array", + "items": { + "type": "string" + } + }, + "Scheduled": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "date": { + "type": "string", + "format": "date-time" + } + } + }, + "postExecution": { + "type": "object", + "properties": { + "webhook": { + "type": "boolean" + }, + "email": { + "type": "boolean" + }, + "psa": { + "type": "boolean" + } + } + }, + "reference": { + "type": "string" + }, + "primDomain": { + "$ref": "#/components/schemas/LabelValue", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "licenses": { + "type": "array", + "items": { + "type": "string" + } + }, + "setManager": { + "$ref": "#/components/schemas/LabelValue" + }, + "setSponsor": { + "$ref": "#/components/schemas/LabelValue" + }, + "PostExecution": { + "type": "string" + }, + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "Domain": { + "type": "string" + }, + "mailNickname": { + "type": "string" + }, + "defaultAttributes": { + "type": "string" + }, + "customData": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "sherwebLicense": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "userPrincipalName": { + "type": "string" + }, + "CopyFrom": { + "type": "string" + }, + "DisplayName": { + "type": "string" + }, + "AddedAliases": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/EditUserAliases": { + "post": { + "summary": "EditUserAliases", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AddedAliases": { + "type": "string" + }, + "id": { + "type": "string" + }, + "MakePrimary": { + "type": "string" + }, + "RemovedAliases": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAPIPermissionList": { + "get": { + "summary": "ExecAPIPermissionList", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.Read" + } + }, + "/api/ExecAccessChecks": { + "post": { + "summary": "ExecAccessChecks", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.Read", + "parameters": [ + { + "name": "SkipCache", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TenantId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAddAlert": { + "post": { + "summary": "ExecAddAlert", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Alert.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "logsToInclude": { + "type": "string" + }, + "onePerTenant": { + "type": "string" + }, + "sendEmailNow": { + "type": "string" + }, + "sendPsaNow": { + "type": "string" + }, + "sendWebhookNow": { + "type": "string" + }, + "Severity": { + "type": "string" + }, + "text": { + "type": "string" + }, + "webhook": { + "type": "string" + }, + "writeLog": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAddGDAPRole": { + "post": { + "summary": "ExecAddGDAPRole", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "x-cipp-warnings": [ + "advancedMappings state is held in the component, not in RHF form values. In AddRoleAdvanced mode, only Action and Mappings are sent — all form field values are discarded." + ], + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "gdapTemplate": { + "$ref": "#/components/schemas/LabelValue" + }, + "inviteCount": { + "type": "number" + }, + "Reference": { + "type": "string" + }, + "Action": { + "type": "string" + }, + "gdapRoles": { + "type": "array", + "items": { + "type": "string" + } + }, + "customSuffix": { + "type": "string" + }, + "templateId": { + "type": "string" + }, + "mappings": { + "type": "string" + }, + "replace": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAddMultiTenantApp": { + "post": { + "summary": "ExecAddMultiTenantApp", + "tags": [ + "Tenant > Administration > Application Approval" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Application.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AppId": { + "type": "string" + }, + "configMode": { + "type": "string" + }, + "CopyPermissions": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "selectedTemplate": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAddSPN": { + "get": { + "summary": "ExecAddSPN", + "tags": [ + "Tenant > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.ReadWrite" + } + }, + "/api/ExecAddTenant": { + "post": { + "summary": "ExecAddTenant", + "tags": [ + "CIPP > Setup" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "defaultDomainName": { + "type": "string" + }, + "tenantId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAddTrustedIP": { + "post": { + "summary": "ExecAddTrustedIP", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "IP": { + "type": "string" + }, + "ipAddress": { + "type": "string" + }, + "State": { + "type": "string" + }, + "tenantfilter": { + "type": "string" + } + }, + "required": [ + "tenantfilter" + ] + } + } + } + } + } + }, + "/api/ExecAlertsList": { + "get": { + "summary": "ExecAlertsList", + "tags": [ + "Security" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Security.Alert.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ExecApiClient": { + "delete": { + "summary": "ExecApiClient", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Extension.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "AppName": { + "type": "string" + }, + "CIPPAPI": { + "type": "string" + }, + "ClientId": { + "type": "string" + }, + "Enabled": { + "type": "boolean" + }, + "IpRange": { + "type": "string" + }, + "RemoveAppReg": { + "type": "string" + }, + "Role": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAppApproval": { + "get": { + "summary": "ExecAppApproval", + "tags": [ + "Tenant > Administration > Application Approval" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Application.Read", + "parameters": [ + { + "name": "ApplicationId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecAppApprovalTemplate": { + "delete": { + "summary": "ExecAppApprovalTemplate", + "tags": [ + "Tenant > Administration > Application Approval" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Application.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "TemplateId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "TemplateId": { + "type": "string" + }, + "TemplateName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAppInsightsQuery": { + "get": { + "summary": "ExecAppInsightsQuery", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.Read" + } + }, + "/api/ExecAppPermissionTemplate": { + "delete": { + "summary": "ExecAppPermissionTemplate", + "tags": [ + "Tenant > Administration > Application Approval" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ApplicationTemplates.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "TemplateId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Permissions": { + "type": "string" + }, + "TemplateId": { + "type": "string" + }, + "TemplateName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAppUpload": { + "get": { + "summary": "ExecAppUpload", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite" + } + }, + "/api/ExecApplication": { + "patch": { + "summary": "ExecApplication", + "tags": [ + "Tenant > Administration > Application Approval" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Application.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "AppId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "AppId": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "KeyIds": { + "type": "string" + }, + "Payload": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Type": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAssignAPDevice": { + "post": { + "summary": "ExecAssignAPDevice", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "device": { + "type": "string" + }, + "serialNumber": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAssignApp": { + "post": { + "summary": "ExecAssignApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "parameters": [ + { + "name": "AppType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "AssignTo", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "GroupIds", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "GroupNames", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Intent", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AppType": { + "type": "string" + }, + "AssignmentFilterName": { + "type": "string" + }, + "AssignmentFilterType": { + "type": "string" + }, + "assignmentMode": { + "type": "string" + }, + "AssignTo": { + "type": "string" + }, + "GroupIds": { + "type": "string" + }, + "GroupNames": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "Intent": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAssignPolicy": { + "post": { + "summary": "ExecAssignPolicy", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AssignmentFilterName": { + "type": "string" + }, + "AssignmentFilterType": { + "type": "string" + }, + "assignmentMode": { + "type": "string" + }, + "AssignTo": { + "type": "string" + }, + "excludeGroup": { + "type": "string" + }, + "GroupIds": { + "type": "string" + }, + "GroupNames": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "platformType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Type": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAssignmentFilter": { + "delete": { + "summary": "ExecAssignmentFilter", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAuditLogSearch": { + "post": { + "summary": "ExecAuditLogSearch", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Alert.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "SearchId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "EndTime": { + "type": "string" + }, + "SearchId": { + "type": "string" + }, + "StartTime": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "PSObject": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecAutoExtendGDAP": { + "post": { + "summary": "ExecAutoExtendGDAP", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecAzBobbyTables": { + "post": { + "summary": "Execute a AzBobbyTables function", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function is used to interact with Azure Tables. This is advanced functionality used for external integrations or SuperAdmin functionality.\n .FUNCTIONALITY\n Entrypoint\n .ROLE\n CIPP.SuperAdmin.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)\n $AllowList = @(\n 'Add-AzDataTableEntity'\n 'Add-CIPPAzDataTableEntity'\n 'Update-AzDataTableEntity'\n 'Get-AzDataTableEntity'\n 'Get-CIPPAzDataTableEntity'\n 'Get-AzDataTable'\n 'New-AzDataTable'\n 'Remove-AzDataTableEntity'\n 'Remove-AzDataTable'\n )", + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "FunctionName": { + "type": "string" + }, + "OffloadFunctions": { + "type": "boolean" + }, + "Parameters": { + "type": "string" + }, + "TableName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecBECCheck": { + "get": { + "summary": "ExecBECCheck", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.Read", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "overwrite", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userid", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "userName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecBECRemediate": { + "post": { + "summary": "ExecBECRemediate", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "userid": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecBPA": { + "post": { + "summary": "ExecBPA", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.BestPracticeAnalyser.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantfilter": { + "type": "string" + } + }, + "required": [ + "tenantfilter" + ] + } + } + } + } + } + }, + "/api/ExecBackendURLs": { + "get": { + "summary": "ExecBackendURLs", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.Read" + } + }, + "/api/ExecBackupRetentionConfig": { + "post": { + "summary": "ExecBackupRetentionConfig", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "List", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RetentionDays": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecBitlockerSearch": { + "post": { + "summary": "ExecBitlockerSearch", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Device.Read", + "parameters": [ + { + "name": "deviceId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "keyId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deviceId": { + "type": "string" + }, + "keyId": { + "type": "string" + }, + "limit": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecBrandingSettings": { + "post": { + "summary": "ExecBrandingSettings", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "colour": { + "type": "string" + }, + "logo": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecBreachSearch": { + "post": { + "summary": "ExecBreachSearch", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecBulkLicense": { + "get": { + "summary": "ExecBulkLicense", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite" + } + }, + "/api/ExecCACheck": { + "post": { + "summary": "ExecCACheck", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "authenticationFlow": { + "$ref": "#/components/schemas/LabelValue" + }, + "ClientAppType": { + "$ref": "#/components/schemas/LabelValue" + }, + "Country": { + "$ref": "#/components/schemas/LabelValue" + }, + "DevicePlatform": { + "$ref": "#/components/schemas/LabelValue" + }, + "IncludeApplications": { + "$ref": "#/components/schemas/LabelValue" + }, + "IpAddress": { + "type": "string" + }, + "SignInRiskLevel": { + "$ref": "#/components/schemas/LabelValue" + }, + "tenantFilter": { + "type": "string" + }, + "userID": { + "type": "string" + }, + "UserRiskLevel": { + "$ref": "#/components/schemas/LabelValue" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCAExclusion": { + "post": { + "summary": "ExecCAExclusion", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "Users": { + "type": "array", + "items": { + "type": "string" + } + }, + "PolicyId": { + "type": "string" + }, + "StartDate": { + "type": "integer" + }, + "EndDate": { + "type": "integer" + }, + "vacation": { + "type": "boolean" + }, + "postExecution": { + "type": "array", + "items": { + "type": "string" + } + }, + "reference": { + "type": "string" + }, + "UserID": { + "type": "string" + }, + "Username": { + "type": "string" + }, + "ExclusionType": { + "type": "string" + }, + "excludeLocationAuditAlerts": { + "type": "string" + }, + "value": { + "type": "string" + }, + "addedFields": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCAServiceExclusion": { + "post": { + "summary": "ExecCAServiceExclusion", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCIPPDBCache": { + "get": { + "summary": "ExecCIPPDBCache", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite", + "parameters": [ + { + "name": "Name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Types", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecCPVPermissions": { + "post": { + "summary": "ExecCPVPermissions", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "ResetSP", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCPVRefresh": { + "get": { + "summary": "This endpoint is used to trigger a refresh of CPV for all tenants", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite" + } + }, + "/api/ExecCSPLicense": { + "get": { + "summary": "ExecCSPLicense", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Add": { + "type": "string" + }, + "iagree": { + "type": "boolean" + }, + "Quantity": { + "type": "number" + }, + "Remove": { + "type": "string" + }, + "SKU": { + "$ref": "#/components/schemas/LabelValue" + }, + "SubscriptionIds": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + }, + "post": { + "summary": "ExecCSPLicense", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Add": { + "type": "string" + }, + "iagree": { + "type": "boolean" + }, + "Quantity": { + "type": "number" + }, + "Remove": { + "type": "string" + }, + "SKU": { + "$ref": "#/components/schemas/LabelValue" + }, + "SubscriptionIds": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCippFunction": { + "post": { + "summary": "Execute a CIPPCore function", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function is used to execute a CIPPCore function from an HTTP request. This is advanced functionality used for external integrations or SuperAdmin functionality.\n .FUNCTIONALITY\n Entrypoint\n .ROLE\n CIPP.SuperAdmin.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)\n $BlockList = @(\n 'Get-GraphToken'\n 'Get-GraphTokenFromCert'\n 'Get-ClassicAPIToken'\n )", + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "FunctionName": { + "type": "string" + }, + "Parameters": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecCippLogsSas": { + "post": { + "summary": "Generate a read-only SAS token for the CippLogs table", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Creates a long-lived, read-only SAS URL for the CippLogs Azure Storage table.\n .FUNCTIONALITY\n Entrypoint\n .ROLE\n CIPP.AppSettings.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "Days", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Days": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } + } + } + } + }, + "/api/ExecCippReplacemap": { + "delete": { + "summary": "ExecCippReplacemap", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Config.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "tenantId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "RowKey": { + "type": "string" + }, + "tenantId": { + "type": "string" + }, + "Value": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecCloneTemplate": { + "post": { + "summary": "ExecCloneTemplate", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "Type": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecClrImmId": { + "post": { + "summary": "ExecClrImmId", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCombinedSetup": { + "post": { + "summary": "ExecCombinedSetup", + "tags": [ + "CIPP > Setup" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "applicationId": { + "type": "string" + }, + "applicationSecret": { + "type": "string" + }, + "baselineOption": { + "type": "string" + }, + "email": { + "type": "string" + }, + "RefreshToken": { + "type": "string" + }, + "selectedBaselines": { + "type": "string" + }, + "selectedOption": { + "type": "string" + }, + "tenantid": { + "type": "string" + }, + "webhook": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecCommunityRepo": { + "delete": { + "summary": "Make changes to a community repository", + "tags": [ + "Tools > GitHub" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function makes changes to a community repository in table storage\n .FUNCTIONALITY\n Entrypoint,AnyTenant\n .ROLE\n CIPP.Core.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "CIPP.Core.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Branch": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "FullName": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "includeforks": { + "type": "boolean" + }, + "Message": { + "type": "string" + }, + "orgName": { + "$ref": "#/components/schemas/LabelValue" + }, + "Path": { + "type": "string" + }, + "policySource": { + "$ref": "#/components/schemas/LabelValue" + }, + "Private": { + "type": "boolean" + }, + "repoName": { + "type": "string" + }, + "searchTerm": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } + } + } + } + }, + "/api/ExecConvertMailbox": { + "post": { + "summary": "ExecConvertMailbox", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "MailboxType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCopyForSent": { + "post": { + "summary": "ExecCopyForSent", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "messageCopyState", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "messageCopyState": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCreateAppTemplate": { + "post": { + "summary": "ExecCreateAppTemplate", + "tags": [ + "Tenant > Administration > Application Approval" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Application.ReadWrite", + "x-cipp-dynamic-options": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AppId": { + "type": "string" + }, + "DisplayName": { + "type": "string" + }, + "Overwrite": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + }, + "Type": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCreateDefaultGroups": { + "get": { + "summary": "Create default tenant groups", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function creates a set of default tenant groups that are commonly used\n .FUNCTIONALITY\n Entrypoint,AnyTenant\n .ROLE\n Tenant.Groups.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Tenant.Groups.ReadWrite" + } + }, + "/api/ExecCreateSAMApp": { + "post": { + "summary": "ExecCreateSAMApp", + "tags": [ + "CIPP > Setup" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecCreateTAP": { + "post": { + "summary": "ExecCreateTAP", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "isUsableOnce", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "lifetimeInMinutes", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "startDateTime", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "isUsableOnce": { + "type": "string" + }, + "lifetimeInMinutes": { + "type": "string" + }, + "startDateTime": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecCustomData": { + "post": { + "summary": "ExecCustomData", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "targetObject", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "dataType": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isMultiValued": { + "type": "string" + }, + "Mapping": { + "type": "string" + }, + "name": { + "type": "string" + }, + "schemaExtension": { + "type": "string" + }, + "status": { + "type": "string" + }, + "targetObjects": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecCustomRole": { + "delete": { + "summary": "ExecCustomRole", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "AllowedTenants": { + "type": "string" + }, + "BlockedEndpoints": { + "type": "string" + }, + "BlockedTenants": { + "type": "string" + }, + "EntraGroup": { + "type": "string" + }, + "IpRange": { + "type": "string" + }, + "NewRoleName": { + "type": "string" + }, + "Permissions": { + "type": "string" + }, + "RoleName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecDeleteGDAPRelationship": { + "post": { + "summary": "ExecDeleteGDAPRelationship", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "parameters": [ + { + "name": "GDAPId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GDAPId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecDeleteGDAPRoleMapping": { + "post": { + "summary": "ExecDeleteGDAPRoleMapping", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "parameters": [ + { + "name": "GroupId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GroupId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecDeleteSafeLinksPolicy": { + "post": { + "summary": "ExecDeleteSafeLinksPolicy", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function deletes a Safe Links rule and its associated policy.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)\n $APIName = $Request.Params.CIPPEndpoint\n $Headers = $Request.Headers", + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "parameters": [ + { + "name": "PolicyName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "RuleName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "PolicyName": { + "type": "string" + }, + "RuleName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDeviceAction": { + "post": { + "summary": "ExecDeviceAction", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "input": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDeviceCodeLogon": { + "get": { + "summary": "ExecDeviceCodeLogon", + "tags": [ + "CIPP > Setup" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "clientId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "deviceCode", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "operation", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "tenantId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecDeviceDelete": { + "post": { + "summary": "ExecDeviceDelete", + "tags": [ + "Identity > Administration > Devices" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Device.ReadWrite", + "parameters": [ + { + "name": "action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDevicePasscodeAction": { + "post": { + "summary": "ExecDevicePasscodeAction", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDiagnosticsPresets": { + "delete": { + "summary": "ExecDiagnosticsPresets", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "queryPreset": { + "$ref": "#/components/schemas/LabelValue" + }, + "name": { + "type": "string" + }, + "action": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "query": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecDisableUser": { + "post": { + "summary": "ExecDisableUser", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "Enable", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Enable": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDismissRiskyUser": { + "post": { + "summary": "ExecDismissRiskyUser", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userDisplayName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "userDisplayName": { + "type": "string" + }, + "userId": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDnsConfig": { + "post": { + "summary": "ExecDnsConfig", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Domains.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Domain", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Resolver", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Selector", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Domain": { + "type": "string" + }, + "Resolver": { + "type": "string" + }, + "Selector": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecDomainAction": { + "delete": { + "summary": "ExecDomainAction", + "tags": [ + "Tenant > Administration > Domains" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDomainAnalyser": { + "post": { + "summary": "ExecDomainAnalyser", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.DomainAnalyser.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecDriftClone": { + "post": { + "summary": "ExecDriftClone", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecDurableFunctions": { + "get": { + "summary": "ExecDurableFunctions", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "x-cipp-dynamic-options": true, + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "PartitionKey", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecEditCalendarPermissions": { + "post": { + "summary": "ExecEditCalendarPermissions", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "CanViewPrivateItems", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "FolderName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Permissions", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "RemoveAccess", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userid", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "UserToGetPermissions", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "CanViewPrivateItems": { + "type": "string" + }, + "FolderName": { + "type": "string" + }, + "Permissions": { + "type": "string" + }, + "RemoveAccess": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userid": { + "type": "string" + }, + "UserToGetPermissions": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecEditMailboxPermissions": { + "post": { + "summary": "ExecEditMailboxPermissions", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AddFullAccess": { + "type": "string" + }, + "AddFullAccessNoAutoMap": { + "type": "string" + }, + "AddSendAs": { + "type": "string" + }, + "AddSendOnBehalf": { + "type": "string" + }, + "RemoveFullAccess": { + "type": "string" + }, + "RemoveSendAs": { + "type": "string" + }, + "RemoveSendOnBehalf": { + "type": "string" + }, + "tenantfilter": { + "type": "string" + }, + "userID": { + "type": "string" + } + }, + "required": [ + "tenantfilter" + ] + } + } + } + } + } + }, + "/api/ExecEditTemplate": { + "post": { + "summary": "ExecEditTemplate", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite", + "x-cipp-dynamic-options": true, + "parameters": [ + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parsedRAWJson": { + "type": "string" + }, + "Type": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecEmailForward": { + "post": { + "summary": "ExecEmailForward", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ForwardExternal": { + "type": "string" + }, + "ForwardInternal": { + "type": "string" + }, + "forwardOption": { + "type": "string" + }, + "KeepCopy": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userID": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecEnableArchive": { + "post": { + "summary": "ExecEnableArchive", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "username", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecEnableAutoExpandingArchive": { + "post": { + "summary": "ExecEnableAutoExpandingArchive", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecExchangeRoleRepair": { + "post": { + "summary": "ExecExchangeRoleRepair", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "tenantId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecExcludeLicenses": { + "post": { + "summary": "ExecExcludeLicenses", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "FullReset": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "SKUName": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecExcludeTenant": { + "post": { + "summary": "ExecExcludeTenant", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "AddExclusion", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "List", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ListAll", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "RemoveExclusion", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecExtensionMapping": { + "get": { + "summary": "ExecExtensionMapping", + "tags": [ + "CIPP > Extensions" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Extension.ReadWrite", + "parameters": [ + { + "name": "AddMapping", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "AutoMapping", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "List", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecExtensionNinjaOneQueue": { + "get": { + "summary": "ExecExtensionNinjaOneQueue", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Extension.ReadWrite" + } + }, + "/api/ExecExtensionSync": { + "get": { + "summary": "ExecExtensionSync", + "tags": [ + "CIPP > Extensions" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Extension.ReadWrite", + "parameters": [ + { + "name": "Extension", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "TenantID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecExtensionTest": { + "get": { + "summary": "ExecExtensionTest", + "tags": [ + "CIPP > Extensions" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Extension.Read", + "parameters": [ + { + "name": "extensionName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecExtensionsConfig": { + "post": { + "summary": "ExecExtensionsConfig", + "tags": [ + "CIPP > Extensions" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Extension.ReadWrite", + "x-cipp-dynamic-options": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Hudu": { + "type": "string" + }, + "NinjaOne": { + "type": "string" + }, + "PSObject": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecFeatureFlag": { + "post": { + "summary": "ExecFeatureFlag", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Enabled": { + "type": "boolean" + }, + "Id": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecGDAPAccessAssignment": { + "patch": { + "summary": "ExecGDAPAccessAssignment", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "RoleTemplateId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecGDAPInvite": { + "delete": { + "summary": "ExecGDAPInvite", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "gdapTemplate": { + "$ref": "#/components/schemas/LabelValue" + }, + "inviteCount": { + "type": "number" + }, + "InviteId": { + "type": "string" + }, + "Reference": { + "type": "string" + }, + "roleMappings": { + "$ref": "#/components/schemas/LabelValue" + }, + "roleDefinitionId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecGDAPInviteApproved": { + "get": { + "summary": "ExecGDAPInviteApproved", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite" + } + }, + "/api/ExecGDAPRemoveGArole": { + "post": { + "summary": "ExecGDAPRemoveGArole", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "parameters": [ + { + "name": "GDAPId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GDAPId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecGDAPRoleTemplate": { + "delete": { + "summary": "ExecGDAPRoleTemplate", + "tags": [ + "Tenant > GDAP" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Relationship.ReadWrite", + "x-cipp-dynamic-options": true, + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "TemplateId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "gdapTemplate": { + "$ref": "#/components/schemas/LabelValue" + }, + "GroupId": { + "type": "string" + }, + "inviteCount": { + "type": "number" + }, + "OriginalTemplateId": { + "type": "string" + }, + "Reference": { + "type": "string" + }, + "RoleMappings": { + "$ref": "#/components/schemas/LabelValue" + }, + "TemplateId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecGDAPTrace": { + "get": { + "summary": "Tests the complete GDAP (Granular Delegated Admin Privileges) access path for a user.", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "GDAP Access Path Testing:\n 1. Validates input parameters (TenantFilter and UPN)\n 2. Retrieves customer tenant information\n 3. Gets all active GDAP relationships for the customer tenant\n 4. Locates the UPN in the partner tenant\n 5. Gets user's transitive group memberships (handles nested groups automatically)\n 6. For each GDAP relationship:\n - Retrieves all access assignments (mapped security groups)\n - For each group: checks user membership (direct or nested) and traces the path\n - Maps roles to relationships and groups\n 7. For each of the 15 GDAP roles:\n - Finds all relationships/groups that have this role assigned\n - Checks if user is a member of any group with this role\n - Builds complete access path showing how user gets the role (if they do)\n 8. Returns comprehensive JSON with role-centric view and complete path traces", + "x-cipp-role": "CIPP.AppSettings.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "UPN", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecGeoIPLookup": { + "post": { + "summary": "ExecGeoIPLookup", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "IP", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "IP": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecGetLocalAdminPassword": { + "post": { + "summary": "ExecGetLocalAdminPassword", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Device.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "guid": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecGetRecoveryKey": { + "post": { + "summary": "ExecGetRecoveryKey", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Device.Read", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "RecoveryKeyType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecGitHubAction": { + "post": { + "summary": "Invoke GitHub Action", + "tags": [ + "Tools > GitHub" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Call GitHub API\n .ROLE\n CIPP.Extension.ReadWrite\n .FUNCTIONALITY\n Entrypoint,AnyTenant\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "CIPP.Extension.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "includeforks": { + "type": "boolean" + }, + "orgName": { + "$ref": "#/components/schemas/LabelValue" + }, + "policySource": { + "$ref": "#/components/schemas/LabelValue" + }, + "Private": { + "type": "boolean" + }, + "repoName": { + "type": "string" + }, + "searchTerm": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } + } + } + } + }, + "/api/ExecGraphExplorerPreset": { + "delete": { + "summary": "ExecGraphExplorerPreset", + "tags": [ + "Tenant > Tools" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "AsApp": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "IsShared": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "NoPagination": { + "type": "boolean" + }, + "preset": { + "type": "string" + }, + "reportTemplate": { + "$ref": "#/components/schemas/LabelValue" + }, + "ReverseTenantLookup": { + "type": "boolean" + }, + "ReverseTenantLookupProperty": { + "type": "string" + }, + "version": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } + } + } + } + }, + "/api/ExecGroupsDelete": { + "post": { + "summary": "ExecGroupsDelete", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "displayName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "GroupType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "GroupType": { + "type": "string" + }, + "id": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecGroupsDeliveryManagement": { + "post": { + "summary": "ExecGroupsDeliveryManagement", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Group.ReadWrite", + "parameters": [ + { + "name": "GroupType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "OnlyAllowInternal", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GroupType": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "OnlyAllowInternal": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecGroupsHideFromGAL": { + "post": { + "summary": "ExecGroupsHideFromGAL", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Group.ReadWrite", + "parameters": [ + { + "name": "GroupType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "HideFromGAL", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GroupType": { + "type": "string" + }, + "HideFromGAL": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecHVEUser": { + "post": { + "summary": "ExecHVEUser", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "primarySMTPAddress": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecHideFromGAL": { + "post": { + "summary": "ExecHideFromGAL", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "HideFromGAL", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "HideFromGAL": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecIncidentsList": { + "get": { + "summary": "ExecIncidentsList", + "tags": [ + "Security" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Security.Incident.Read", + "parameters": [ + { + "name": "EndDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "StartDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ExecJITAdmin": { + "post": { + "summary": "ExecJITAdmin", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Just-in-time admin management API endpoint. This function can create users, add roles, remove roles, delete, or disable a user.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Identity.Role.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GroupMemberships": { + "type": "string" + }, + "useGroups": { + "type": "boolean" + }, + "useRoles": { + "type": "boolean" + }, + "AdminRoles": { + "$ref": "#/components/schemas/LabelValue" + }, + "Domain": { + "$ref": "#/components/schemas/LabelValue" + }, + "EndDate": { + "type": "string", + "format": "date-time" + }, + "existingUser": { + "$ref": "#/components/schemas/LabelValue" + }, + "ExpireAction": { + "$ref": "#/components/schemas/LabelValue" + }, + "FirstName": { + "type": "string" + }, + "jitAdminTemplate": { + "$ref": "#/components/schemas/LabelValue" + }, + "LastName": { + "type": "string" + }, + "PostExecution": { + "type": "array", + "items": { + "type": "string" + } + }, + "StartDate": { + "type": "string", + "format": "date-time" + }, + "tenantFilter": { + "type": "string" + }, + "userAction": { + "type": "string", + "enum": [ + "create", + "select" + ] + }, + "UseTAP": { + "type": "boolean" + }, + "Username": { + "type": "string" + }, + "Reason": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecJITAdminSettings": { + "post": { + "summary": "ExecJITAdminSettings", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "MaxDuration": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecLicenseSearch": { + "post": { + "summary": "ExecLicenseSearch", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "skuIds": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecListAppId": { + "get": { + "summary": "ExecListAppId", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite" + }, + "post": { + "summary": "ExecListAppId", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite" + } + }, + "/api/ExecListBackup": { + "get": { + "summary": "ExecListBackup", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Backup.Read", + "parameters": [ + { + "name": "BackupName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "NameOnly", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecLogRetentionConfig": { + "post": { + "summary": "ExecLogRetentionConfig", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "List", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RetentionDays": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecMailTest": { + "get": { + "summary": "ExecMailTest", + "tags": [ + "Email-Exchange > Tools" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecMailboxMobileDevices": { + "get": { + "summary": "ExecMailboxMobileDevices", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "Delete", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "deviceid", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "guid", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Quarantine", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Userid", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecMailboxRestore": { + "post": { + "summary": "ExecMailboxRestore", + "tags": [ + "Email-Exchange > Tools" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Identity", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TenantFilter": { + "type": "string" + }, + "RequestName": { + "type": "string" + }, + "SourceMailbox": { + "type": "string" + }, + "TargetMailbox": { + "type": "string" + }, + "BadItemLimit": { + "type": "integer" + }, + "LargeItemLimit": { + "type": "integer" + }, + "AcceptLargeDataLoss": { + "type": "boolean" + }, + "AssociatedMessagesCopyOption": { + "type": "string" + }, + "ExcludeFolders": { + "type": "array", + "items": { + "type": "string" + } + }, + "IncludeFolders": { + "type": "array", + "items": { + "type": "string" + } + }, + "BatchName": { + "type": "string" + }, + "CompletedRequestAgeLimit": { + "type": "integer" + }, + "ConflictResolutionOption": { + "type": "string" + }, + "SourceRootFolder": { + "type": "string" + }, + "TargetRootFolder": { + "type": "string" + }, + "TargetType": { + "type": "string" + }, + "ExcludeDumpster": { + "type": "boolean" + }, + "SourceIsArchive": { + "type": "boolean" + }, + "TargetIsArchive": { + "type": "boolean" + }, + "Action": { + "type": "string" + }, + "Identity": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecMaintenanceScripts": { + "get": { + "summary": "ExecMaintenanceScripts", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.Read", + "parameters": [ + { + "name": "MakeLink", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ScriptFile", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecManageRetentionPolicies": { + "delete": { + "summary": "ExecManageRetentionPolicies", + "tags": [ + "Email-Exchange > Administration > Mailbox Retention" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.RetentionPolicies.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "CreatePolicies": { + "type": "array", + "items": { + "type": "string" + } + }, + "ModifyPolicies": { + "type": "array", + "items": { + "type": "string" + } + }, + "DeletePolicies": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecManageRetentionTags": { + "delete": { + "summary": "ExecManageRetentionTags", + "tags": [ + "Email-Exchange > Administration > Mailbox Retention" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.RetentionPolicies.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Comment": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "CreateTags": { + "type": "array", + "items": { + "type": "string" + } + }, + "ModifyTags": { + "type": "array", + "items": { + "type": "string" + } + }, + "DeleteTags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecMdoAlertsList": { + "get": { + "summary": "ExecMdoAlertsList", + "tags": [ + "Security" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Security.Alert.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ExecModifyCalPerms": { + "post": { + "summary": "ExecModifyCalPerms", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "permissions": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userID": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecModifyContactPerms": { + "post": { + "summary": "ExecModifyContactPerms", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "permissions": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userID": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecModifyMBPerms": { + "post": { + "summary": "ExecModifyMBPerms", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "mailboxRequests": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userID": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + }, + { + "type": "object", + "additionalProperties": true, + "properties": { + "mailboxRequests": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userID": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + ], + "description": "Accepts a single object or an array of objects. Each object must include the required fields." + } + } + } + } + } + }, + "/api/ExecNamedLocation": { + "post": { + "summary": "ExecNamedLocation", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "parameters": [ + { + "name": "change", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "input", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "namedLocationId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "change": { + "type": "string" + }, + "input": { + "type": "string" + }, + "namedLocationId": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecNewSafeLinksPolicy": { + "post": { + "summary": "ExecNewSafeLinksPolicy", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function creates a new Safe Links policy and an associated rule.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "PolicyName": { + "type": "string" + }, + "RuleName": { + "type": "string" + }, + "EnableSafeLinksForEmail": { + "type": "boolean" + }, + "EnableSafeLinksForTeams": { + "type": "boolean" + }, + "EnableSafeLinksForOffice": { + "type": "boolean" + }, + "TrackClicks": { + "type": "boolean" + }, + "AllowClickThrough": { + "type": "boolean" + }, + "ScanUrls": { + "type": "boolean" + }, + "EnableForInternalSenders": { + "type": "boolean" + }, + "DeliverMessageAfterScan": { + "type": "boolean" + }, + "DisableUrlRewrite": { + "type": "boolean" + }, + "EnableOrganizationBranding": { + "type": "boolean" + }, + "AdminDisplayName": { + "type": "string" + }, + "CustomNotificationText": { + "type": "string" + }, + "DoNotRewriteUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "Priority": { + "type": "integer" + }, + "Comments": { + "type": "string" + }, + "State": { + "type": "boolean" + }, + "SentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "SentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "RecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentTo": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfSentToMemberOf": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExceptIfRecipientDomainIs": { + "type": "array", + "items": { + "type": "string" + } + }, + "Count": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecNotificationConfig": { + "post": { + "summary": "ExecNotificationConfig", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "webhook": { + "type": "string" + }, + "logsToInclude": { + "$ref": "#/components/schemas/LabelValue" + }, + "Severity": { + "$ref": "#/components/schemas/LabelValue" + }, + "onePerTenant": { + "type": "boolean" + }, + "sendtoIntegration": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "/api/ExecOffboardTenant": { + "patch": { + "summary": "ExecOffboardTenant", + "tags": [ + "Tenant > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RemoveCSPGuestUsers": { + "type": "boolean" + }, + "RemoveCSPnotificationContacts": { + "type": "boolean" + }, + "RemoveDomainAnalyserData": { + "type": "boolean" + }, + "RemoveMultitenantCSPApps": { + "type": "boolean" + }, + "TenantFilter": { + "$ref": "#/components/schemas/LabelValue" + }, + "TerminateContract": { + "type": "boolean" + }, + "TerminateGDAP": { + "type": "boolean" + }, + "vendorApplications": { + "$ref": "#/components/schemas/LabelValue" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecOffboardUser": { + "post": { + "summary": "ExecOffboardUser", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Offboard a user with configurable options for mailbox conversion, access delegation, license removal, and more", + "x-cipp-role": "Identity.User.ReadWrite", + "x-cipp-scheduled-branch": true, + "x-cipp-dynamic-options": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "user": { + "type": "array", + "items": { + "type": "string" + } + }, + "ConvertToShared": { + "type": "boolean" + }, + "HideFromGAL": { + "type": "boolean" + }, + "removeCalendarInvites": { + "type": "boolean" + }, + "removePermissions": { + "type": "boolean" + }, + "removeCalendarPermissions": { + "type": "boolean" + }, + "RemoveRules": { + "type": "boolean" + }, + "RemoveMobile": { + "type": "boolean" + }, + "RemoveGroups": { + "type": "boolean" + }, + "RemoveLicenses": { + "type": "boolean" + }, + "RevokeSessions": { + "type": "boolean" + }, + "DisableSignIn": { + "type": "boolean" + }, + "ClearImmutableId": { + "type": "boolean" + }, + "ResetPass": { + "type": "boolean" + }, + "RemoveMFADevices": { + "type": "boolean" + }, + "RemoveTeamsPhoneDID": { + "type": "boolean" + }, + "DeleteUser": { + "type": "boolean" + }, + "AccessNoAutomap": { + "$ref": "#/components/schemas/LabelValue" + }, + "AccessAutomap": { + "$ref": "#/components/schemas/LabelValue" + }, + "OnedriveAccess": { + "$ref": "#/components/schemas/LabelValue" + }, + "forward": { + "type": "string" + }, + "disableForwarding": { + "type": "boolean" + }, + "KeepCopy": { + "type": "boolean" + }, + "OOO": { + "type": "string" + }, + "Scheduled": { + "$ref": "#/components/schemas/ScheduledTask" + }, + "PostExecution": { + "$ref": "#/components/schemas/PostExecution" + }, + "reference": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecOffloadFunctions": { + "post": { + "summary": "ExecOffloadFunctions", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "OffloadFunctions": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "/api/ExecOnboardTenant": { + "post": { + "summary": "ExecOnboardTenant", + "tags": [ + "Tenant > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "addMissingGroups": { + "type": "string" + }, + "autoMapRoles": { + "type": "string" + }, + "Cancel": { + "type": "string" + }, + "gdapRoles": { + "$ref": "#/components/schemas/LabelValue" + }, + "id": { + "$ref": "#/components/schemas/LabelValue" + }, + "ignoreMissingRoles": { + "type": "boolean" + }, + "remapRoles": { + "type": "string" + }, + "Retry": { + "type": "string" + }, + "standardsExcludeAllTenants": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "/api/ExecOneDriveShortCut": { + "post": { + "summary": "ExecOneDriveShortCut", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "siteUrl": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userid": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecOnedriveProvision": { + "post": { + "summary": "ExecOnedriveProvision", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "UserPrincipalName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + }, + "UserPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecPartnerMode": { + "post": { + "summary": "ExecPartnerMode", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "parameters": [ + { + "name": "action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TenantMode": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecPartnerWebhook": { + "post": { + "summary": "ExecPartnerWebhook", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "CorrelationId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "EventType": { + "$ref": "#/components/schemas/LabelValue" + }, + "standardsExcludeAllTenants": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "/api/ExecPasswordConfig": { + "post": { + "summary": "ExecPasswordConfig", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "List", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "appendNumber": { + "type": "string" + }, + "appendSpecialChar": { + "type": "string" + }, + "capitalizeWords": { + "type": "string" + }, + "charCount": { + "type": "string" + }, + "includeDigits": { + "type": "string" + }, + "includeLowercase": { + "type": "string" + }, + "includeSpecialChars": { + "type": "string" + }, + "includeUppercase": { + "type": "string" + }, + "passwordType": { + "type": "string" + }, + "separator": { + "type": "string" + }, + "specialCharSet": { + "type": "string" + }, + "wordCount": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecPasswordNeverExpires": { + "post": { + "summary": "ExecPasswordNeverExpires", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "PasswordPolicy": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecPerUserMFA": { + "post": { + "summary": "ExecPerUserMFA", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "State": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecPermissionRepair": { + "get": { + "summary": "This endpoint will update the CIPP-SAM app permissions.", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Merges new permissions from the SAM manifest into the AppPermissions entry for CIPP-SAM.\n .FUNCTIONALITY\n Entrypoint\n .ROLE\n CIPP.AppSettings.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "CIPP.AppSettings.ReadWrite" + } + }, + "/api/ExecQuarantineManagement": { + "post": { + "summary": "ExecQuarantineManagement", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AllowSender": { + "type": "string" + }, + "Identity": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Type": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecRemoveMailboxRule": { + "post": { + "summary": "ExecRemoveMailboxRule", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "ruleId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ruleName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userPrincipalName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ruleId": { + "type": "string" + }, + "ruleName": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecRemoveRestrictedUser": { + "post": { + "summary": "ExecRemoveRestrictedUser", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Removes a user from the restricted senders list in Exchange Online.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "SenderAddress": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecRemoveTeamsVoicePhoneNumberAssignment": { + "post": { + "summary": "ExecRemoveTeamsVoicePhoneNumberAssignment", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Teams.Voice.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AssignedTo": { + "type": "string" + }, + "PhoneNumber": { + "type": "string" + }, + "PhoneNumberType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecRemoveTenant": { + "post": { + "summary": "ExecRemoveTenant", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TenantID": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecRenameAPDevice": { + "post": { + "summary": "ExecRenameAPDevice", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deviceId": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "serialNumber": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecReprocessUserLicenses": { + "post": { + "summary": "ExecReprocessUserLicenses", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userPrincipalName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecResetMFA": { + "post": { + "summary": "ExecResetMFA", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecResetPass": { + "post": { + "summary": "ExecResetPass", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "displayName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "MustChange", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "MustChange": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecRestoreBackup": { + "post": { + "summary": "ExecRestoreBackup", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "x-cipp-dynamic-options": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "BackupName": { + "type": "string" + }, + "SelectedTypes": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecRestoreDeleted": { + "post": { + "summary": "ExecRestoreDeleted", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecRevokeSessions": { + "post": { + "summary": "ExecRevokeSessions", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Username", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Username": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecRunBackup": { + "get": { + "summary": "ExecRunBackup", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite" + } + }, + "/api/ExecRunTenantGroupRule": { + "post": { + "summary": "Execute tenant group dynamic rules immediately", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function executes dynamic tenant group rules for immediate membership updates\n .FUNCTIONALITY\n Entrypoint,AnyTenant\n .ROLE\n Tenant.Groups.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Tenant.Groups.ReadWrite", + "parameters": [ + { + "name": "groupId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "groupId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecSAMAppPermissions": { + "post": { + "summary": "ExecSAMAppPermissions", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Permissions": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecSAMRoles": { + "post": { + "summary": "ExecSAMRoles", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Roles": { + "type": "string" + }, + "Tenants": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecSAMSetup": { + "post": { + "summary": "ExecSAMSetup", + "tags": [ + "CIPP > Setup" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "parameters": [ + { + "name": "CheckSetupProcess", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "code", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "count", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "CreateSAM", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "error", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "error_description", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "step", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "applicationid": { + "type": "string" + }, + "applicationsecret": { + "type": "string" + }, + "RefreshToken": { + "type": "string" + }, + "setkeys": { + "type": "string" + }, + "tenantid": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecScheduleMailboxVacation": { + "post": { + "summary": "ExecScheduleMailboxVacation", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "autoMap": { + "type": "string" + }, + "calendarPermission": { + "type": "string" + }, + "canViewPrivateItems": { + "type": "string" + }, + "delegates": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "includeCalendar": { + "type": "string" + }, + "mailboxOwners": { + "type": "string" + }, + "permissionTypes": { + "type": "string" + }, + "postExecution": { + "$ref": "#/components/schemas/PostExecution" + }, + "reference": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecScheduleOOOVacation": { + "post": { + "summary": "ExecScheduleOOOVacation", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "endDate": { + "type": "string" + }, + "externalMessage": { + "type": "string" + }, + "internalMessage": { + "type": "string" + }, + "postExecution": { + "$ref": "#/components/schemas/PostExecution" + }, + "reference": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Users": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSchedulerBillingRun": { + "get": { + "summary": "ExecSchedulerBillingRun", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Scheduler.Billing.ReadWrite" + } + }, + "/api/ExecSendOrgMessage": { + "get": { + "summary": "ExecSendOrgMessage", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.ReadWrite", + "parameters": [ + { + "name": "freq", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "URL", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecSendPush": { + "post": { + "summary": "ExecSendPush", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TenantFilter": { + "type": "string" + }, + "UserEmail": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecServicePrincipals": { + "get": { + "summary": "ExecServicePrincipals", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Application.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "AppId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Select", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecSetAPDeviceGroupTag": { + "post": { + "summary": "ExecSetAPDeviceGroupTag", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deviceId": { + "type": "string" + }, + "groupTag": { + "type": "string" + }, + "serialNumber": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetCIPPAutoBackup": { + "post": { + "summary": "ExecSetCIPPAutoBackup", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Backup.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "/api/ExecSetCalendarProcessing": { + "post": { + "summary": "ExecSetCalendarProcessing", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "additionalResponse": { + "type": "string" + }, + "addOrganizerToSubject": { + "type": "string" + }, + "allowConflicts": { + "type": "string" + }, + "allowRecurringMeetings": { + "type": "string" + }, + "automaticallyAccept": { + "type": "string" + }, + "automaticallyProcess": { + "type": "string" + }, + "bookingWindowInDays": { + "type": "string" + }, + "deleteComments": { + "type": "string" + }, + "deleteSubject": { + "type": "string" + }, + "maxConflicts": { + "type": "string" + }, + "maximumDurationInMinutes": { + "type": "string" + }, + "minimumDurationInMinutes": { + "type": "string" + }, + "processExternalMeetingMessages": { + "type": "string" + }, + "removeCanceledMeetings": { + "type": "string" + }, + "removeOldMeetingMessages": { + "type": "string" + }, + "removePrivateProperty": { + "type": "string" + }, + "scheduleOnlyDuringWorkHours": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "UPN": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetCloudManaged": { + "post": { + "summary": "ExecSetCloudManaged", + "tags": [ + "Identity" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Sets the cloud-managed status of a user, group, or contact.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Identity.DirSync.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "isCloudManaged": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetLitigationHold": { + "post": { + "summary": "ExecSetLitigationHold", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "days": { + "type": "string" + }, + "disable": { + "type": "string" + }, + "Identity": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "UPN": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetMailboxEmailSize": { + "post": { + "summary": "ExecSetMailboxEmailSize", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "maxReceiveSize": { + "type": "string" + }, + "maxSendSize": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "UPN": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetMailboxLocale": { + "post": { + "summary": "ExecSetMailboxLocale", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "locale": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetMailboxQuota": { + "post": { + "summary": "ExecSetMailboxQuota", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "IssueWarningQuota": { + "type": "string" + }, + "ProhibitSendQuota": { + "type": "string" + }, + "ProhibitSendReceiveQuota": { + "type": "string" + }, + "quota": { + "type": "string" + }, + "tenantfilter": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "tenantfilter" + ] + } + } + } + } + } + }, + "/api/ExecSetMailboxRetentionPolicies": { + "post": { + "summary": "ExecSetMailboxRetentionPolicies", + "tags": [ + "Email-Exchange > Administration > Mailbox Retention" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Mailboxes": { + "type": "string" + }, + "PolicyName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetMailboxRule": { + "post": { + "summary": "ExecSetMailboxRule", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Disable": { + "type": "string" + }, + "Enable": { + "type": "string" + }, + "ruleId": { + "type": "string" + }, + "ruleName": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetMdoAlert": { + "post": { + "summary": "ExecSetMdoAlert", + "tags": [ + "Security" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Security.Incident.ReadWrite", + "parameters": [ + { + "name": "Assigned", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Classification", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Determination", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Status", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Assigned": { + "type": "string" + }, + "Classification": { + "type": "string" + }, + "Determination": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "Status": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetOoO": { + "post": { + "summary": "ExecSetOoO", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AutoReplyState": { + "type": "string" + }, + "EndTime": { + "type": "string" + }, + "ExternalMessage": { + "type": "string" + }, + "input": { + "type": "string" + }, + "InternalMessage": { + "type": "string" + }, + "StartTime": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userId": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetPackageTag": { + "post": { + "summary": "ExecSetPackageTag", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "Package": { + "type": "string" + }, + "Remove": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecSetRecipientLimits": { + "post": { + "summary": "ExecSetRecipientLimits", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Identity": { + "type": "string" + }, + "recipientLimit": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userid": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetRetentionHold": { + "post": { + "summary": "ExecSetRetentionHold", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "disable": { + "type": "string" + }, + "Identity": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "UPN": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetSecurityAlert": { + "post": { + "summary": "ExecSetSecurityAlert", + "tags": [ + "Security" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Security.Alert.ReadWrite", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Provider", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Status", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Vendor", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "Provider": { + "type": "string" + }, + "Status": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Vendor": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetSecurityIncident": { + "post": { + "summary": "ExecSetSecurityIncident", + "tags": [ + "Security" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Security.Incident.ReadWrite", + "parameters": [ + { + "name": "Assigned", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Classification", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Determination", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Redirected", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Status", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Assigned": { + "type": "string" + }, + "Classification": { + "type": "string" + }, + "Determination": { + "type": "string" + }, + "GUID": { + "type": "string" + }, + "Redirected": { + "type": "string" + }, + "Status": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetSharePointMember": { + "post": { + "summary": "ExecSetSharePointMember", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Sharepoint.Site.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Add": { + "type": "string" + }, + "GroupID": { + "type": "string" + }, + "SharePointType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSetUserPhoto": { + "post": { + "summary": "ExecSetUserPhoto", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "photoData": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userId": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSharePointPerms": { + "post": { + "summary": "ExecSharePointPerms", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Sharepoint.Site.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "onedriveAccessUser": { + "type": "string" + }, + "RemovePermission": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "UPN": { + "type": "string" + }, + "URL": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecStandardConvert": { + "get": { + "summary": "ExecStandardConvert", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", + "x-cipp-dynamic-options": true + } + }, + "/api/ExecStandardsRun": { + "get": { + "summary": "ExecStandardsRun", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", + "parameters": [ + { + "name": "templateId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ExecStartManagedFolderAssistant": { + "post": { + "summary": "ExecStartManagedFolderAssistant", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "parameters": [ + { + "name": "Id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Id": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "UserPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSyncAPDevices": { + "post": { + "summary": "ExecSyncAPDevices", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSyncDEP": { + "post": { + "summary": "ExecSyncDEP", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Syncs devices from Apple Business Manager to Intune\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)\n $APIName = $Request.Params.CIPPEndpoint\n $Headers = $Request.Headers", + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecSyncVPP": { + "post": { + "summary": "ExecSyncVPP", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecTeamsVoicePhoneNumberAssignment": { + "post": { + "summary": "ExecTeamsVoicePhoneNumberAssignment", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Teams.Voice.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "input": { + "type": "string" + }, + "locationOnly": { + "type": "string" + }, + "PhoneNumber": { + "type": "string" + }, + "PhoneNumberType": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecTenantGroup": { + "delete": { + "summary": "Entrypoint for tenant group management", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function is used to manage tenant groups in CIPP\n .FUNCTIONALITY\n Entrypoint,AnyTenant\n .ROLE\n Tenant.Groups.ReadWrite\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Tenant.Groups.ReadWrite", + "x-cipp-warnings": [ + "customDataformatter is present on the CippFormPage for this endpoint. Form field names and/or types may be renamed or restructured before POST. Verify the transformer function in the frontend page file and correct param names/types accordingly." + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "groupDescription": { + "type": "string" + }, + "groupType": { + "type": "string", + "enum": [ + "static", + "dynamic" + ] + }, + "members": { + "type": "array", + "items": { + "type": "string" + } + }, + "dynamicRules": { + "type": "array", + "items": { + "type": "string" + } + }, + "ruleLogic": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecTestRun": { + "post": { + "summary": "ExecTestRun", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Tests.ReadWrite", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecTimeSettings": { + "post": { + "summary": "ExecTimeSettings", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "BusinessHoursStart": { + "$ref": "#/components/schemas/LabelValue" + }, + "Timezone": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } + } + } + } + }, + "/api/ExecTokenExchange": { + "post": { + "summary": "ExecTokenExchange", + "tags": [ + "CIPP > Setup" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantId": { + "type": "string" + }, + "tokenRequest": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecUniversalSearch": { + "get": { + "summary": "ExecUniversalSearch", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "name", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecUniversalSearchV2": { + "get": { + "summary": "ExecUniversalSearchV2", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "searchTerms", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ExecUpdateDriftDeviation": { + "post": { + "summary": "ExecUpdateDriftDeviation", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deviations": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "RemoveDriftCustomization": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecUpdateRefreshToken": { + "post": { + "summary": "ExecUpdateRefreshToken", + "tags": [ + "CIPP > Setup" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "refreshtoken": { + "type": "string" + }, + "tenantId": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecUpdateSecureScore": { + "post": { + "summary": "ExecUpdateSecureScore", + "tags": [ + "Tenant > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ControlName": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "resolutionType": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + }, + "vendorInformation": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } + } + } + } + }, + "/api/ExecUserBookmarks": { + "post": { + "summary": "ExecUserBookmarks", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "currentSettings": { + "type": "string" + }, + "user": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ExecUserSettings": { + "post": { + "summary": "ExecUserSettings", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.ReadWrite", + "x-cipp-dynamic-options": true, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "currentSettings": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } + } + } + } + }, + "/api/ExecWebhookSubscriptions": { + "delete": { + "summary": "ExecWebhookSubscriptions", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Alert.ReadWrite", + "parameters": [ + { + "name": "Action", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "WebhookID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/GetCippAlerts": { + "get": { + "summary": "GetCippAlerts", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "localversion", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/GetVersion": { + "get": { + "summary": "GetVersion", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.Read", + "parameters": [ + { + "name": "LocalVersion", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListAPDevices": { + "get": { + "summary": "ListAPDevices", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAdminPortalLicenses": { + "get": { + "summary": "ListAdminPortalLicenses", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAlertsQueue": { + "get": { + "summary": "ListAlertsQueue", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Alert.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "preset": { + "type": "string" + }, + "logbook": { + "type": "string" + }, + "Actions": { + "type": "string" + }, + "AlertComment": { + "type": "string" + }, + "command": { + "type": "string" + }, + "recurrence": { + "type": "string" + }, + "startDateTime": { + "type": "string" + }, + "postExecution": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ListAllTenantDeviceCompliance": { + "get": { + "summary": "ListAllTenantDeviceCompliance", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.DeviceCompliance.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAntiPhishingFilters": { + "get": { + "summary": "ListAntiPhishingFilters", + "tags": [ + "Email-Exchange > Reports" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListApiTest": { + "get": { + "summary": "ListApiTest", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read" + } + }, + "/api/ListAppApprovalTemplates": { + "get": { + "summary": "ListAppApprovalTemplates", + "tags": [ + "Tenant > Administration > Application Approval" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Application.Read" + } + }, + "/api/ListAppConsentRequests": { + "get": { + "summary": "ListAppConsentRequests", + "tags": [ + "Tenant > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.Read", + "parameters": [ + { + "name": "Filter", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "RequestStatus", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAppProtectionPolicies": { + "get": { + "summary": "ListAppProtectionPolicies", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAppStatus": { + "get": { + "summary": "ListAppStatus", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Device.Read", + "parameters": [ + { + "name": "AppFilter", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListApplicationQueue": { + "get": { + "summary": "ListApplicationQueue", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.Read" + } + }, + "/api/ListApps": { + "get": { + "summary": "ListApps", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAppsRepository": { + "post": { + "summary": "ListAppsRepository", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AcceptLicense": { + "type": "boolean" + }, + "applicationName": { + "type": "string" + }, + "appType": { + "$ref": "#/components/schemas/LabelValue" + }, + "arch": { + "type": "boolean" + }, + "AssignTo": { + "type": "string", + "enum": [ + "On", + "allLicensedUsers", + "AllDevices", + "AllDevicesAndUsers", + "customGroup" + ] + }, + "customArguments": { + "type": "string" + }, + "customGroup": { + "type": "string" + }, + "customRepo": { + "type": "string" + }, + "customXml": { + "type": "string" + }, + "description": { + "type": "string" + }, + "detectionFile": { + "type": "string" + }, + "detectionPath": { + "type": "string" + }, + "DisableRestart": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "enforceSignatureCheck": { + "type": "boolean" + }, + "excludedApps": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LabelValue" + } + }, + "InstallAsSystem": { + "type": "boolean" + }, + "InstallationIntent": { + "type": "boolean" + }, + "installScript": { + "type": "string" + }, + "languages": { + "$ref": "#/components/schemas/LabelValue" + }, + "packagename": { + "type": "string" + }, + "packageSearch": { + "$ref": "#/components/schemas/LabelValue" + }, + "params": { + "type": "object", + "properties": { + "AccountKey": { + "type": "string" + }, + "dattoUrl": { + "type": "string" + }, + "Server": { + "type": "string" + } + } + }, + "publisher": { + "type": "string" + }, + "RemoveVersions": { + "type": "boolean" + }, + "rmmname": { + "$ref": "#/components/schemas/LabelValue" + }, + "runAs32Bit": { + "type": "boolean" + }, + "SharedComputerActivation": { + "type": "boolean" + }, + "uninstallScript": { + "type": "string" + }, + "updateChannel": { + "$ref": "#/components/schemas/LabelValue" + }, + "useCustomXml": { + "type": "boolean" + }, + "Search": { + "type": "string" + }, + "Repository": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ListAssignmentFilterTemplates": { + "get": { + "summary": "ListAssignmentFilterTemplates", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListAssignmentFilters": { + "get": { + "summary": "ListAssignmentFilters", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ + { + "name": "filterId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAuditLogSearches": { + "get": { + "summary": "ListAuditLogSearches", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Alert.Read", + "parameters": [ + { + "name": "Days", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "SearchId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListAuditLogTest": { + "get": { + "summary": "ListAuditLogTest", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Alert.Read", + "parameters": [ + { + "name": "SearchId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAuditLogs": { + "get": { + "summary": "ListAuditLogs", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Alert.Read", + "parameters": [ + { + "name": "EndDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "LogId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "RelativeTime", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "StartDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListAutopilotconfig": { + "get": { + "summary": "ListAutopilotconfig", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListAvailableTests": { + "get": { + "summary": "ListAvailableTests", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Dashboard.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ListAzureADConnectStatus": { + "get": { + "summary": "ListAzureADConnectStatus", + "tags": [ + "Identity > Reports" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.Read", + "parameters": [ + { + "name": "DataToReturn", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListBPA": { + "get": { + "summary": "ListBPA", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.BestPracticeAnalyser.Read", + "parameters": [ + { + "name": "Report", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListBPATemplates": { + "get": { + "summary": "ListBPATemplates", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.BestPracticeAnalyser.Read", + "parameters": [ + { + "name": "RawJson", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListBasicAuth": { + "get": { + "summary": "ListBasicAuth", + "tags": [ + "Identity > Reports" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.AuditLog.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListBreachesAccount": { + "get": { + "summary": "ListBreachesAccount", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "account", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListBreachesTenant": { + "get": { + "summary": "ListBreachesTenant", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListCAtemplates": { + "get": { + "summary": "ListCAtemplates", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.Read", + "parameters": [ + { + "name": "GUID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListCSPLicenses": { + "get": { + "summary": "ListCSPLicenses", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListCSPsku": { + "get": { + "summary": "ListCSPsku", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.Read", + "parameters": [ + { + "name": "currentSkuOnly", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListCalendarPermissions": { + "get": { + "summary": "ListCalendarPermissions", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.Read", + "parameters": [ + { + "name": "ByUser", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "UseReportDB", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "UserID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListCheckExtAlerts": { + "get": { + "summary": "ListCheckExtAlerts", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListCommunityRepos": { + "get": { + "summary": "List community repositories in Table Storage", + "tags": [ + "Tools > GitHub" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function lists community repositories in Table Storage\n .FUNCTIONALITY\n Entrypoint,AnyTenant\n .ROLE\n CIPP.Core.Read\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "WriteAccess", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListCompliancePolicies": { + "get": { + "summary": "ListCompliancePolicies", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListConditionalAccessPolicies": { + "get": { + "summary": "ListConditionalAccessPolicies", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListConditionalAccessPolicyChanges": { + "get": { + "summary": "ListConditionalAccessPolicyChanges", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.Read", + "parameters": [ + { + "name": "displayName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListConnectionFilter": { + "get": { + "summary": "ListConnectionFilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.ConnectionFilter.Read", + "x-cipp-dynamic-options": true, + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListConnectionFilterTemplates": { + "get": { + "summary": "ListConnectionFilterTemplates", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.ConnectionFilter.Read", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListContactPermissions": { + "get": { + "summary": "ListContactPermissions", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "UserID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListContactTemplates": { + "get": { + "summary": "ListContactTemplates", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Contact.Read", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListContacts": { + "get": { + "summary": "ListContacts", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Contact.Read", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListCustomDataMappings": { + "get": { + "summary": "ListCustomDataMappings", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "directoryObject", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sourceType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListCustomRole": { + "get": { + "summary": "ListCustomRole", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read" + } + }, + "/api/ListCustomVariables": { + "get": { + "summary": "ListCustomVariables", + "tags": [ + "CIPP > Settings" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ + { + "name": "excludeGlobalReserved", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "includeSystem", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } }, - "/ListSharepointSettings": { + "/api/ListDBCache": { "get": { - "description": "ListSharepointSettings", - "summary": "ListSharepointSettings", - "tags": ["GET"], + "summary": "ListDBCache", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } + } + ] + } + }, + "/api/ListDefenderState": { + "get": { + "summary": "ListDefenderState", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ { - "required": true, + "name": "DeviceID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "usertoGet", - "in": "query" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListDefenderTVM": { + "get": { + "summary": "ListDefenderTVM", + "tags": [ + "Endpoint > MEM" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListDeletedItems": { + "get": { + "summary": "ListDeletedItems", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Directory.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListDetectedAppDevices": { + "get": { + "summary": "ListDetectedAppDevices", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Device.Read", + "parameters": [ { - "required": true, + "name": "AppID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "user", - "in": "query" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListDetectedApps": { + "get": { + "summary": "ListDetectedApps", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Device.Read", + "parameters": [ + { + "name": "DeviceID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "includeDevices", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListDeviceDetails": { + "get": { + "summary": "ListDeviceDetails", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.Device.Read", + "parameters": [ + { + "name": "DeviceID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "DeviceName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "DeviceSerial", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListDevices": { + "get": { + "summary": "ListDevices", + "tags": [ + "Endpoint > Reports" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Endpoint.Device.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ExecSendOrgMessage": { + "/api/ListDiagnosticsPresets": { "get": { - "description": "ExecSendOrgMessage", - "summary": "ExecSendOrgMessage", - "tags": ["GET"], - "parameters": [ + "summary": "ListDiagnosticsPresets", + "tags": [ + "CIPP > Core" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "URL", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "type", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.SuperAdmin.Read" + } + }, + "/api/ListDirectoryObjects": { + "post": { + "summary": "ListDirectoryObjects", + "tags": [ + "CIPP > Core" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "freq", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "asApp": { + "type": "string" + }, + "ids": { + "type": "string" + }, + "partnerLookup": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } } } } }, - "/AddSpamFilterTemplate": { - "post": { - "description": "AddSpamFilterTemplate", - "summary": "AddSpamFilterTemplate", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "PowerShellCommand", - "in": "body" - }, + "/api/ListDomainAnalyser": { + "get": { + "summary": "ListDomainAnalyser", + "tags": [ + "Tenant > Standards" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "name", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.DomainAnalyser.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListGroupTemplates": { + "/api/ListDomainHealth": { "get": { - "description": "ListGroupTemplates", - "summary": "ListGroupTemplates", - "tags": ["GET"], - "parameters": [ + "summary": "ListDomainHealth", + "tags": [ + "Tenant > Standards" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListAutopilotconfig": { - "get": { - "description": "ListAutopilotconfig", - "summary": "ListAutopilotconfig", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.DomainAnalyser.Read", "parameters": [ { - "required": true, + "name": "Action", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "Domain", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "type", - "in": "query" + } }, { - "required": true, + "name": "ExpectedInclude", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "UserID", - "in": "query" + } + }, + { + "name": "Record", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Selector", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Subdomains", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/ListDomains": { + "get": { + "summary": "ListDomains", + "tags": [ + "Tenant > Administration" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.Administration.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ExecSetSecurityIncident": { + "/api/ListEquipment": { "get": { - "description": "ExecSetSecurityIncident", - "summary": "ExecSetSecurityIncident", - "tags": ["GET"], - "parameters": [ + "summary": "ListEquipment", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Redirected", - "in": "query" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Determination", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Equipment.Read", + "parameters": [ { - "required": true, + "name": "EquipmentId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "Status", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListExConnectorTemplates": { + "get": { + "summary": "ListExConnectorTemplates", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Classification", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "GUID", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Connector.Read", + "parameters": [ { - "required": true, + "name": "id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Assigned", - "in": "query" + } + } + ] + } + }, + "/api/ListExchangeConnectors": { + "get": { + "summary": "ListExchangeConnectors", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Exchange.Connector.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/EditExConnector": { + "/api/ListExcludedLicenses": { "get": { - "description": "EditExConnector", - "summary": "EditExConnector", - "tags": ["GET"], - "parameters": [ + "summary": "ListExcludedLicenses", + "tags": [ + "CIPP > Settings" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "guid", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "state", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.Read" + } + }, + "/api/ListExoRequest": { + "post": { + "summary": "ListExoRequest", + "tags": [ + "Email-Exchange > Tools" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Type", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Anchor": { + "type": "string" + }, + "AsApp": { + "type": "boolean" + }, + "AvailableCmdlets": { + "type": "string" + }, + "Cmdlet": { + "type": "string" + }, + "cmdParams": { + "type": "string" + }, + "Compliance": { + "type": "boolean" + }, + "Select": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + }, + "UseSystemMailbox": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } } } } }, - "/AddUser": { + "/api/ListExtensionCacheData": { "post": { - "description": "AddUser", - "summary": "AddUser", - "tags": ["POST"], - "parameters": [ + "summary": "List Extension Cache Data", + "tags": [ + "CIPP > Core" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "CopyFrom", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListUserPhoto": { - "get": { - "description": "ListUserPhoto", - "summary": "ListUserPhoto", - "tags": ["GET"], + }, + "description": "This function is used to list the extension cache data.\n .FUNCTIONALITY\n Entrypoint\n .ROLE\n CIPP.Core.Read\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)\n $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter\n $DataTypes = $Request.Query.dataTypes -split ',' ?? $Request.Body.dataTypes ?? 'All'", + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "name": "dataTypes", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "dataTypes": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } - }, - "description": "Successful operation" + } } } } }, - "/ListConditionalAccessPolicies": { + "/api/ListExtensionSync": { "get": { - "description": "ListConditionalAccessPolicies", - "summary": "ListConditionalAccessPolicies", - "tags": ["GET"], - "parameters": [ + "summary": "ListExtensionSync", + "tags": [ + "CIPP > Extensions" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "CIPP.Extension.Read" } }, - "/ListBasicAuth": { + "/api/ListExtensionsConfig": { "get": { - "description": "ListBasicAuth", - "summary": "ListBasicAuth", - "tags": ["GET"], - "parameters": [ + "summary": "ListExtensionsConfig", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Extension.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Description": { + "type": "string" + }, + "includeforks": { + "type": "boolean" + }, + "orgName": { + "$ref": "#/components/schemas/LabelValue" + }, + "Private": { + "type": "boolean" + }, + "repoName": { + "type": "string" + }, + "searchTerm": { + "$ref": "#/components/schemas/LabelValue" + } + } + } + } } } } }, - "/RemoveSpamfilterTemplate": { + "/api/ListExternalTenantInfo": { "get": { - "description": "RemoveSpamfilterTemplate", - "summary": "RemoveSpamfilterTemplate", - "tags": ["GET"], - "parameters": [ + "summary": "ListExternalTenantInfo", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecGDAPInvite": { - "post": { - "description": "ExecGDAPInvite", - "summary": "ExecGDAPInvite", - "tags": ["POST"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "name": "tenant", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "gdapRoles", - "in": "body" + } + } + ] + } + }, + "/api/ListFeatureFlags": { + "get": { + "summary": "ListFeatureFlags", + "tags": [ + "CIPP > Core" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "CIPP.Core.Read" } }, - "/ListUserSigninLogs": { + "/api/ListFunctionParameters": { "get": { - "description": "ListUserSigninLogs", - "summary": "ListUserSigninLogs", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListFunctionParameters", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/EditCAPolicy": { - "get": { - "description": "EditCAPolicy", - "summary": "EditCAPolicy", - "tags": ["GET"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "name": "Compliance", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantFilter", - "in": "query" + } }, { - "required": true, + "name": "Function", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "guid", - "in": "query" + } }, { - "required": true, + "name": "Module", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "state", - "in": "query" + } + } + ] + } + }, + "/api/ListFunctionStats": { + "get": { + "summary": "ListFunctionStats", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListDomainHealth": { - "get": { - "description": "ListDomainHealth", - "summary": "ListDomainHealth", - "tags": ["GET"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Action", - "in": "query" - }, - { - "required": true, + "name": "FunctionType", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Record", - "in": "query" + } }, { - "required": true, + "name": "Interval", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "ExpectedInclude", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "Domain", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "Time", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Subdomains", - "in": "query" - }, + } + } + ] + } + }, + "/api/ListGDAPAccessAssignments": { + "get": { + "summary": "ListGDAPAccessAssignments", + "tags": [ + "Tenant > GDAP" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Selector", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecUniversalSearch": { - "get": { - "description": "ExecUniversalSearch", - "summary": "ExecUniversalSearch", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.Relationship.Read", "parameters": [ { - "required": true, + "name": "Id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "name", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/ListMailboxCAS": { + "/api/ListGDAPInvite": { "get": { - "description": "ListMailboxCAS", - "summary": "ListMailboxCAS", - "tags": ["GET"], - "parameters": [ + "summary": "ListGDAPInvite", + "tags": [ + "Tenant > GDAP" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemoveExConnectorTemplate": { - "get": { - "description": "RemoveExConnectorTemplate", - "summary": "RemoveExConnectorTemplate", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.Relationship.Read", "parameters": [ { - "required": true, + "name": "RelationshipId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "id", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/ListUserCounts": { + "/api/ListGDAPRoles": { "get": { - "description": "ListUserCounts", - "summary": "ListUserCounts", - "tags": ["GET"], - "parameters": [ + "summary": "ListGDAPRoles", + "tags": [ + "Tenant > GDAP" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.Relationship.Read" } }, - "/ExecGetLocalAdminPassword": { + "/api/ListGenericTestFunction": { "get": { - "description": "ExecGetLocalAdminPassword", - "summary": "ExecGetLocalAdminPassword", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "guid", - "in": "query" - }, + "summary": "ListGenericTestFunction", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "CIPP.Core.Read" } }, - "/ListOrg": { + "/api/ListGitHubReleaseNotes": { "get": { - "description": "ListOrg", - "summary": "ListOrg", - "tags": ["GET"], - "parameters": [ + "summary": "Retrieves release notes for a GitHub repository.", + "tags": [ + "Tools > GitHub" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecExcludeLicenses": { - "post": { - "description": "ExecExcludeLicenses", - "summary": "ExecExcludeLicenses", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "SKUName", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddExclusion", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "RemoveExclusion", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "GUID", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Returns release metadata for the provided repository and semantic version. Hotfix\n versions (e.g. v8.5.2) map back to the base release tag (v8.5.0).\n .FUNCTIONALITY\n Entrypoint,AnyTenant\n .ROLE\n CIPP.Core.Read\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ { - "required": true, + "name": "Owner", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "body" + } }, { - "required": true, + "name": "Repository", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "List", - "in": "body" + } + } + ] + } + }, + "/api/ListGlobalAddressList": { + "get": { + "summary": "ListGlobalAddressList", + "tags": [ + "Email-Exchange > Reports" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - }, - "get": { - "description": "ExecExcludeLicenses", - "summary": "ExecExcludeLicenses", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "SKUName", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddExclusion", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "RemoveExclusion", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "GUID", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Mailbox.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListGraphBulkRequest": { + "post": { + "summary": "ListGraphBulkRequest", + "tags": [ + "CIPP > Core" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "List", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "asApp": { + "type": "string" + }, + "noPaginateIds": { + "type": "string" + }, + "requests": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } } } } }, - "/ExecDeleteGDAPRelationship": { + "/api/ListGraphExplorerPresets": { "get": { - "description": "ExecDeleteGDAPRelationship", - "summary": "ExecDeleteGDAPRelationship", - "tags": ["GET"], - "parameters": [ + "summary": "ListGraphExplorerPresets", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "GDAPId", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddMSPApp": { - "post": { - "description": "AddMSPApp", - "summary": "AddMSPApp", - "tags": ["POST"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "name": "Endpoint", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "selectedTenants", - "in": "body" - }, + } + } + ] + } + }, + "/api/ListGraphRequest": { + "get": { + "summary": "ListGraphRequest", + "tags": [ + "CIPP > Core" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "AssignTo", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/EditPolicy": { - "post": { - "description": "EditPolicy", - "summary": "EditPolicy", - "tags": ["POST"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "name": "AsApp", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Description", - "in": "body" + } }, { - "required": true, + "name": "CountOnly", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Displayname", - "in": "body" + } }, { - "required": true, + "name": "Endpoint", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantid", - "in": "body" + } }, { - "required": true, + "name": "expand", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Assignto", - "in": "body" + } + }, + { + "name": "graphFilter", + "in": "query", + "required": false, + "schema": { + "type": "string", + "description": "OData $filter expression passed to Graph" + } }, { - "required": true, + "name": "IgnoreErrors", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "groupid", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecDeviceAction": { - "post": { - "description": "ExecDeviceAction", - "summary": "ExecDeviceAction", - "tags": ["POST"], - "parameters": [ + } + }, { - "required": true, + "name": "ListProperties", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "input", - "in": "body" + } }, { - "required": true, + "name": "manualPagination", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "body" + } }, { - "required": true, + "name": "nextLink", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Action", - "in": "body" + } }, { - "required": true, + "name": "NoPagination", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "GUID", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - }, - "get": { - "description": "ExecDeviceAction", - "summary": "ExecDeviceAction", - "tags": ["GET"], - "parameters": [ + } + }, + { + "name": "QueueId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, { - "required": true, + "name": "QueueNameOverride", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ReverseTenantLookup", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ReverseTenantLookupProperty", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "input", - "in": "query" + } }, { - "required": true, + "name": "SkipCache", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "Sort", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Action", - "in": "query" + } }, { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Version", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "GUID", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "gdapRoles": { + "$ref": "#/components/schemas/LabelValue" + }, + "id": { + "$ref": "#/components/schemas/LabelValue" + }, + "ignoreMissingRoles": { + "type": "boolean" + }, + "ooo": { + "type": "object", + "properties": { + "AutoReplyState": { + "$ref": "#/components/schemas/LabelValue" + }, + "EndTime": { + "type": "string", + "format": "date-time" + }, + "ExternalMessage": { + "type": "string" + }, + "InternalMessage": { + "type": "string" + }, + "StartTime": { + "type": "string", + "format": "date-time" + } + } + }, + "recipientLimits": { + "type": "object", + "properties": { + "MaxRecipients": { + "type": "number" + } + } + }, + "remapRoles": { + "type": "string" + }, + "standardsExcludeAllTenants": { + "type": "boolean" + } } } - }, - "description": "Successful operation" + } } } } }, - "/AddWinGetApp": { - "post": { - "description": "AddWinGetApp", - "summary": "AddWinGetApp", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "InstallationIntent", - "in": "body" - }, + "/api/ListGroupSenderAuthentication": { + "get": { + "summary": "ListGroupSenderAuthentication", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "AssignTo", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemovePolicy": { - "get": { - "description": "RemovePolicy", - "summary": "RemovePolicy", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Groups.Read", "parameters": [ { - "required": true, + "name": "groupid", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "URLName", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/AddExConnector": { - "post": { - "description": "AddExConnector", - "summary": "AddExConnector", - "tags": ["POST"], - "parameters": [ - { - "required": true, + "name": "Type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "PowerShellCommand", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/ListTeamsVoice": { + "/api/ListGroupTemplates": { "get": { - "description": "ListTeamsVoice", - "summary": "ListTeamsVoice", - "tags": ["GET"], - "parameters": [ + "summary": "ListGroupTemplates", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListTeamsActivity": { - "get": { - "description": "ListTeamsActivity", - "summary": "ListTeamsActivity", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.Group.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, + "name": "id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Type", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/ListSpamFilterTemplates": { + "/api/ListGroups": { "get": { - "description": "ListSpamFilterTemplates", - "summary": "ListSpamFilterTemplates", - "tags": ["GET"], - "parameters": [ + "summary": "ListGroups", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecCopyForSent": { - "get": { - "description": "ExecCopyForSent", - "summary": "ExecCopyForSent", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.Group.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "MessageCopyForSentAsEnabled", - "in": "query" - }, - { - "required": true, + "name": "expandMembers", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/ListAzureADConnectStatus": { - "get": { - "description": "ListAzureADConnectStatus", - "summary": "ListAzureADConnectStatus", - "tags": ["GET"], - "parameters": [ - { - "required": true, + "name": "groupID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "groupType", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "DataToReturn", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecEnableArchive": { - "get": { - "description": "ExecEnableArchive", - "summary": "ExecEnableArchive", - "tags": ["GET"], - "parameters": [ + } + }, { - "required": true, + "name": "members", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "id", - "in": "query" + } }, { - "required": true, + "name": "owners", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListHaloClients": { + "get": { + "summary": "ListHaloClients", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "CIPP.Extension.Read", + "x-cipp-dynamic-options": true } }, - "/ExecGetRecoveryKey": { + "/api/ListIPWhitelist": { "get": { - "description": "ExecGetRecoveryKey", - "summary": "ExecGetRecoveryKey", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListIPWhitelist", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "GUID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "CIPP.Core.Read" } }, - "/ListSharedMailboxStatistics": { + "/api/ListInactiveAccounts": { "get": { - "description": "ListSharedMailboxStatistics", - "summary": "ListSharedMailboxStatistics", - "tags": ["GET"], - "parameters": [ + "summary": "ListInactiveAccounts", + "tags": [ + "Identity > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListPotentialApps": { - "post": { - "description": "ListPotentialApps", - "summary": "ListPotentialApps", - "tags": ["POST"], + }, + "x-cipp-role": "Tenant.Directory.Read", "parameters": [ { - "required": true, + "name": "InactiveDays", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "type", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "SearchString", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + "$ref": "#/components/parameters/tenantFilter" } - } + ] } }, - "/ExecCPVPermissions": { + "/api/ListIntuneIntents": { "get": { - "description": "ExecCPVPermissions", - "summary": "ExecCPVPermissions", - "tags": ["GET"], - "parameters": [ + "summary": "ListIntuneIntents", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListSharepointQuota": { - "get": { - "description": "ListSharepointQuota", - "summary": "ListSharepointQuota", - "tags": ["GET"], + }, + "x-cipp-role": "Endpoint.MEM.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + "$ref": "#/components/parameters/tenantFilter" } - } + ] } }, - "/ListDefenderTVM": { + "/api/ListIntunePolicy": { "get": { - "description": "ListDefenderTVM", - "summary": "ListDefenderTVM", - "tags": ["GET"], - "parameters": [ + "summary": "ListIntunePolicy", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecEditMailboxPermissions": { - "post": { - "description": "ExecEditMailboxPermissions", - "summary": "ExecEditMailboxPermissions", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddSendAs", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "userID", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddFullAccess", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "AccessAutomap", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddFullAccessNoAutoMap", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "URLName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "RemoveFullAccess", - "in": "body" - }, + } + } + ] + } + }, + "/api/ListIntuneReusableSettingTemplates": { + "get": { + "summary": "ListIntuneReusableSettingTemplates", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "RemoveSendAs", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/AddOfficeApp": { - "post": { - "description": "AddOfficeApp", - "summary": "AddOfficeApp", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AcceptLicense", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "languages", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "SharedComputerActivation", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "updateChannel", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "excludedApps", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "arch", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Assignto", - "in": "body" - }, + } + } + ] + } + }, + "/api/ListIntuneReusableSettings": { + "get": { + "summary": "ListIntuneReusableSettings", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "RemoveVersions", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/EditSpamFilter": { - "get": { - "description": "EditSpamFilter", - "summary": "EditSpamFilter", - "tags": ["GET"], + }, + "x-cipp-role": "Endpoint.MEM.Read", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantfilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "name", - "in": "query" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListIntuneScript": { + "get": { + "summary": "ListIntuneScript", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "state", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Endpoint.MEM.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListSignIns": { + "/api/ListIntuneTemplates": { "get": { - "description": "ListSignIns", - "summary": "ListSignIns", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "failedlogonOnly", - "in": "query" - }, + "summary": "ListIntuneTemplates", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Filter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecDnsConfig": { - "get": { - "description": "ExecDnsConfig", - "summary": "ExecDnsConfig", - "tags": ["GET"], + }, + "x-cipp-role": "Endpoint.MEM.Read", "parameters": [ { - "required": true, + "name": "id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Action", - "in": "query" + } }, { - "required": true, + "name": "mode", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Resolver", - "in": "query" + } }, { - "required": true, + "name": "View", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Domain", - "in": "query" + } + } + ] + } + }, + "/api/ListJITAdmin": { + "get": { + "summary": "ListJITAdmin", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecEmailForward": { - "post": { - "description": "ExecEmailForward", - "summary": "ExecEmailForward", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "disableForwarding", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ForwardExternal", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ForwardInternal", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "description": "List Just-in-time admin users for a tenant or all tenants.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Identity.Role.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "userID", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListJITAdminTemplates": { + "post": { + "summary": "ListJITAdminTemplates", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "KeepCopy", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecBECRemediate": { - "post": { - "description": "ExecBECRemediate", - "summary": "ExecBECRemediate", - "tags": ["POST"], + }, + "x-cipp-role": "Identity.Role.Read", "parameters": [ { - "required": true, + "name": "GUID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantfilter", - "in": "body" + } }, { - "required": true, + "name": "includeAllTenants", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "userid", - "in": "body" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "includeAllTenants": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListPartnerRelationships": { + "/api/ListKnownIPDb": { "get": { - "description": "ListPartnerRelationships", - "summary": "ListPartnerRelationships", - "tags": ["GET"], - "parameters": [ + "summary": "ListKnownIPDb", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListAppsRepository": { - "post": { - "description": "ListAppsRepository", - "summary": "ListAppsRepository", - "tags": ["POST"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Repository", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListLicenses": { + "get": { + "summary": "ListLicenses", + "tags": [ + "Tenant > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Search", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.Directory.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ExecClrImmId": { + "/api/ListLogs": { "get": { - "description": "ExecClrImmId", - "summary": "ExecClrImmId", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListLogs", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddGroupTemplate": { - "post": { - "description": "AddGroupTemplate", - "summary": "AddGroupTemplate", - "tags": ["POST"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "name": "API", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "membershipRule", - "in": "body" + } }, { - "required": true, + "name": "DateFilter", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "displayname", - "in": "body" + } }, { - "required": true, + "name": "EndDate", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "allowExternal", - "in": "body" + } }, { - "required": true, + "name": "Filter", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "groupType", - "in": "body" + } }, { - "required": true, + "name": "ListLogs", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "description", - "in": "body" + } }, { - "required": true, + "name": "logentryid", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "username", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/Standards_IntuneTemplate": { - "post": { - "description": "Standards_IntuneTemplate", - "summary": "Standards_IntuneTemplate", - "tags": ["POST"], - "parameters": [ + } + }, { - "required": true, + "name": "ScheduledTaskId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Displayname", - "in": "body" + } }, { - "required": true, + "name": "Severity", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "RawJSON", - "in": "body" + } }, { - "required": true, + "name": "StandardTemplateId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Description", - "in": "body" + } }, { - "required": true, + "name": "StartDate", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Type", - "in": "body" + } }, { - "required": true, + "name": "Tenant", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Assignto", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } + }, + { + "name": "User", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } - } + ] } }, - "/ListIntuneTemplates": { + "/api/ListMFAUsers": { "get": { - "description": "ListIntuneTemplates", - "summary": "ListIntuneTemplates", - "tags": ["GET"], - "parameters": [ + "summary": "ListMFAUsers", + "tags": [ + "Identity > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecResetPass": { - "get": { - "description": "ExecResetPass", - "summary": "ExecResetPass", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.User.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "UseReportDB", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "MustChange", - "in": "query" - }, + } + } + ] + } + }, + "/api/ListMailQuarantine": { + "get": { + "summary": "ListMailQuarantine", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "displayName", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Exchange.SpamFilter.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ExecAlertsList": { + "/api/ListMailQuarantineMessage": { "get": { - "description": "ExecAlertsList", - "summary": "ExecAlertsList", - "tags": ["GET"], - "parameters": [ + "summary": "ListMailQuarantineMessage", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecQuarantineManagement": { - "get": { - "description": "ExecQuarantineManagement", - "summary": "ExecQuarantineManagement", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.SpamFilter.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, + "name": "Identity", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "ID", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "type", - "in": "query" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListMailboxCAS": { + "get": { + "summary": "ListMailboxCAS", + "tags": [ + "Email-Exchange > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "AllowSender", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Exchange.Mailbox.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ExecRestoreDeleted": { + "/api/ListMailboxForwarding": { "get": { - "description": "ExecRestoreDeleted", - "summary": "ExecRestoreDeleted", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListMailboxForwarding", + "tags": [ + "Email-Exchange > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListUserGroups": { - "get": { - "description": "ListUserGroups", - "summary": "ListUserGroups", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + "name": "UseReportDB", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } - } + ] } }, - "/RemoveStandard": { + "/api/ListMailboxMobileDevices": { "get": { - "description": "RemoveStandard", - "summary": "RemoveStandard", - "tags": ["GET"], - "parameters": [ + "summary": "ListMailboxMobileDevices", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListUserConditionalAccessPolicies": { - "get": { - "description": "ListUserConditionalAccessPolicies", - "summary": "ListUserConditionalAccessPolicies", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, + "name": "Mailbox", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + "$ref": "#/components/parameters/tenantFilter" } - } + ] } }, - "/ListCAtemplates": { + "/api/ListMailboxRestores": { "get": { - "description": "ListCAtemplates", - "summary": "ListCAtemplates", - "tags": ["GET"], - "parameters": [ + "summary": "ListMailboxRestores", + "tags": [ + "Email-Exchange > Tools" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListContacts": { - "get": { - "description": "ListContacts", - "summary": "ListContacts", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, + "name": "Identity", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "IncludeReport", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "ContactID", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } + }, + { + "name": "Statistics", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" } - } + ] } }, - "/ListContactTemplates": { + "/api/ListMailboxRules": { "get": { - "description": "List Contact Templates", - "summary": "List Contact Templates", - "tags": ["GET"], + "summary": "ListMailboxRules", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "object", - "properties": {} - } + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemoveContactTemplates": { - "get": { - "description": "Remove Contact Template", - "summary": "Remove Contact Template", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "UseReportDB", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "id", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/AddContactTemplates": { - "post": { - "description": "Add Contact Template", - "summary": "Add Contact Template", - "tags": ["POST"], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } + "/api/ListMailboxes": { + "get": { + "summary": "ListMailboxes", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ + { + "bearerAuth": [] } - }, + ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListContactPermissions": { - "get": { - "description": "ListContactPermissions - Retrieves contact folder permissions for a specified user", - "summary": "ListContactPermissions", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, + "name": "PSObject", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "UserID", - "in": "query", - "description": "The user ID to retrieve contact permissions for" + } }, { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "UseReportDB", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantFilter", - "in": "query", - "description": "The tenant filter to specify which tenant" + } + } + ] + } + }, + "/api/ListMalwareFilters": { + "get": { + "summary": "ListMalwareFilters", + "tags": [ + "Email-Exchange > Reports" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "Identity": { - "type": "string", - "description": "The identity of the contact folder" - }, - "User": { - "type": "string", - "description": "The user who has permissions" - }, - "AccessRights": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The access rights granted to the user" - }, - "FolderName": { - "type": "string", - "description": "The name of the contact folder" - }, - "MailboxInfo": { - "type": "object", - "description": "Information about the mailbox" - } - } - } + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successfully retrieved contact permissions" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListMessageTrace": { + "post": { + "summary": "ListMessageTrace", + "tags": [ + "Email-Exchange > Tools" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "error": { - "type": "string", - "description": "Error message" - } - } + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Forbidden - insufficient permissions" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecModifyContactPerms": { - "post": { - "description": "ExecModifyContactPerms - Modifies contact folder permissions for a specified user", - "summary": "ExecModifyContactPerms", - "tags": ["POST"], + }, + "x-cipp-role": "Exchange.TransportRule.Read", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", - "required": ["userID", "tenantFilter", "permissions"], "properties": { - "userID": { + "dateFilter": { + "type": "string", + "enum": [ + "relative", + "startEnd" + ] + }, + "days": { + "type": "number" + }, + "endDate": { "type": "string", - "description": "The user ID whose contact permissions will be modified" + "format": "date-time" + }, + "fromIP": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "recipient": { + "type": "array", + "items": { + "type": "string" + } + }, + "sender": { + "type": "array", + "items": { + "type": "string" + } }, - "tenantFilter": { + "startDate": { "type": "string", - "description": "The tenant filter to specify which tenant" + "format": "date-time" }, - "permissions": { + "status": { "type": "array", - "description": "Array of permission objects to apply", "items": { - "type": "object", - "properties": { - "PermissionLevel": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "Permission level (e.g., Owner, PublishingEditor, Editor, PublishingAuthor, Author, NonEditingAuthor, Reviewer, Contributor, AvailabilityOnly, LimitedDetails)" - } - } - }, - "Modification": { - "type": "string", - "description": "Type of modification (Add, Remove)", - "enum": ["Add", "Remove"] - }, - "UserID": { - "type": "array", - "description": "Array of target users to grant/remove permissions", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "User ID or email address" - } - } - } - }, - "CanViewPrivateItems": { - "type": "boolean", - "description": "Whether the user can view private items", - "default": false - }, - "FolderName": { - "type": "string", - "description": "Name of the contact folder", - "default": "Contact" - }, - "SendNotificationToUser": { - "type": "boolean", - "description": "Whether to send notification to the user", - "default": false - } - }, - "required": ["PermissionLevel", "Modification", "UserID"] + "type": "string" } + }, + "tenantFilter": { + "type": "string" + }, + "toIP": { + "type": "string" + }, + "traceDetail": { + "type": "string" + }, + "MessageId": { + "type": "string" } - } + }, + "required": [ + "tenantFilter" + ] } } } - }, + } + } + }, + "/api/ListNamedLocations": { + "get": { + "summary": "ListNamedLocations", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "Results": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of result messages for each permission operation" - } - } + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successfully processed contact permission modifications" + } }, "400": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Results": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Error messages" - } - } - } - } - }, - "description": "Bad Request - Missing required parameters" + "description": "Bad request — missing required field or invalid input" }, - "404": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Results": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Error messages" - } - } - } - } - }, - "description": "Not Found - User ID not found" + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, "500": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Results": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Error messages" - } - } - } - } - }, - "description": "Internal Server Error - Operation failed" + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.ConditionalAccess.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListMailboxRules": { - "get": { - "description": "ListMailboxRules", - "summary": "ListMailboxRules", - "tags": ["GET"], - "parameters": [ + "/api/ListNewUserDefaults": { + "post": { + "summary": "ListNewUserDefaults", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListSites": { - "get": { - "description": "ListSites", - "summary": "ListSites", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.User.Read", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Type", - "in": "query" + } }, { - "required": true, + "name": "includeAllTenants", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserUPN", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "includeAllTenants": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ExecSetOoO": { - "post": { - "description": "ExecSetOoO", - "summary": "ExecSetOoO", - "tags": ["POST"], - "parameters": [ + "/api/ListNotificationConfig": { + "get": { + "summary": "ListNotificationConfig", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "body" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "user", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "input", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.AppSettings.Read", + "x-cipp-dynamic-options": true + } + }, + "/api/ListOAuthApps": { + "get": { + "summary": "ListOAuthApps", + "tags": [ + "Tenant > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Disable", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddExConnectorTemplate": { - "post": { - "description": "AddExConnectorTemplate", - "summary": "AddExConnectorTemplate", - "tags": ["POST"], + }, + "x-cipp-role": "Tenant.Application.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "name", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListOoO": { + "get": { + "summary": "ListOoO", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "cippconnectortype", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecOffboardUser": { - "post": { - "description": "ExecOffboardUser", - "summary": "ExecOffboardUser", - "tags": ["POST"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "KeepCopy", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "userid", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "OnedriveAccess", - "in": "body" - }, + } + } + ] + } + }, + "/api/ListOrg": { + "get": { + "summary": "ListOrg", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "body" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "OOO", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AccessNoAutomap", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "forward", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "user", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListPartnerRelationships": { + "get": { + "summary": "ListPartnerRelationships", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.Relationship.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListTenantDetails": { + "/api/ListPendingWebhooks": { "get": { - "description": "ListTenantDetails", - "summary": "ListTenantDetails", - "tags": ["GET"], - "parameters": [ + "summary": "ListPendingWebhooks", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "CIPP.Alert.Read" } }, - "/ExecConverttoSharedMailbox": { + "/api/ListPerUserMFA": { "get": { - "description": "ExecConverttoSharedMailbox", - "summary": "ExecConverttoSharedMailbox", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ConvertToUser", - "in": "query" - }, + "summary": "ListPerUserMFA", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecGraphRequest": { - "get": { - "description": "ExecGraphRequest", - "summary": "ExecGraphRequest", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.User.Read", "parameters": [ { - "required": true, + "name": "allUsers", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "Endpoint", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "userId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "DisablePagination", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/ListDefenderState": { - "get": { - "description": "ListDefenderState", - "summary": "ListDefenderState", - "tags": ["GET"], - "parameters": [ + "/api/ListPotentialApps": { + "post": { + "summary": "ListPotentialApps", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AcceptLicense": { + "type": "boolean" + }, + "applicationName": { + "type": "string" + }, + "appType": { + "$ref": "#/components/schemas/LabelValue" + }, + "arch": { + "type": "boolean" + }, + "AssignTo": { + "type": "string", + "enum": [ + "On", + "allLicensedUsers", + "AllDevices", + "AllDevicesAndUsers", + "customGroup" + ] + }, + "customArguments": { + "type": "string" + }, + "customGroup": { + "type": "string" + }, + "customRepo": { + "type": "string" + }, + "customXml": { + "type": "string" + }, + "description": { + "type": "string" + }, + "detectionFile": { + "type": "string" + }, + "detectionPath": { + "type": "string" + }, + "DisableRestart": { + "type": "boolean" + }, + "displayName": { + "type": "string" + }, + "enforceSignatureCheck": { + "type": "boolean" + }, + "excludedApps": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LabelValue" + } + }, + "InstallAsSystem": { + "type": "boolean" + }, + "InstallationIntent": { + "type": "boolean" + }, + "installScript": { + "type": "string" + }, + "languages": { + "$ref": "#/components/schemas/LabelValue" + }, + "packagename": { + "type": "string" + }, + "packageSearch": { + "$ref": "#/components/schemas/LabelValue" + }, + "params": { + "type": "object", + "properties": { + "AccountKey": { + "type": "string" + }, + "dattoUrl": { + "type": "string" + }, + "Server": { + "type": "string" + } + } + }, + "publisher": { + "type": "string" + }, + "RemoveVersions": { + "type": "boolean" + }, + "rmmname": { + "$ref": "#/components/schemas/LabelValue" + }, + "runAs32Bit": { + "type": "boolean" + }, + "searchQuery": { + "type": "string" + }, + "SearchString": { + "type": "string" + }, + "SharedComputerActivation": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "uninstallScript": { + "type": "string" + }, + "updateChannel": { + "$ref": "#/components/schemas/LabelValue" + }, + "useCustomXml": { + "type": "boolean" + } + } + } + } } } } }, - "/ListMailQuarantine": { - "get": { - "description": "ListMailQuarantine", - "summary": "ListMailQuarantine", - "tags": ["GET"], - "parameters": [ + "/api/ListQuarantinePolicy": { + "post": { + "summary": "ListQuarantinePolicy", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListIntunePolicy": { - "get": { - "description": "ListIntunePolicy", - "summary": "ListIntunePolicy", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.SpamFilter.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "Type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "URLName", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] } - }, - "description": "Successful operation" + } } } } }, - "/ExecRevokeSessions": { + "/api/ListRestrictedUsers": { "get": { - "description": "ExecRevokeSessions", - "summary": "ExecRevokeSessions", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListRestrictedUsers", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "description": "Lists users from the restricted senders list in Exchange Online.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)\n # Interact with query parameters or the body of the request.\n $TenantFilter = $Request.Query.tenantFilter", + "x-cipp-role": "Exchange.Mailbox.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListmailboxPermissions": { + "/api/ListRoles": { "get": { - "description": "ListmailboxPermissions", - "summary": "ListmailboxPermissions", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListRoles", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Identity.Role.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ExecExtensionsConfig": { - "post": { - "description": "ExecExtensionsConfig", - "summary": "ExecExtensionsConfig", - "tags": ["POST"], - "parameters": [ + "/api/ListRoomLists": { + "get": { + "summary": "ListRoomLists", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "CIPPAPI", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListDevices": { - "get": { - "description": "ListDevices", - "summary": "ListDevices", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Room.Read", "parameters": [ { - "required": true, + "name": "groupID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } + }, + { + "name": "members", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "owners", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListRooms": { + "get": { + "summary": "ListRooms", + "tags": [ + "Email-Exchange > Resources" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemoveCATemplate": { - "get": { - "description": "RemoveCATemplate", - "summary": "RemoveCATemplate", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Room.Read", "parameters": [ { - "required": true, + "name": "roomId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "id", - "in": "query" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListSafeAttachmentsFilters": { + "get": { + "summary": "ListSafeAttachmentsFilters", + "tags": [ + "Email-Exchange > Reports" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Exchange.SpamFilter.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/RemoveQueuedApp": { + "/api/ListSafeLinksPolicy": { "get": { - "description": "RemoveQueuedApp", - "summary": "RemoveQueuedApp", - "tags": ["GET"], - "parameters": [ + "summary": "ListSafeLinksPolicy", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "description": "This function is used to list the Safe Links policies in the tenant, including unmatched rules and policies.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)\n $APIName = $Request.Params.CIPPEndpoint\n $Headers = $Request.Headers", + "x-cipp-role": "Security.SafeLinksPolicy.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/DomainAnalyser_List": { - "get": { - "description": "DomainAnalyser_List", - "summary": "DomainAnalyser_List", - "tags": ["GET"], - "parameters": [ + "/api/ListSafeLinksPolicyDetails": { + "post": { + "summary": "ListSafeLinksPolicyDetails", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/AddNamedLocation": { - "post": { - "description": "AddNamedLocation", - "summary": "AddNamedLocation", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Ips", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "includeUnknownCountriesAndRegions", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Countries", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Trusted", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function retrieves details for a specific Safe Links policy and rule.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.SpamFilter.Read", + "parameters": [ { - "required": true, + "name": "PolicyName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "policyName", - "in": "body" + } }, { - "required": true, + "name": "RuleName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Type", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "selectedTenants", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "PolicyName": { + "type": "string" + }, + "RuleName": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/ListSafeLinksPolicyTemplateDetails": { + "post": { + "summary": "ListSafeLinksPolicyTemplateDetails", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "This function retrieves details for a specific Safe Links policy template.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "Exchange.SafeLinks.Read", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListTransportRulesTemplates": { + "/api/ListSafeLinksPolicyTemplates": { "get": { - "description": "ListTransportRulesTemplates", - "summary": "ListTransportRulesTemplates", - "tags": ["GET"], - "parameters": [ + "summary": "ListSafeLinksPolicyTemplates", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecEditCalendarPermissions": { - "get": { - "description": "ExecEditCalendarPermissions", - "summary": "ExecEditCalendarPermissions", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "permissions", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserToGetPermissions", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SafeLinks.Read", + "parameters": [ { - "required": true, + "name": "id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "removeaccess", - "in": "query" - }, + } + } + ] + } + }, + "/api/ListScheduledItemDetails": { + "post": { + "summary": "ListScheduledItemDetails", + "tags": [ + "CIPP > Scheduler" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "folderName", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecDisableUser": { - "get": { - "description": "ExecDisableUser", - "summary": "ExecDisableUser", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Scheduler.Read", + "parameters": [ { - "required": true, + "name": "RowKey", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Enable", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RowKey": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/AddTransportRule": { + "/api/ListScheduledItems": { "post": { - "description": "AddTransportRule", - "summary": "AddTransportRule", - "tags": ["POST"], - "parameters": [ + "summary": "ListScheduledItems", + "tags": [ + "CIPP > Scheduler" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "PowerShellCommand", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListTenants": { - "get": { - "description": "ListTenants", - "summary": "ListTenants", - "tags": ["GET"], + }, + "x-cipp-role": "CIPP.Scheduler.Read", "parameters": [ { - "required": true, + "name": "Id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "Name", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "ClearCache", - "in": "query" + } }, { - "required": true, + "name": "SearchTitle", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantsOnly", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "AllTenantSelector", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecResetMFA": { - "get": { - "description": "ExecResetMFA", - "summary": "ExecResetMFA", - "tags": ["GET"], - "parameters": [ - { - "required": true, + "name": "ShowHidden", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/ListIntuneIntents": { - "get": { - "description": "ListIntuneIntents", - "summary": "ListIntuneIntents", - "tags": ["GET"], - "parameters": [ + "$ref": "#/components/parameters/tenantFilter" + }, { - "required": true, + "name": "Type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Id": { + "type": "string" + }, + "Name": { + "type": "string" + }, + "SearchTitle": { + "type": "string" + }, + "ShowHidden": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Type": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } - }, - "description": "Successful operation" + } } } } }, - "/ListStandards": { + "/api/ListServiceHealth": { "get": { - "description": "ListStandards", - "summary": "ListStandards", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - } + "summary": "ListServiceHealth", + "tags": [ + "Tenant > Reports" ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/ListDeletedItems": { - "get": { - "description": "ListDeletedItems", - "summary": "ListDeletedItems", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecGroupsHideFromGAL": { - "get": { - "description": "ExecGroupsHideFromGAL", - "summary": "ExecGroupsHideFromGAL", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.Administration.Read", "parameters": [ { - "required": true, + "name": "defaultDomainName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "displayName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "groupType", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListSharedMailboxAccountEnabled": { + "get": { + "summary": "ListSharedMailboxAccountEnabled", + "tags": [ + "Email-Exchange > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "HidefromGAL", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Exchange.Mailbox.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ExecSendPush": { + "/api/ListSharedMailboxStatistics": { "get": { - "description": "ExecSendPush", - "summary": "ExecSendPush", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListSharedMailboxStatistics", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserEmail", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecExtensionMapping": { - "post": { - "description": "ExecExtensionMapping", - "summary": "ExecExtensionMapping", - "tags": ["POST"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "mappings", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "List", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListSharepointAdminUrl": { + "get": { + "summary": "ListSharepointAdminUrl", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddMapping", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - }, - "get": { - "description": "ExecExtensionMapping", - "summary": "ExecExtensionMapping", - "tags": ["GET"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "mappings", - "in": "query" - }, - { - "required": true, + "name": "ReturnUrl", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "List", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddMapping", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + "$ref": "#/components/parameters/tenantFilter" } - } + ] } }, - "/ListAppStatus": { + "/api/ListSharepointQuota": { "get": { - "description": "ListAppStatus", - "summary": "ListAppStatus", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListSharepointQuota", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "AppFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Sharepoint.Admin.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/GetVersion": { + "/api/ListSharepointSettings": { "get": { - "description": "GetVersion", - "summary": "GetVersion", - "tags": ["GET"], - "parameters": [ + "summary": "ListSharepointSettings", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "localversion", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecMailboxMobileDevices": { - "get": { - "description": "ExecMailboxMobileDevices", - "summary": "ExecMailboxMobileDevices", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Userid", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Quarantine", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Delete", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "deviceid", - "in": "query" + "500": { + "description": "Internal server error" } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + }, + "x-cipp-role": "Sharepoint.Admin.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" } - } + ] } }, - "/RemoveApp": { + "/api/ListSignIns": { "get": { - "description": "RemoveApp", - "summary": "RemoveApp", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListSignIns", + "tags": [ + "Identity > Reports" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecPasswordConfig": { - "post": { - "description": "ExecPasswordConfig", - "summary": "ExecPasswordConfig", - "tags": ["POST"], + }, + "x-cipp-role": "Identity.AuditLog.Read", "parameters": [ { - "required": true, + "name": "Days", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "failedLogonsOnly", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "List", - "in": "body" + } }, { - "required": true, + "name": "FailureThreshold", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "passwordType", - "in": "body" + } + }, + { + "name": "Filter", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListSiteMembers": { + "get": { + "summary": "ListSiteMembers", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - }, - "get": { - "description": "ExecPasswordConfig", - "summary": "ExecPasswordConfig", - "tags": ["GET"], + }, + "x-cipp-role": "Sharepoint.Site.Read", "parameters": [ { - "required": true, + "name": "SiteId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "List", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "passwordType", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListSites": { + "get": { + "summary": "ListSites", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecHideFromGAL": { - "get": { - "description": "ExecHideFromGAL", - "summary": "ExecHideFromGAL", - "tags": ["GET"], + }, + "x-cipp-role": "Sharepoint.Site.Read", "parameters": [ { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "Type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "HideFromGal", - "in": "query" + } }, { - "required": true, + "name": "URLOnly", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantfilter", - "in": "query" + } }, { - "required": true, + "name": "UserUPN", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "ID", - "in": "query" + } + } + ] + } + }, + "/api/ListSpamFilterTemplates": { + "get": { + "summary": "ListSpamFilterTemplates", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/EditUser": { - "post": { - "description": "EditUser", - "summary": "EditUser", - "tags": ["POST"], + }, + "x-cipp-role": "Exchange.SpamFilter.Read", "parameters": [ { - "required": true, + "name": "id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "CopyFrom", - "in": "body" + } + } + ] + } + }, + "/api/ListSpamfilter": { + "get": { + "summary": "ListSpamfilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Exchange.SpamFilter.Read", + "x-cipp-dynamic-options": true, + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListOAuthApps": { + "/api/ListStandards": { "get": { - "description": "ListOAuthApps", - "summary": "ListOAuthApps", - "tags": ["GET"], - "parameters": [ + "summary": "ListStandards", + "tags": [ + "Tenant > Standards" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListDeviceDetails": { - "get": { - "description": "ListDeviceDetails", - "summary": "ListDeviceDetails", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.Standards.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, + "name": "ShowConsolidated", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "DeviceSerial", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "DeviceID", - "in": "query" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListStandardsCompare": { + "get": { + "summary": "ListStandardsCompare", + "tags": [ + "Tenant > Standards" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "DeviceName", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ListLogs": { - "get": { - "description": "ListLogs", - "summary": "ListLogs", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ListLogs", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Severity", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "User", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.BestPracticeAnalyser.Read", + "parameters": [ { - "required": true, + "name": "templateId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "DateFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "Filter", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListTeams": { + "get": { + "summary": "ListTeams", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Teams.Group.Read", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } - } + ] } }, - "/ListAllTenantDeviceCompliance": { + "/api/ListTeamsActivity": { "get": { - "description": "ListAllTenantDeviceCompliance", - "summary": "ListAllTenantDeviceCompliance", - "tags": ["GET"], - "parameters": [ + "summary": "ListTeamsActivity", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListUsers": { - "get": { - "description": "ListUsers", - "summary": "ListUsers", - "tags": ["GET"], + }, + "x-cipp-role": "Teams.Activity.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "IncludeLogonDetails", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "Type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "graphFilter", - "in": "query" - }, + } + } + ] + } + }, + "/api/ListTeamsLisLocation": { + "get": { + "summary": "ListTeamsLisLocation", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ListMessageTrace": { - "get": { - "description": "ListMessageTrace", - "summary": "ListMessageTrace", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Tracedetail", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "sender", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "days", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Teams.Voice.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListTeamsVoice": { + "get": { + "summary": "ListTeamsVoice", + "tags": [ + "Teams-Sharepoint" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "recipient", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Teams.Voice.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListPhishPolicies": { + "/api/ListTenantAlignment": { "get": { - "description": "ListPhishPolicies", - "summary": "ListPhishPolicies", - "tags": ["GET"], - "parameters": [ + "summary": "ListTenantAlignment", + "tags": [ + "Tenant > Standards" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.Standards.Read" } }, - "/RemoveSpamfilter": { + "/api/ListTenantAllowBlockList": { "get": { - "description": "RemoveSpamfilter", - "summary": "RemoveSpamfilter", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" - }, + "summary": "ListTenantAllowBlockList", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "name", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecNotificationConfig": { - "post": { - "description": "ExecNotificationConfig", - "summary": "ExecNotificationConfig", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Webhook", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Email", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "logsToInclude", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Severity", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.SpamFilter.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "onePerTenant", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListTenantDetails": { + "get": { + "summary": "ListTenantDetails", + "tags": [ + "Tenant > Administration > Tenant" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "sendtoIntegration", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecSAMSetup": { - "post": { - "description": "ExecSAMSetup", - "summary": "ExecSAMSetup", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "error_description", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "setkeys", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "applicationsecret", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "error", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "CheckSetupProcess", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "count", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "RefreshToken", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListTenantDrift": { + "get": { + "summary": "ListTenantDrift", + "tags": [ + "Tenant > Standards" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "applicationid", - "in": "body" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "step", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantid", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "partnersetup", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "code", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListTenantGroups": { + "post": { + "summary": "Entrypoint for listing tenant groups", + "tags": [ + "CIPP > Settings" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "CreateSAM", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - }, - "get": { - "description": "ExecSAMSetup", - "summary": "ExecSAMSetup", - "tags": ["GET"], + }, + "x-cipp-role": "CIPP.Core.Read", "parameters": [ { - "required": true, + "name": "groupId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "error_description", - "in": "query" + } }, { - "required": true, + "name": "includeUsage", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "setkeys", - "in": "query" - }, + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "groupId": { + "type": "string" + }, + "includeUsage": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ListTenantOnboarding": { + "get": { + "summary": "ListTenantOnboarding", + "tags": [ + "Tenant > Administration" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "applicationsecret", - "in": "query" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "error", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "CheckSetupProcess", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "count", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Administration.Read", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "standardsExcludeAllTenants": { + "type": "string" + }, + "id": { + "type": "string" + }, + "remapRoles": { + "type": "string" + }, + "gdapRoles": { + "type": "string" + }, + "ignoreMissingRoles": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/ListTenants": { + "post": { + "summary": "ListTenants", + "tags": [ + "Tenant > Administration > Tenant" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "RefreshToken", - "in": "query" + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "applicationid", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "CIPP.Core.Read", + "parameters": [ { - "required": true, + "name": "AllTenantSelector", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "step", - "in": "query" + } }, { - "required": true, + "name": "IncludeOffboardingDefaults", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantid", - "in": "query" + } }, { - "required": true, + "name": "Mode", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "partnersetup", - "in": "query" + } }, { - "required": true, + "name": "TriggerRefresh", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "code", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "CreateSAM", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ClearCache": { + "type": "string" + }, + "integrationCompany": { + "$ref": "#/components/schemas/LabelValue" + }, + "TenantsOnly": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListUserMailboxDetails": { + "/api/ListTestReports": { "get": { - "description": "ListUserMailboxDetails", - "summary": "ListUserMailboxDetails", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "Lists all available test reports from JSON files and database", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.Reports.Read" } }, - "/ListExConnectorTemplates": { - "get": { - "description": "ListExConnectorTemplates", - "summary": "ListExConnectorTemplates", - "tags": ["GET"], - "parameters": [ + "/api/ListTests": { + "post": { + "summary": "Lists tests for a tenant, optionally filtered by report ID", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/AddDefenderDeployment": { - "post": { - "description": "AddDefenderDeployment", - "summary": "AddDefenderDeployment", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ASR", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Compliance", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "EDR", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Reports.Read", + "parameters": [ { - "required": true, + "name": "reportId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "selectedTenants", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "Policy", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reportId": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } - }, - "description": "Successful operation" + } } } } }, - "/ListGDAPInvite": { + "/api/ListTransportRules": { "get": { - "description": "ListGDAPInvite", - "summary": "ListGDAPInvite", - "tags": ["GET"], - "parameters": [ + "summary": "ListTransportRules", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "RelationshipId", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/AddIntuneTemplate": { - "post": { - "description": "AddIntuneTemplate", - "summary": "AddIntuneTemplate", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TemplateType", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "description", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "displayname", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.TransportRule.Read", + "parameters": [ { - "required": true, + "name": "id", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "rawJSON", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ] + } + }, + "/api/ListTransportRulesTemplates": { + "get": { + "summary": "ListTransportRulesTemplates", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "URLName", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - }, - "get": { - "description": "AddIntuneTemplate", - "summary": "AddIntuneTemplate", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TemplateType", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "description", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "displayname", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "rawJSON", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.TransportRule.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, "name": "id", - "in": "query" - }, - { - "required": true, + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "URLName", - "in": "query" + } + } + ] + } + }, + "/api/ListUserConditionalAccessPolicies": { + "get": { + "summary": "ListUserConditionalAccessPolicies", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddAPDevice": { - "post": { - "description": "AddAPDevice", - "summary": "AddAPDevice", - "tags": ["POST"], + }, + "x-cipp-role": "Identity.User.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "autopilotData", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Groupname", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/RemoveTransportRuleTemplate": { + "/api/ListUserCounts": { "get": { - "description": "RemoveTransportRuleTemplate", - "summary": "RemoveTransportRuleTemplate", - "tags": ["GET"], - "parameters": [ + "summary": "ListUserCounts", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Identity.User.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/ListMailboxMobileDevices": { + "/api/ListUserDevices": { "get": { - "description": "ListMailboxMobileDevices", - "summary": "ListMailboxMobileDevices", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListUserDevices", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Mailbox", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecExcludeTenant": { - "post": { - "description": "ExecExcludeTenant", - "summary": "ExecExcludeTenant", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ListAll", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddExclusion", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "value", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "RemoveExclusion", - "in": "body" + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "List", - "in": "body" + } + } + ] + } + }, + "/api/ListUserGroups": { + "get": { + "summary": "ListUserGroups", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - }, - "get": { - "description": "ExecExcludeTenant", - "summary": "ExecExcludeTenant", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ListAll", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AddExclusion", - "in": "query" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "value", - "in": "query" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.Read", + "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "RemoveExclusion", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "userId", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "List", - "in": "query" + } + } + ] + } + }, + "/api/ListUserMailboxDetails": { + "get": { + "summary": "ListUserMailboxDetails", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecSetSecurityAlert": { - "get": { - "description": "ExecSetSecurityAlert", - "summary": "ExecSetSecurityAlert", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "vendor", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "GUID", - "in": "query" + } }, { - "required": true, + "name": "userMail", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "provider", - "in": "query" - }, + } + } + ] + } + }, + "/api/ListUserMailboxRules": { + "get": { + "summary": "ListUserMailboxRules", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Status", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecAddGDAPRole": { - "post": { - "description": "ExecAddGDAPRole", - "summary": "ExecAddGDAPRole", - "tags": ["POST"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", + "x-cipp-dynamic-options": true, "parameters": [ { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userEmail", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "gdapRoles", - "in": "body" + } }, { - "required": true, + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "customSuffix", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/ListExchangeConnectors": { + "/api/ListUserPhoto": { "get": { - "description": "ListExchangeConnectors", - "summary": "ListExchangeConnectors", - "tags": ["GET"], - "parameters": [ + "summary": "ListUserPhoto", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddTransportTemplate": { - "post": { - "description": "AddTransportTemplate", - "summary": "AddTransportTemplate", - "tags": ["POST"], + }, + "x-cipp-role": "Identity.User.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "PowerShellCommand", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "name", - "in": "body" + } + } + ] + } + }, + "/api/ListUserSettings": { + "get": { + "summary": "ListUserSettings", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Identity.User.Read" } }, - "/ExecCreateTAP": { + "/api/ListUserSigninLogs": { "get": { - "description": "ExecCreateTAP", - "summary": "ExecCreateTAP", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" - }, + "summary": "ListUserSigninLogs", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemoveCAPolicy": { - "get": { - "description": "RemoveCAPolicy", - "summary": "RemoveCAPolicy", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.User.Read", "parameters": [ { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "top", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "GUID", - "in": "query" + } + } + ] + } + }, + "/api/ListUserTrustedBlockedSenders": { + "get": { + "summary": "ListUserTrustedBlockedSenders", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemoveTransportRule": { - "get": { - "description": "RemoveTransportRule", - "summary": "RemoveTransportRule", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantfilter", - "in": "query" + } }, { - "required": true, + "name": "userPrincipalName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "guid", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + } } - } + ] } }, - "/RemoveAPDevice": { + "/api/ListUsers": { "get": { - "description": "RemoveAPDevice", - "summary": "RemoveAPDevice", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "summary": "ListUsers", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddPolicy": { - "post": { - "description": "AddPolicy", - "summary": "AddPolicy", - "tags": ["POST"], + }, + "x-cipp-role": "Identity.User.Read", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TemplateType", - "in": "body" - }, - { - "required": true, + "name": "graphFilter", + "in": "query", + "required": false, "schema": { - "type": "string" - }, - "name": "Description", - "in": "body" + "type": "string", + "description": "OData $filter expression passed to Graph" + } }, { - "required": true, + "name": "IncludeLogonDetails", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Displayname", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "replacemap", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "UserID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "RawJSON", - "in": "body" - }, + } + } + ] + } + }, + "/api/ListUsersAndGroups": { + "get": { + "summary": "ListUsersAndGroups", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Assignto", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Tenant.Directory.Read", + "parameters": [ + { + "$ref": "#/components/parameters/tenantFilter" + } + ] } }, - "/AddContact": { - "post": { - "description": "AddContact", - "summary": "AddContact", - "tags": ["POST"], - "parameters": [ + "/api/ListWebhookAlert": { + "get": { + "summary": "ListWebhookAlert", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantid", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "CIPP.Alert.Read" } }, - "/PublicScripts": { + "/api/ListmailboxPermissions": { "get": { - "description": "PublicScripts", - "summary": "PublicScripts", - "tags": ["GET"], - "parameters": [ + "summary": "ListmailboxPermissions", + "tags": [ + "Email-Exchange > Administration" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Guid", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemoveContact": { - "get": { - "description": "RemoveContact", - "summary": "RemoveContact", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Mailbox.Read", "parameters": [ { - "required": true, + "name": "ByUser", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantfilter", - "in": "query" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "UseReportDB", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "guid", - "in": "query" + } + }, + { + "name": "userId", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/PatchUser": { + "patch": { + "summary": "PatchUser", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string" + }, + "tenantFilter": { + "type": "string", + "description": "Required field on each array element (validated in handler)." + } + }, + "required": [ + "tenantFilter" + ] + } + }, + { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string" + }, + "tenantFilter": { + "type": "string", + "description": "Required field on each array element (validated in handler)." + } + }, + "required": [ + "tenantFilter" + ] + } + ], + "description": "Accepts a single object or an array of objects. Each object must include the required fields." + } + } } } } }, - "/EditTransportRule": { - "get": { - "description": "EditTransportRule", - "summary": "EditTransportRule", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "guid", - "in": "query" - }, + "/api/PublicPhishingCheck": { + "post": { + "summary": "PublicPhishingCheck", + "tags": [ + "Uncategorized" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "state", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Public", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "AlertMessage": { + "type": "string" + }, + "Cloned": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "score": { + "type": "string" + }, + "source": { + "type": "string" + }, + "TenantId": { + "type": "string" + }, + "threshold": { + "type": "string" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "userDisplayName": { + "type": "string" + }, + "userEmail": { + "type": "string" + } + } + } + } } } } }, - "/AddSpamFilter": { - "post": { - "description": "AddSpamFilter", - "summary": "AddSpamFilter", - "tags": ["POST"], - "parameters": [ + "/api/PublicPing": { + "get": { + "summary": "PublicPing", + "tags": [ + "CIPP > Core" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "PowerShellCommand", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } + }, + "x-cipp-role": "Public" } }, - "/RemoveIntuneTemplate": { - "get": { - "description": "RemoveIntuneTemplate", - "summary": "RemoveIntuneTemplate", - "tags": ["GET"], - "parameters": [ + "/api/PublicWebhooks": { + "post": { + "summary": "PublicWebhooks", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecGroupsDeliveryManagement": { - "get": { - "description": "ExecGroupsDeliveryManagement", - "summary": "ExecGroupsDeliveryManagement", - "tags": ["GET"], + }, + "x-cipp-role": "Public", "parameters": [ { - "required": true, + "name": "CIPPID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "GroupType", - "in": "query" + } }, { - "required": true, + "name": "Type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, + "name": "validationCode", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "id", - "in": "query" + } }, { - "required": true, + "name": "ValidationToken", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "OnlyAllowInternal", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "validationCode": { + "type": "string" + }, + "value": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListUserDevices": { - "get": { - "description": "ListUserDevices", - "summary": "ListUserDevices", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, + "/api/RemoveAPDevice": { + "post": { + "summary": "RemoveAPDevice", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/EditTenant": { - "post": { - "description": "EditTenant", - "summary": "EditTenant", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "displayName", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "defaultDomainName", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantid", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "customerId", - "in": "body" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecAppApproval": { - "get": { - "description": "ExecAppApproval", - "summary": "ExecAppApproval", - "tags": ["GET"], + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "applicationid", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "ID", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } - }, - "description": "Successful operation" + } } } } }, - "/ListInactiveAccounts": { - "get": { - "description": "ListInactiveAccounts", - "summary": "ListInactiveAccounts", - "tags": ["GET"], - "parameters": [ + "/api/RemoveApp": { + "post": { + "summary": "RemoveApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/AddAlert": { - "post": { - "description": "AddAlert", - "summary": "AddAlert", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ApnCertExpiry", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "NewRole", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "MFAAlertUsers", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "DefenderMalware", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "NewGA", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "QuotaUsed", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AppSecretExpiry", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "OverusedLicenses", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "VppTokenExpiry", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "SecDefaultsUpsell", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "NoCAConfig", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "DefenderStatus", - "in": "body" + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "AdminPassword", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "MFAAdmins", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "DepTokenExpiry", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Application.ReadWrite", + "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "SharePointQuota", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "ExpiringLicenses", - "in": "body" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/RemoveAssignmentFilterTemplate": { + "post": { + "summary": "RemoveAssignmentFilterTemplate", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "UnusedLicenses", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListMailboxes": { - "get": { - "description": "ListMailboxes", - "summary": "ListMailboxes", - "tags": ["GET"], + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListMFAUsers": { - "get": { - "description": "ListMFAUsers", - "summary": "ListMFAUsers", - "tags": ["GET"], - "parameters": [ + "/api/RemoveAutopilotConfig": { + "post": { + "summary": "RemoveAutopilotConfig", + "tags": [ + "Endpoint > Autopilot" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.Autopilot.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "assignments": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } } } } }, - "/RemoveGroupTemplate": { - "get": { - "description": "RemoveGroupTemplate", - "summary": "RemoveGroupTemplate", - "tags": ["GET"], - "parameters": [ + "/api/RemoveBPATemplate": { + "post": { + "summary": "RemoveBPATemplate", + "tags": [ + "Tenant > Standards" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecRunBackup": { - "get": { - "description": "ExecRunBackup", - "summary": "ExecRunBackup", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", "parameters": [ { - "required": true, + "name": "TemplateName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Selected", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TemplateName": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListTransportRules": { - "get": { - "description": "ListTransportRules", - "summary": "ListTransportRules", - "tags": ["GET"], - "parameters": [ + "/api/RemoveCAPolicy": { + "post": { + "summary": "RemoveCAPolicy", + "tags": [ + "Tenant > Conditional" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecMaintenanceScripts": { - "get": { - "description": "ExecMaintenanceScripts", - "summary": "ExecMaintenanceScripts", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", "parameters": [ { - "required": true, + "name": "GUID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "MakeLink", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "ScriptFile", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/RemoveCATemplate": { + "post": { + "summary": "RemoveCATemplate", + "tags": [ + "Tenant > Conditional" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.ConditionalAccess.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/GetCippAlerts": { - "get": { - "description": "GetCippAlerts", - "summary": "GetCippAlerts", - "tags": ["GET"], - "parameters": [ + "/api/RemoveConnectionfilterTemplate": { + "post": { + "summary": "RemoveConnectionfilterTemplate", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "localversion", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.ConnectionFilter.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } + } + } + } } } } }, - "/ExecGDAPMigration": { + "/api/RemoveContact": { "post": { - "description": "ExecGDAPMigration", - "summary": "ExecGDAPMigration", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "selectedTenants", - "in": "body" - }, + "summary": "RemoveContact", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "gdapRoles", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListGroups": { - "get": { - "description": "ListGroups", - "summary": "ListGroups", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Contact.ReadWrite", "parameters": [ { - "required": true, + "name": "GUID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "GroupID", - "in": "query" + } }, { - "required": true, + "name": "Mail", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "owners", - "in": "query" - }, + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "Mail": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/RemoveContactTemplates": { + "post": { + "summary": "RemoveContactTemplates", + "tags": [ + "Email-Exchange > Administration > Contacts" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "members", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListDomains": { - "get": { - "description": "ListDomains", - "summary": "ListDomains", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Contact.ReadWrite", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListExternalTenantInfo": { - "get": { - "description": "ListExternalTenantInfo", - "summary": "ListExternalTenantInfo", - "tags": ["GET"], - "parameters": [ + "/api/RemoveDeletedObject": { + "post": { + "summary": "RemoveDeletedObject", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenant", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListAPDevices": { - "get": { - "description": "ListAPDevices", - "summary": "ListAPDevices", - "tags": ["GET"], + }, + "x-cipp-role": "Tenant.Directory.ReadWrite", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "UserID", - "in": "query" + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/RemoveExConnector": { + "post": { + "summary": "RemoveExConnector", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddAutopilotConfig": { - "post": { - "description": "AddAutopilotConfig", - "summary": "AddAutopilotConfig", - "tags": ["POST"], + }, + "x-cipp-role": "Exchange.Connector.ReadWrite", "parameters": [ { - "required": true, + "name": "GUID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Displayname", - "in": "body" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "Type", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Description", - "in": "body" - }, + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "Type": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] + } + } + } + } + } + }, + "/api/RemoveExConnectorTemplate": { + "post": { + "summary": "RemoveExConnectorTemplate", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Assignto", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/AddCAPolicy": { - "post": { - "description": "AddCAPolicy", - "summary": "AddCAPolicy", - "tags": ["POST"], + }, + "x-cipp-role": "Exchange.Connector.ReadWrite", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "RawJSON", - "in": "body" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListMailboxStatistics": { - "get": { - "description": "ListMailboxStatistics", - "summary": "ListMailboxStatistics", - "tags": ["GET"], - "parameters": [ + "/api/RemoveGroupTemplate": { + "post": { + "summary": "RemoveGroupTemplate", + "tags": [ + "Identity > Administration > Groups" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ExecAssignApp": { - "get": { - "description": "ExecAssignApp", - "summary": "ExecAssignApp", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.Group.ReadWrite", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, "name": "ID", - "in": "query" - }, - { - "required": true, + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "AssignTo", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ExecExtensionTest": { - "get": { - "description": "ExecExtensionTest", - "summary": "ExecExtensionTest", - "tags": ["GET"], - "parameters": [ + "/api/RemoveIntuneReusableSetting": { + "post": { + "summary": "RemoveIntuneReusableSetting", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "extensionName", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/ExecSetMailboxQuota": { - "post": { - "description": "ExecSetMailboxQuota", - "summary": "ExecSetMailboxQuota", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "input", - "in": "body" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ProhibitSendReceiveQuota", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ProhibitSendQuota", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "parameters": [ { - "required": true, + "name": "DisplayName", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantfilter", - "in": "body" + } }, { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "IssueWarningQuota", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "string" - }, - "name": "user", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "DisplayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } - }, - "description": "Successful operation" + } } } } }, - "/ListNamedLocations": { - "get": { - "description": "ListNamedLocations", - "summary": "ListNamedLocations", - "tags": ["GET"], - "parameters": [ + "/api/RemoveIntuneReusableSettingTemplate": { + "post": { + "summary": "RemoveIntuneReusableSettingTemplate", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "TenantFilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListMFAUsersAllTenants": { - "get": { - "description": "ListMFAUsersAllTenants", - "summary": "ListMFAUsersAllTenants", - "tags": ["GET"], + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/RemoveQueuedAlert": { - "get": { - "description": "RemoveQueuedAlert", - "summary": "RemoveQueuedAlert", - "tags": ["GET"], - "parameters": [ + "/api/RemoveIntuneScript": { + "post": { + "summary": "RemoveIntuneScript", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "id", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "DisplayName": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "ScriptType": { + "type": "string" + }, + "TenantFilter": { + "type": "string" + } + }, + "required": [ + "TenantFilter" + ] + } + } } } } }, - "/ExecAccessChecks": { + "/api/RemoveIntuneTemplate": { "post": { - "description": "ExecAccessChecks", - "summary": "ExecAccessChecks", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Tenants", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantid", - "in": "body" - }, + "summary": "RemoveIntuneTemplate", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Permissions", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - }, - "get": { - "description": "ExecAccessChecks", - "summary": "ExecAccessChecks", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Tenants", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantid", - "in": "query" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Permissions", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListSharedMailboxAccountEnabled": { - "get": { - "description": "ListSharedMailboxAccountEnabled", - "summary": "ListSharedMailboxAccountEnabled", - "tags": ["GET"], + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", "parameters": [ { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "TenantFilter", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/ListSpamfilter": { - "get": { - "description": "ListSpamfilter", - "summary": "ListSpamfilter", - "tags": ["GET"], - "parameters": [ + "/api/RemoveJITAdminTemplate": { + "post": { + "summary": "RemoveJITAdminTemplate", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/ListQuarantinePolicy": { - "get": { - "description": "ListQuarantinePolicy", - "summary": "ListQuarantinePolicy", - "tags": ["GET"], + }, + "x-cipp-role": "Identity.Role.ReadWrite", "parameters": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" - }, - { - "required": true, + "name": "ID", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Type", - "in": "query" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + } } } - }, - "description": "Successful operation" + } } } } }, - "/AddQuarantinePolicy": { + "/api/RemovePolicy": { "post": { - "description": "AddQuarantinePolicy", - "summary": "AddQuarantinePolicy", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Name", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "BlockSender", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "Delete", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "Preview", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "ReleaseActionPreference", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "AllowSender", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "QuarantineNotification", - "in": "body" - }, + "summary": "RemovePolicy", + "tags": [ + "Endpoint > MEM" + ], + "security": [ { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "IncludeMessagesFromBlockedSenderAddress", - "in": "body" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" - } - } - } - }, - "/EditQuarantinePolicy": { - "post": { - "description": "EditQuarantinePolicy", - "summary": "EditQuarantinePolicy", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Type", - "in": "query" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Identity", - "in": "body" + "400": { + "description": "Bad request — missing required field or invalid input" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "EndUserSpamNotificationFrequency", - "in": "body" + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "EndUserSpamNotificationCustomFromAddress", - "in": "body" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Endpoint.MEM.ReadWrite", + "parameters": [ { + "name": "ID", + "in": "query", "required": false, - "schema": { - "type": "boolean" - }, - "name": "OrganizationBrandingEnabled", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "BlockSender", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "Delete", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "Preview", - "in": "body" - }, - { - "required": true, "schema": { "type": "string" - }, - "name": "ReleaseActionPreference", - "in": "body" - }, - { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "AllowSender", - "in": "body" + } }, { - "required": true, - "schema": { - "type": "boolean" - }, - "name": "QuarantineNotification", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" }, { - "required": true, + "name": "URLName", + "in": "query", + "required": false, "schema": { - "type": "boolean" - }, - "name": "IncludeMessagesFromBlockedSenderAddress", - "in": "body" + "type": "string" + } } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "URLName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } - }, - "description": "Successful operation" + } } } } }, - "/RemoveExConnector": { - "get": { - "description": "RemoveExConnector", - "summary": "RemoveExConnector", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantfilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "guid", - "in": "query" - }, + "/api/RemoveQuarantinePolicy": { + "post": { + "summary": "RemoveQuarantinePolicy", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ { - "required": true, - "schema": { - "type": "string" - }, - "name": "Type", - "in": "query" + "bearerAuth": [] } ], "responses": { "200": { + "description": "Success", "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "$ref": "#/components/schemas/StandardResults" } } - }, - "description": "Successful operation" + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - } - }, - "/RemoveQuarantinePolicy": { - "get": { - "description": "RemoveQuarantinePolicy", - "summary": "RemoveQuarantinePolicy", - "tags": ["GET"], + }, + "x-cipp-role": "Exchange.Spamfilter.ReadWrite", "parameters": [ { - "required": true, + "name": "Identity", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "tenantfilter", - "in": "query" + } }, { - "required": true, + "name": "Name", + "in": "query", + "required": false, "schema": { "type": "string" - }, - "name": "Identity", - "in": "body" + } }, { - "required": false, - "schema": { - "type": "string" - }, - "name": "Name", - "in": "body" + "$ref": "#/components/parameters/tenantFilter" } ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - } - }, - "/ExecDeleteSafeLinksPolicy": { - "get": { - "description": "ExecDeleteSafeLinksPolicy", - "summary": "ExecDeleteSafeLinksPolicy", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "RuleName", - "in": "query" - }, - { + "requestBody": { "required": true, - "schema": { - "type": "string" - }, - "name": "PolicyName", - "in": "query" - } - ], - "responses": { - "200": { "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" - } - } - }, - "description": "Successful operation" - } - } - } - }, - "/EditSafeLinksPolicy": { - "post": { - "description": "EditSafeLinksPolicy", - "summary": "EditSafeLinksPolicy", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "PolicyName", - "in": "query" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Name": { - "type": "string" - }, - "EnableSafeLinksForEmail": { - "type": "boolean" - }, - "EnableSafeLinksForTeams": { - "type": "boolean" - }, - "EnableSafeLinksForOffice": { - "type": "boolean" - }, - "TrackClicks": { - "type": "boolean" - }, - "AllowClickThrough": { - "type": "boolean" - }, - "ScanUrls": { - "type": "boolean" - }, - "EnableForInternalSenders": { - "type": "boolean" - }, - "DeliverMessageAfterScan": { - "type": "boolean" - }, - "DisableUrlRewrite": { - "type": "boolean" - }, - "DoNotRewriteUrls": { - "type": "array", - "items": { - "type": "string" - } - }, - "AdminDisplayName": { - "type": "string" - }, - "CustomNotificationText": { - "type": "string" - }, - "EnableOrganizationBranding": { - "type": "boolean" - }, - "Priority": { - "type": "integer" - }, - "Comments": { - "type": "string" - }, - "Enabled": { - "type": "boolean" - }, - "SentTo": { - "type": "array", - "items": { - "type": "string" - } - }, - "ExceptIfSentTo": { - "type": "array", - "items": { - "type": "string" - } - }, - "SentToMemberOf": { - "type": "array", - "items": { + "type": "object", + "properties": { + "Identity": { "type": "string" - } - }, - "ExceptIfSentToMemberOf": { - "type": "array", - "items": { + }, + "Name": { "type": "string" - } - }, - "RecipientDomainIs": { - "type": "array", - "items": { + }, + "TenantFilter": { "type": "string" } }, - "ExceptIfRecipientDomainIs": { - "type": "array", - "items": { - "type": "string" - } - } + "required": [ + "TenantFilter" + ] } } } } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Results": { - "type": "array", - "items": { - "type": "string" - } - } + } + }, + "/api/RemoveQueuedAlert": { + "post": { + "summary": "RemoveQueuedAlert", + "tags": [ + "Tenant > Administration > Alerts" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" } } } }, - "description": "Successful operation" - } - } - } - }, - "/ListSafeLinksPolicyDetails": { - "get": { - "description": "ListSafeLinksPolicyDetails", - "summary": "ListSafeLinksPolicyDetails", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantFilter", - "in": "query" - }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "PolicyName", - "in": "query" - }, - { - "required": false, - "schema": { - "type": "string" + "400": { + "description": "Bad request — missing required field or invalid input" }, - "name": "RuleName", - "in": "query" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "Results": { - "type": "object", - "properties": { - "Policy": { - "type": "object", - "properties": { - "Name": { - "type": "string" - }, - "EnableSafeLinksForEmail": { - "type": "boolean" - }, - "EnableSafeLinksForTeams": { - "type": "boolean" - }, - "EnableSafeLinksForOffice": { - "type": "boolean" - }, - "TrackClicks": { - "type": "boolean" - }, - "AllowClickThrough": { - "type": "boolean" - }, - "ScanUrls": { - "type": "boolean" - }, - "EnableForInternalSenders": { - "type": "boolean" - }, - "DeliverMessageAfterScan": { - "type": "boolean" - }, - "DisableUrlRewrite": { - "type": "boolean" - }, - "DoNotRewriteUrls": { - "type": "array", - "items": { - "type": "string" - } - }, - "AdminDisplayName": { - "type": "string" - }, - "CustomNotificationText": { - "type": "string" - }, - "EnableOrganizationBranding": { - "type": "boolean" - } - } - }, - "Rule": { - "type": "object", - "properties": { - "Name": { - "type": "string" - }, - "Priority": { - "type": "integer" - }, - "Comments": { - "type": "string" - }, - "State": { - "type": "string" - }, - "SentTo": { - "type": "array", - "items": { - "type": "string" - } - }, - "ExceptIfSentTo": { - "type": "array", - "items": { - "type": "string" - } - }, - "SentToMemberOf": { - "type": "array", - "items": { - "type": "string" - } - }, - "ExceptIfSentToMemberOf": { - "type": "array", - "items": { - "type": "string" - } - }, - "RecipientDomainIs": { - "type": "array", - "items": { - "type": "string" - } - }, - "ExceptIfRecipientDomainIs": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Message": { - "type": "string" - } - } - } - } - } - } + "401": { + "description": "Unauthorized — invalid or missing bearer token" }, - "description": "Successful operation" - } - } - } - }, - "/ExecNewSafeLinksPolicy": { - "post": { - "description": "Create a new SafeLinks policy and associated rule", - "summary": "Create SafeLinks Policy and Rule Configuration", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" + "403": { + "description": "Forbidden — caller lacks the required RBAC role" }, - "name": "tenantFilter", - "in": "query" + "500": { + "description": "Internal server error" + } }, - { - "required": true, - "schema": { - "type": "string" - }, - "name": "Name", - "in": "query" - } - ], - "requestBody": { - "content": { - "application/json": { + "x-cipp-role": "CIPP.Alert.ReadWrite", + "parameters": [ + { + "name": "EventType", + "in": "query", + "required": false, "schema": { - "type": "object", - "properties": { - "EnableSafeLinksForEmail": { - "type": "boolean", - "description": "Enable Safe Links protection for email messages" - }, - "EnableSafeLinksForTeams": { - "type": "boolean", - "description": "Enable Safe Links protection for Teams messages" - }, - "EnableSafeLinksForOffice": { - "type": "boolean", - "description": "Enable Safe Links protection for Office applications" - }, - "TrackClicks": { - "type": "boolean", - "description": "Track user clicks related to Safe Links protection" - }, - "AllowClickThrough": { - "type": "boolean", - "description": "Allow users to click through to the original URL" - }, - "ScanUrls": { - "type": "boolean", - "description": "Enable real-time scanning of URLs" - }, - "EnableForInternalSenders": { - "type": "boolean", - "description": "Enable Safe Links for messages sent between internal senders" - }, - "DeliverMessageAfterScan": { - "type": "boolean", - "description": "Wait until URL scanning is complete before delivering the message" - }, - "DisableUrlRewrite": { - "type": "boolean", - "description": "Disable the rewriting of URLs in messages" - }, - "DoNotRewriteUrls": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of URLs that will not be rewritten by Safe Links" - }, - "AdminDisplayName": { - "type": "string", - "description": "Display name for the policy in the admin interface" - }, - "CustomNotificationText": { - "type": "string", - "description": "Custom text for the notification when a link is blocked" - }, - "EnableOrganizationBranding": { - "type": "boolean", - "description": "Enable organization branding on warning pages" - }, - "Priority": { - "type": "integer", - "description": "Priority of the rule (lower numbers = higher priority)" - }, - "Comments": { - "type": "string", - "description": "Administrative comments for the rule" - }, - "Enabled": { - "type": "boolean", - "description": "Whether the rule is enabled or disabled" - }, - "SentTo": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Apply the rule if the recipient is any of these email addresses" - }, - "SentToMemberOf": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Apply the rule if the recipient is a member of any of these groups" - }, - "RecipientDomainIs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Apply the rule if the recipient's domain matches any of these domains" - }, - "ExceptIfSentTo": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Do not apply the rule if the recipient is any of these email addresses" - }, - "ExceptIfSentToMemberOf": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Do not apply the rule if the recipient is a member of any of these groups" - }, - "ExceptIfRecipientDomainIs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Do not apply the rule if the recipient's domain matches any of these domains" - } - } + "type": "string" + } + }, + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" } } - } - }, - "responses": { - "200": { + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "string", - "description": "Result message from the operation" + "EventType": { + "type": "string" + }, + "ID": { + "type": "string" } } } } + } + } + } + }, + "/api/RemoveQueuedApp": { + "post": { + "summary": "RemoveQueuedApp", + "tags": [ + "Endpoint > Applications" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" }, - "description": "Successfully created SafeLinks policy and rule" + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "400": { - "description": "Bad request - missing required parameters", + "x-cipp-role": "Endpoint.Application.ReadWrite", + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "string", - "description": "Error message" + "ID": { + "type": "string" } } } } } + } + } + }, + "/api/RemoveSafeLinksPolicyTemplate": { + "post": { + "summary": "RemoveSafeLinksPolicyTemplate", + "tags": [ + "Security > Safe-Links-Policy" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "500": { - "description": "Internal server error", + "x-cipp-role": "Exchange.SafeLinks.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "string", - "description": "Error message" + "ID": { + "type": "string" } } } @@ -9431,736 +33767,869 @@ } } } - } - }, - "/ListSafeLinksPolicyTemplates": { - "get": { - "description": "List SafeLinks Policy Templates", - "summary": "List SafeLinks Policy Templates", - "tags": ["GET"], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": {} + }, + "/api/RemoveScheduledItem": { + "post": { + "summary": "RemoveScheduledItem", + "tags": [ + "CIPP > Scheduler" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" } } } }, - "description": "Successful operation" - } - } - } - }, - "/RemoveSafeLinksPolicyTemplate": { - "get": { - "description": "Remove SafeLinks Policy Template", - "summary": "Remove SafeLinks Policy Template", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" + "400": { + "description": "Bad request — missing required field or invalid input" }, - "name": "id", - "in": "query" - } - ], - "responses": { - "200": { + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "description": "Removes a scheduled item from CIPP's scheduler.\n #>\n [CmdletBinding()]\n param($Request, $TriggerMetadata)", + "x-cipp-role": "CIPP.Scheduler.ReadWrite", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "type": "object", + "properties": { + "id": { + "type": "string" + } + } } } - }, - "description": "Successful operation" + } } } - } - }, - "/AddSafeLinksPolicyTemplate": { - "post": { - "description": "Add SafeLinks Policy Template", - "summary": "Add SafeLinks Policy Template", - "tags": ["POST"], - "requestBody": { - "content": { - "application/json": { + }, + "/api/RemoveSpamfilter": { + "post": { + "summary": "RemoveSpamfilter", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Exchange.Spamfilter.ReadWrite", + "parameters": [ + { + "name": "name", + "in": "query", + "required": false, "schema": { - "type": "object", - "properties": {} + "type": "string" } + }, + { + "$ref": "#/components/parameters/tenantFilter" } - } - }, - "responses": { - "200": { + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "type": "object", + "properties": { + "name": { + "type": "string" + } + } } } - }, - "description": "Successful operation" + } } } - } - }, - "/AddSafeLinksPolicyFromTemplate": { - "post": { - "description": "Deploy SafeLinks Policy From Template", - "summary": "Deploy SafeLinks Policy From Template", - "tags": ["POST"], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} + }, + "/api/RemoveSpamfilterTemplate": { + "post": { + "summary": "RemoveSpamfilterTemplate", + "tags": [ + "Email-Exchange > Spamfilter" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } - } - }, - "responses": { - "200": { + }, + "x-cipp-role": "Exchange.Spamfilter.ReadWrite", + "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "properties": {}, - "type": "object" + "type": "object", + "properties": { + "ID": { + "type": "string" + } + } } } - }, - "description": "Successful operation" + } } } - } - }, - "/ExecManageRetentionPolicies": { - "get": { - "description": "List retention policies or get a specific retention policy by name", - "summary": "Get Retention Policies", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" + }, + "/api/RemoveStandard": { + "get": { + "summary": "RemoveStandard", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - "name": "tenantFilter", - "in": "query" - }, - { - "required": false, - "schema": { - "type": "string" + "400": { + "description": "Bad request — missing required field or invalid input" }, - "name": "name", - "in": "query", - "description": "Name of specific retention policy to retrieve" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "Name": { - "type": "string" - }, - "RetentionPolicyTagLinks": { - "type": "array", - "items": { - "type": "string" - } - } - } + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/RemoveStandardTemplate": { + "post": { + "summary": "RemoveStandardTemplate", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" } } } }, - "description": "Successfully retrieved retention policies" + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "400": { - "description": "Bad request - missing required parameters", + "x-cipp-role": "Tenant.Standards.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "string", - "description": "Error message" + "ID": { + "type": "string" } } } } } + } + } + }, + "/api/RemoveTenantAllowBlockList": { + "post": { + "summary": "RemoveTenantAllowBlockList", + "tags": [ + "Uncategorized" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "500": { - "description": "Internal server error", + "x-cipp-role": "Exchange.SpamFilter.ReadWrite", + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "string", - "description": "Error message" + "Entries": { + "type": "string" + }, + "ListType": { + "type": "string" + }, + "tenantFilter": { + "type": "string" } - } + }, + "required": [ + "tenantFilter" + ] } } } } } }, - "post": { - "description": "Create, modify, or delete retention policies", - "summary": "Manage Retention Policies", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantFilter", - "in": "query" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "CreatePolicies": { - "type": "array", - "items": { - "type": "object", - "properties": { - "Name": { - "type": "string", - "description": "Name of the retention policy" - }, - "RetentionPolicyTagLinks": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of retention tag names to link to this policy" - } - }, - "required": ["Name"] - }, - "description": "Array of retention policies to create" - }, - "ModifyPolicies": { - "type": "array", - "items": { - "type": "object", - "properties": { - "Identity": { - "type": "string", - "description": "Identity of the retention policy to modify" - }, - "Name": { - "type": "string", - "description": "New name for the retention policy" - }, - "RetentionPolicyTagLinks": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of retention tag names to link to this policy" - }, - "AddTags": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of retention tag names to add to the policy" - }, - "RemoveTags": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of retention tag names to remove from the policy" - } - }, - "required": ["Identity"] - }, - "description": "Array of retention policies to modify" - }, - "DeletePolicies": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of retention policy identities to delete" - } - } - } + "/api/RemoveTenantCapabilitiesCache": { + "get": { + "summary": "RemoveTenantCapabilitiesCache", + "tags": [ + "Tenant > Administration > Tenant" + ], + "security": [ + { + "bearerAuth": [] } - } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "string" + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" } } } }, - "description": "Successfully processed retention policy operations" + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "400": { - "description": "Bad request - missing required parameters", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "string" + "x-cipp-role": "Tenant.Administration.ReadWrite", + "parameters": [ + { + "name": "defaultDomainName", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/RemoveTransportRule": { + "post": { + "summary": "RemoveTransportRule", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" } } } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } }, - "403": { - "description": "Forbidden - insufficient permissions", + "x-cipp-role": "Exchange.TransportRule.ReadWrite", + "parameters": [ + { + "name": "guid", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + } + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "string" - } + "type": "object", + "properties": { + "guid": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } } } } } - } - }, - "/ExecManageRetentionTags": { - "get": { - "description": "List retention tags or get a specific retention tag by name", - "summary": "Get Retention Tags", - "tags": ["GET"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantFilter", - "in": "query" - }, - { - "required": false, - "schema": { - "type": "string" - }, - "name": "name", - "in": "query", - "description": "Name of specific retention tag to retrieve" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "Name": { - "type": "string" - }, - "Type": { - "type": "string" - }, - "RetentionAction": { - "type": "string" - }, - "AgeLimitForRetention": { - "type": "integer" - }, - "RetentionEnabled": { - "type": "boolean" - } - } + }, + "/api/RemoveTransportRuleTemplate": { + "post": { + "summary": "RemoveTransportRuleTemplate", + "tags": [ + "Email-Exchange > Transport" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" } } } }, - "description": "Successfully retrieved retention tags" + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "400": { - "description": "Bad request - missing required parameters", + "x-cipp-role": "Exchange.TransportRule.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "string", - "description": "Error message" + "ID": { + "type": "string" } } } } } + } + } + }, + "/api/RemoveTrustedBlockedSender": { + "post": { + "summary": "RemoveTrustedBlockedSender", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "500": { - "description": "Internal server error", + "x-cipp-role": "Exchange.Mailbox.ReadWrite", + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "string", - "description": "Error message" - } - } - } - } - } - } - } - }, - "post": { - "description": "Create, modify, or delete retention tags", - "summary": "Manage Retention Tags", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" - }, - "name": "tenantFilter", - "in": "query" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "CreateTags": { - "type": "array", - "items": { - "type": "object", - "properties": { - "Name": { - "type": "string", - "description": "Name of the retention tag (max 64 characters)" - }, - "Type": { - "type": "string", - "enum": [ - "All", - "Inbox", - "SentItems", - "DeletedItems", - "Drafts", - "Outbox", - "JunkEmail", - "Journal", - "SyncIssues", - "ConversationHistory", - "Personal", - "RecoverableItems", - "NonIpmRoot", - "LegacyArchiveJournals", - "Clutter", - "Calendar", - "Notes", - "Tasks", - "Contacts", - "RssSubscriptions", - "ManagedCustomFolder" - ], - "description": "Type of the retention tag" - }, - "RetentionAction": { - "type": "string", - "enum": [ - "DeleteAndAllowRecovery", - "PermanentlyDelete", - "MoveToArchive", - "MarkAsPastRetentionLimit" - ], - "description": "Action to take when retention period expires" - }, - "AgeLimitForRetention": { - "type": "integer", - "minimum": 0, - "maximum": 24855, - "description": "Age limit for retention in days" - }, - "RetentionEnabled": { - "type": "boolean", - "description": "Whether retention is enabled for this tag" - }, - "Comment": { - "type": "string", - "description": "Administrative comment for the tag" - }, - "LocalizedComment": { - "type": "string", - "description": "Localized comment for the tag" - }, - "LocalizedRetentionPolicyTagName": { - "type": "string", - "description": "Localized name for the retention policy tag" - } - }, - "required": ["Name", "Type"] + "tenantFilter": { + "type": "string" }, - "description": "Array of retention tags to create" - }, - "ModifyTags": { - "type": "array", - "items": { - "type": "object", - "properties": { - "Identity": { - "type": "string", - "description": "Identity of the retention tag to modify" - }, - "Name": { - "type": "string", - "description": "New name for the retention tag" - }, - "RetentionAction": { - "type": "string", - "enum": [ - "DeleteAndAllowRecovery", - "PermanentlyDelete", - "MoveToArchive", - "MarkAsPastRetentionLimit" - ], - "description": "Action to take when retention period expires" - }, - "AgeLimitForRetention": { - "type": "integer", - "minimum": 0, - "maximum": 24855, - "description": "Age limit for retention in days" - }, - "RetentionEnabled": { - "type": "boolean", - "description": "Whether retention is enabled for this tag" - }, - "Comment": { - "type": "string", - "description": "Administrative comment for the tag" - }, - "LocalizedComment": { - "type": "string", - "description": "Localized comment for the tag" - }, - "LocalizedRetentionPolicyTagName": { - "type": "string", - "description": "Localized name for the retention policy tag" - } - }, - "required": ["Identity"] + "typeProperty": { + "type": "string" }, - "description": "Array of retention tags to modify" - }, - "DeleteTags": { - "type": "array", - "items": { + "userPrincipalName": { "type": "string" }, - "description": "Array of retention tag identities to delete" - } + "value": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } } } } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "string" + } + }, + "/api/RemoveUser": { + "post": { + "summary": "RemoveUser", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" } } } }, - "description": "Successfully processed retention tag operations" + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } }, - "400": { - "description": "Bad request - missing required parameters", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "string" - } - } + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/tenantFilter" + }, + { + "name": "userPrincipalName", + "in": "query", + "required": false, + "schema": { + "type": "string" } } - }, - "403": { - "description": "Forbidden - insufficient permissions", + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "string" - } + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "tenantFilter": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + } + }, + "required": [ + "tenantFilter" + ] } } } } } - } - }, - "/ExecSetMailboxRetentionPolicies": { - "post": { - "description": "Apply a retention policy to one or more mailboxes", - "summary": "Set Mailbox Retention Policies", - "tags": ["POST"], - "parameters": [ - { - "required": true, - "schema": { - "type": "string" + }, + "/api/RemoveUserDefaultTemplate": { + "post": { + "summary": "RemoveUserDefaultTemplate", + "tags": [ + "Identity > Administration > Users" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } }, - "name": "tenantFilter", - "in": "query" - } - ], - "requestBody": { - "content": { - "application/json": { + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Identity.User.ReadWrite", + "parameters": [ + { + "name": "ID", + "in": "query", + "required": false, "schema": { - "type": "object", - "properties": { - "PolicyName": { - "type": "string", - "description": "Name of the retention policy to apply" - }, - "Mailboxes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of mailbox identities (email addresses or names) to apply the policy to" - } - }, - "required": ["PolicyName", "Mailboxes"] + "type": "string" } } - } - }, - "responses": { - "200": { + ], + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Array of result messages for each mailbox operation" + "ID": { + "type": "string" } } } } - }, - "description": "Successfully processed mailbox retention policy assignments" - }, - "400": { - "description": "Bad request - missing required parameters", - "content": { - "application/json": { - "schema": { - "type": "string" + } + } + } + }, + "/api/SetAuthMethod": { + "post": { + "summary": "SetAuthMethod", + "tags": [ + "Tenant > Administration" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } } } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" } }, - "403": { - "description": "Forbidden - insufficient permissions", + "x-cipp-role": "Tenant.Administration.ReadWrite", + "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { - "Results": { - "type": "array", - "items": { - "type": "string" - } + "GroupIds": { + "type": "string" + }, + "Id": { + "type": "string" + }, + "state": { + "type": "string" + }, + "tenantFilter": { + "type": "string" } - } + }, + "required": [ + "tenantFilter" + ] } } } } } - } - }, - "openapi": "3.1.0", - "servers": [ - { - "variables": { - "url": { - "description": "The base URL for the API. Enter your server URL here.", - "default": "CIPPURL.com" - } - }, - "url": "https://{url}/api/", - "description": "CIPP-API" - } - ], - "tags": [ - { - "name": "GET", - "description": "GET methods" }, - { - "name": "POST", - "description": "POST methods" + "/api/listStandardTemplates": { + "get": { + "summary": "listStandardTemplates", + "tags": [ + "Tenant > Standards" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResults" + } + } + } + }, + "400": { + "description": "Bad request — missing required field or invalid input" + }, + "401": { + "description": "Unauthorized — invalid or missing bearer token" + }, + "403": { + "description": "Forbidden — caller lacks the required RBAC role" + }, + "500": { + "description": "Internal server error" + } + }, + "x-cipp-role": "Tenant.Standards.Read", + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ] + } } - ] -} + } +} \ No newline at end of file From 8341f49d863900a36070225eb9c39b0813c91c94 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 31 May 2026 23:15:16 +0200 Subject: [PATCH 81/90] Test --- .../CIPP/MCP/Get-CippMcpSpec.ps1 | 39 +++++ .../CIPP/MCP/Get-CippMcpToolList.ps1 | 149 ++++++++++++++++++ .../CIPP/MCP/Get-CippMcpToolResult.ps1 | 84 ++++++++++ .../CIPP/MCP/Invoke-ExecMcp.ps1 | 74 +++++++++ 4 files changed, 346 insertions(+) create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpSpec.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolList.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolResult.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Invoke-ExecMcp.ps1 diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpSpec.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpSpec.ps1 new file mode 100644 index 000000000000..341f7eb08f9f --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpSpec.ps1 @@ -0,0 +1,39 @@ +function Get-CippMcpSpec { + <# + .SYNOPSIS + Loads and caches the CIPP OpenAPI specification (openapi.json). + .DESCRIPTION + Returns the parsed OpenAPI document used to project the MCP tool list. The result + is cached per worker runspace; pass -Force to reload (e.g. after the spec is + regenerated). Not an HTTP entrypoint. + .FUNCTIONALITY + Internal + #> + [CmdletBinding()] + param([switch]$Force) + + if ($script:CippMcpSpec -and -not $Force) { + return $script:CippMcpSpec + } + + $Root = $env:CIPPRootPath + if (-not $Root -or -not (Test-Path (Join-Path $Root 'openapi.json'))) { + # Fallback: walk up from this module until openapi.json is found. + $Root = $PSScriptRoot + while ($Root -and -not (Test-Path (Join-Path $Root 'openapi.json'))) { + $Parent = Split-Path $Root -Parent + if (-not $Parent -or $Parent -eq $Root) { $Root = $null; break } + $Root = $Parent + } + } + + $SpecPath = if ($Root) { Join-Path $Root 'openapi.json' } else { $null } + if (-not $SpecPath -or -not (Test-Path $SpecPath)) { + throw [pscustomobject]@{ code = -32603; message = 'OpenAPI spec (openapi.json) not found; cannot project MCP tools.' } + } + + # -AsHashtable is required: the spec contains objects with case-differing keys + # (e.g. displayName / DisplayName) which a case-insensitive PSCustomObject cannot hold. + $script:CippMcpSpec = [System.IO.File]::ReadAllText($SpecPath) | ConvertFrom-Json -AsHashtable -Depth 100 + return $script:CippMcpSpec +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolList.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolList.ps1 new file mode 100644 index 000000000000..a102a82e340b --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolList.ps1 @@ -0,0 +1,149 @@ +function Get-CippMcpToolList { + <# + .SYNOPSIS + Projects the CIPP OpenAPI spec into the read-only MCP tool list. + .DESCRIPTION + Returns every operation whose x-cipp-role ends in '.Read' (never '.ReadWrite') as an + MCP tool definition: name (the API endpoint), description, inputSchema (JSON Schema + built from the operation's query parameters / request body with $ref inlined), and + read-only annotations. Cached per worker; pass -Force to rebuild. Not an entrypoint. + The spec is consumed as nested hashtables (Get-CippMcpSpec uses -AsHashtable). + .FUNCTIONALITY + Internal + #> + [CmdletBinding()] + param([switch]$Force) + + if ($script:CippMcpToolListCache -and -not $Force) { + return $script:CippMcpToolListCache + } + + $Spec = Get-CippMcpSpec + $Tools = [System.Collections.Generic.List[object]]::new() + + foreach ($PathEntry in $Spec['paths'].GetEnumerator()) { + $Endpoint = $PathEntry.Key -replace '^/api/', '' + + # Never expose the MCP transport itself as a tool. + if ($Endpoint -eq 'ExecMcp') { continue } + + foreach ($MethodEntry in $PathEntry.Value.GetEnumerator()) { + $Method = [string]$MethodEntry.Key + if ($Method -notin @('get', 'post')) { continue } + + $Op = $MethodEntry.Value + $Role = $Op['x-cipp-role'] + + # Read-only surface only. + if (-not $Role -or $Role -notmatch '\.Read$') { continue } + + # Defensive backstop: never expose an endpoint whose name implies a mutation, + # even if its x-cipp-role is mislabeled '.Read' (e.g. AddTestReport, EditIntunePolicy). + if ($Endpoint -match '^(Add|Set|Remove|Delete|Edit|New|Update|Disable|Enable|Reset|Revoke|Push|Clear|Start|Stop|Rename|Move|Copy)') { continue } + + $Properties = [ordered]@{} + $RequiredList = [System.Collections.Generic.List[string]]::new() + + # Query / path parameters. + foreach ($ParamRaw in @($Op['parameters'])) { + if (-not $ParamRaw) { continue } + $Param = Resolve-CippMcpNode -Node $ParamRaw -Spec $Spec + if ($Param['in'] -notin @('query', 'path')) { continue } + $Schema = if ($Param['schema']) { $Param['schema'] } else { @{ type = 'string' } } + $Properties[[string]$Param['name']] = $Schema + if ($Param['required']) { $RequiredList.Add([string]$Param['name']) } + } + + # Request body (uncommon for reads; included for completeness). + if ($Op['requestBody'] -and $Op['requestBody']['content'] -and $Op['requestBody']['content']['application/json']) { + $BodySchema = Resolve-CippMcpNode -Node $Op['requestBody']['content']['application/json']['schema'] -Spec $Spec + if ($BodySchema -and $BodySchema['properties']) { + foreach ($BodyProp in $BodySchema['properties'].GetEnumerator()) { + $Properties[[string]$BodyProp.Key] = $BodyProp.Value + } + foreach ($Req in @($BodySchema['required'])) { if ($Req) { $RequiredList.Add([string]$Req) } } + } + } + + $InputSchema = [ordered]@{ + type = 'object' + properties = $Properties + } + if ($RequiredList.Count -gt 0) { + $InputSchema['required'] = @($RequiredList | Select-Object -Unique) + } + + $Tools.Add([ordered]@{ + name = $Endpoint + description = Get-CippMcpDescription -Operation $Op + inputSchema = $InputSchema + annotations = [ordered]@{ title = $Endpoint; readOnlyHint = $true } + }) + } + } + + $script:CippMcpToolListCache = $Tools + return $Tools +} + +function Resolve-CippMcpNode { + # Deep-resolves a parsed OpenAPI node (hashtable/array/scalar), inlining any $ref. Internal helper. + param($Node, $Spec, [int]$Depth = 0, [string[]]$Seen = @()) + + if ($null -eq $Node) { return $null } + if ($Depth -gt 15) { return @{ type = 'object' } } + if ($Node -is [string] -or $Node -is [valuetype]) { return $Node } + + if ($Node -is [System.Collections.IDictionary]) { + if ($Node.Contains('$ref')) { + $Ref = [string]$Node['$ref'] + if ($Seen -contains $Ref) { return [ordered]@{ type = 'object'; description = 'recursive reference omitted' } } + $Target = Resolve-CippMcpRef -Ref $Ref -Spec $Spec + return Resolve-CippMcpNode -Node $Target -Spec $Spec -Depth ($Depth + 1) -Seen ($Seen + $Ref) + } + $Out = [ordered]@{} + foreach ($Entry in $Node.GetEnumerator()) { + if ($Entry.Key -eq '$ref') { continue } + $Out[[string]$Entry.Key] = Resolve-CippMcpNode -Node $Entry.Value -Spec $Spec -Depth ($Depth + 1) -Seen $Seen + } + return $Out + } + + if ($Node -is [System.Collections.IEnumerable]) { + return @($Node | ForEach-Object { Resolve-CippMcpNode -Node $_ -Spec $Spec -Depth ($Depth + 1) -Seen $Seen }) + } + + return $Node +} + +function Resolve-CippMcpRef { + # Resolves a JSON pointer like '#/components/parameters/tenantFilter' against the spec. Internal helper. + param([string]$Ref, $Spec) + + $Segments = $Ref.TrimStart('#') -split '/' | Where-Object { $_ -ne '' } + $Node = $Spec + foreach ($Seg in $Segments) { + $Key = $Seg -replace '~1', '/' -replace '~0', '~' + if ($Node -is [System.Collections.IDictionary] -and $Node.Contains($Key)) { + $Node = $Node[$Key] + } else { + return $null + } + } + return $Node +} + +function Get-CippMcpDescription { + # Cleans the operation description (strips leaked PowerShell help) and prefixes the tag. Internal helper. + param($Operation) + + $Desc = [string]$Operation['description'] + $Desc = $Desc -replace '(?s)\s*#>.*$', '' + $Desc = $Desc -replace '(?s)\[CmdletBinding.*$', '' + $Desc = $Desc.Trim() + if ([string]::IsNullOrWhiteSpace($Desc)) { $Desc = [string]$Operation['summary'] } + + $Tag = @($Operation['tags'])[0] + if ($Tag -and $Tag -ne 'Uncategorized') { $Desc = "[$Tag] $Desc" } + return $Desc +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolResult.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolResult.ps1 new file mode 100644 index 000000000000..073846f871f0 --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Get-CippMcpToolResult.ps1 @@ -0,0 +1,84 @@ +function Get-CippMcpToolResult { + <# + .SYNOPSIS + Executes a single MCP 'tools/call' by re-dispatching it through the CIPP API router. + .DESCRIPTION + Validates the requested tool against the read-only MCP tool list, then invokes the + corresponding /api endpoint via New-CippCoreRequest using the caller's own principal + headers. This guarantees Test-CIPPAccess (RBAC + tenant scoping + logging) runs for + every tool call exactly as for a normal API request. The synthetic request is tagged + with 'X-CIPP-Origin: mcp' so model-initiated calls are distinguishable in logs. + Returns an MCP tool result object ({ content, isError }). Not an HTTP entrypoint. + .FUNCTIONALITY + Internal + #> + [CmdletBinding()] + param( + $Request, + $TriggerMetadata, + [string]$ToolName, + $Arguments + ) + + if ([string]::IsNullOrWhiteSpace($ToolName)) { + throw [pscustomobject]@{ code = -32602; message = 'Invalid params: tool name is required' } + } + + # The tool list is the read-only allowlist; anything not in it cannot be called. + $Tool = Get-CippMcpToolList | Where-Object { $_.name -eq $ToolName } | Select-Object -First 1 + if (-not $Tool) { + throw [pscustomobject]@{ code = -32602; message = "Unknown or unavailable tool: $ToolName" } + } + + # Determine the HTTP method from the spec (defaults to GET for the read surface). + $Spec = Get-CippMcpSpec + $PathItem = $Spec['paths']["/api/$ToolName"] + $Method = if ($PathItem -and $PathItem.Contains('post')) { 'POST' } else { 'GET' } + + # Flatten the MCP arguments object into a parameter hashtable. + $ArgHash = @{} + if ($Arguments) { + foreach ($Prop in $Arguments.PSObject.Properties) { + $ArgHash[$Prop.Name] = $Prop.Value + } + } + + # Clone caller headers (preserves the EasyAuth principal) and tag the origin for auditing. + $Headers = @{} + if ($Request.Headers) { + foreach ($Header in $Request.Headers.PSObject.Properties) { + $Headers[$Header.Name] = $Header.Value + } + } + $Headers['X-CIPP-Origin'] = 'mcp' + + $Query = @{} + $Body = @{} + if ($Method -eq 'GET') { $Query = $ArgHash } else { $Body = $ArgHash } + + $InnerRequest = [pscustomobject]@{ + Params = @{ CIPPEndpoint = $ToolName } + Method = $Method + Headers = $Headers + Query = $Query + Body = $Body + } + + try { + $Response = New-CippCoreRequest -Request $InnerRequest -TriggerMetadata $TriggerMetadata + } catch { + return [ordered]@{ + content = @(@{ type = 'text'; text = "Tool execution failed: $($_.Exception.Message)" }) + isError = $true + } + } + + $ResultBody = $Response.Body + $Text = if ($ResultBody -is [string]) { $ResultBody } else { $ResultBody | ConvertTo-Json -Depth 20 -Compress } + $IsError = $null -ne $Response.StatusCode -and [int]$Response.StatusCode -ge 400 + + return [ordered]@{ + content = @(@{ type = 'text'; text = "$Text" }) + isError = $IsError + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Invoke-ExecMcp.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Invoke-ExecMcp.ps1 new file mode 100644 index 000000000000..19514f9db6fa --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/MCP/Invoke-ExecMcp.ps1 @@ -0,0 +1,74 @@ +function Invoke-ExecMcp { + <# + .SYNOPSIS + Model Context Protocol (MCP) server endpoint for CIPP. + .DESCRIPTION + A Streamable-HTTP MCP server running in JSON response mode (no SSE). It exposes + CIPP's read-only API surface as MCP tools, projected at runtime from openapi.json. + + Every 'tools/call' is re-dispatched through New-CippCoreRequest using the caller's + own principal headers, so the standard RBAC and tenant scoping in Test-CIPPAccess + is enforced for each tool exactly as it would be for a normal API request. This + endpoint's own role (CIPP.Core.Read) is only the floor required to use MCP at all. + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Core.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + # v1 supports HTTP POST (JSON response mode) only. SSE/GET streaming is not enabled. + if ($Request.Method -and $Request.Method -ne 'POST') { + return ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::MethodNotAllowed + Headers = @{ 'Content-Type' = 'application/json'; 'Allow' = 'POST' } + Body = (@{ jsonrpc = '2.0'; id = $null; error = @{ code = -32600; message = 'Only HTTP POST (JSON mode) is supported.' } } | ConvertTo-Json -Compress) + }) + } + + $Rpc = $Request.Body + $RpcId = $Rpc.id + + # JSON-RPC notifications carry no id and receive no response body. + if ($null -eq $RpcId) { + return ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::Accepted }) + } + + try { + if (-not $Rpc.method) { + throw [pscustomobject]@{ code = -32600; message = 'Invalid Request: missing method' } + } + + switch ($Rpc.method) { + 'initialize' { + $Result = [ordered]@{ + protocolVersion = $Rpc.params.protocolVersion ?? '2025-06-18' + capabilities = @{ tools = @{ listChanged = $false } } + serverInfo = [ordered]@{ + name = 'CIPP' + version = $Request.Headers.'X-CIPP-Version' ?? 'unknown' + } + } + } + 'ping' { $Result = @{} } + 'tools/list' { $Result = [ordered]@{ tools = @(Get-CippMcpToolList) } } + 'tools/call' { + $Result = Get-CippMcpToolResult -Request $Request -TriggerMetadata $TriggerMetadata -ToolName $Rpc.params.name -Arguments $Rpc.params.arguments + } + default { throw [pscustomobject]@{ code = -32601; message = "Method not found: $($Rpc.method)" } } + } + + $ResponseBody = [ordered]@{ jsonrpc = '2.0'; id = $RpcId; result = $Result } + } catch { + $Code = $_.TargetObject.code ?? -32603 + $Message = $_.TargetObject.message ?? $_.Exception.Message + $ResponseBody = [ordered]@{ jsonrpc = '2.0'; id = $RpcId; error = [ordered]@{ code = $Code; message = "$Message" } } + } + + return ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Headers = @{ 'Content-Type' = 'application/json' } + Body = ($ResponseBody | ConvertTo-Json -Depth 30 -Compress) + }) +} From 26a87b279488aa2375d055e1aefbf5c20f33004a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 1 Jun 2026 00:49:42 +0200 Subject: [PATCH 82/90] add-member force --- .../Public/Authentication/Set-CippApiAuth.ps1 | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 index 1e1dc84a1fb6..74fa1ce71e0b 100644 --- a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 @@ -41,7 +41,7 @@ function Set-CippApiAuth { # The env var has the raw config (identityProviders at top level, no properties wrapper) # Safely navigate/create the full path — any level may be null if (-not $Current.ContainsKey('identityProviders') -or $null -eq $Current.identityProviders) { $Current.identityProviders = @{} } - if (-not $Current.identityProviders.ContainsKey('azureActiveDirectory') -or $null -eq $Current.identityProviders.azureActiveDirectory) { $Current.identityProviders.azureActiveDirectory = @{} } + if (-not $Current.identityProviders.ContainsKey('azureActiveDirectory') -or $null -eq $Current.identityProviders.azureActiveDirectory) { $Current.identityProviders | Add-Member -MemberType NoteProperty -Name 'azureActiveDirectory' -Value @{} -Force } $AAD = $Current.identityProviders.azureActiveDirectory Write-Information "[ApiAuth] AAD keys: $($AAD.Keys -join ', ')" @@ -89,7 +89,7 @@ function Set-CippApiAuth { $PutUri = "$BaseUri/config/authsettingsV2?api-version=2020-06-01" $PutResult = New-CIPPAzRestRequest -Uri $PutUri -Method PUT -Body $PutBody -ContentType 'application/json' -ErrorAction Stop Write-Information "[ApiAuth] PUT result: $($PutResult | ConvertTo-Json -Depth 10 -Compress)" - Write-Information "[ApiAuth] Updated EasyAuth successfully" + Write-Information '[ApiAuth] Updated EasyAuth successfully' } } else { # Full overwrite path (no SSO EasyAuth config to preserve) @@ -106,7 +106,7 @@ function Set-CippApiAuth { if (!$ClientIds) { $ClientIds = @() } if (($ClientIds | Measure-Object).Count -gt 0) { - $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ + $AuthSettings.properties.identityProviders | Add-Member -MemberType NoteProperty -Name 'azureActiveDirectory' -Value @{ enabled = $true registration = @{ clientId = $ClientIds[0] ?? $ClientIds @@ -118,13 +118,14 @@ function Set-CippApiAuth { allowedApplications = @($ClientIds) } } - } + } -Force } else { - $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ + #Replaced with add-member -force + $AuthSettings.properties.identityProviders | Add-Member -MemberType NoteProperty -Name 'azureActiveDirectory' -Value @{ enabled = $false registration = @{} validation = @{} - } + } -Force } $AuthSettings.properties.globalValidation = @{ From 2a2851b57a7a19f2105f2d892cab3c1b94df953d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 1 Jun 2026 00:58:30 +0200 Subject: [PATCH 83/90] sso auth --- .../Authentication/Set-CIPPSSOEasyAuth.ps1 | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 index 5ca8abadae37..8ced685da842 100644 --- a/Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 @@ -95,7 +95,7 @@ function Set-CIPPSSOEasyAuth { # Safely navigate to AAD registration if (-not $Current.ContainsKey('identityProviders') -or $null -eq $Current.identityProviders) { $Current.identityProviders = @{} } - if (-not $Current.identityProviders.ContainsKey('azureActiveDirectory') -or $null -eq $Current.identityProviders.azureActiveDirectory) { $Current.identityProviders.azureActiveDirectory = @{} } + if (-not $Current.identityProviders.ContainsKey('azureActiveDirectory') -or $null -eq $Current.identityProviders.azureActiveDirectory) { $Current.identityProviders | Add-Member -MemberType NoteProperty -Name 'azureActiveDirectory' -Value @{} -Force } $AAD = $Current.identityProviders.azureActiveDirectory if (-not $AAD.ContainsKey('registration') -or $null -eq $AAD.registration) { $AAD.registration = @{} } @@ -131,10 +131,10 @@ function Set-CIPPSSOEasyAuth { # Full overwrite: initial setup — build the entire authsettingsV2 from scratch $AuthConfig = @{ properties = @{ - platform = @{ enabled = $true } - globalValidation = @{ + platform = @{ enabled = $true } + globalValidation = @{ unauthenticatedClientAction = 'RedirectToLoginPage' - redirectToProvider = 'azureactivedirectory' + redirectToProvider = 'azureactivedirectory' excludedPaths = @( '/api/Public*' '/api/setup/health' @@ -144,17 +144,17 @@ function Set-CIPPSSOEasyAuth { azureActiveDirectory = @{ enabled = $true registration = $(if ($ImplicitAuth) { - @{ - clientId = $AppId - openIdIssuer = $IssuerUrl - } - } else { - @{ - clientId = $AppId - clientSecretSettingName = 'AUTH_SECRET' - openIdIssuer = $IssuerUrl - } - }) + @{ + clientId = $AppId + openIdIssuer = $IssuerUrl + } + } else { + @{ + clientId = $AppId + clientSecretSettingName = 'AUTH_SECRET' + openIdIssuer = $IssuerUrl + } + }) validation = @{ allowedAudiences = @("api://$AppId") defaultAuthorizationPolicy = @{ @@ -164,7 +164,7 @@ function Set-CIPPSSOEasyAuth { } } } - login = @{ + login = @{ tokenStore = @{ enabled = $true tokenRefreshExtensionHours = 72 From acf4bf337f4545c5fc5fce2b93c540023c100c91 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 1 Jun 2026 01:05:53 +0200 Subject: [PATCH 84/90] Add or update the Azure App Service build and deployment workflow config --- .github/workflows/dev_cippjta72.yml | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/dev_cippjta72.yml diff --git a/.github/workflows/dev_cippjta72.yml b/.github/workflows/dev_cippjta72.yml new file mode 100644 index 000000000000..4f143bb4b2df --- /dev/null +++ b/.github/workflows/dev_cippjta72.yml @@ -0,0 +1,32 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Powershell project to Azure Function App - cippjta72 + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'cippjta72' + slot-name: 'Production' + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_1EBE9D73F9EC4528BA666FC934055536 }} + sku: 'flexconsumption' + \ No newline at end of file From 72f7882fe5c042f4e05353e8fe27a10c6d4a5456 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 1 Jun 2026 01:16:02 +0200 Subject: [PATCH 85/90] fixes another add member --- .../CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 index 74fa1ce71e0b..5b2c57c2c853 100644 --- a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 @@ -128,15 +128,15 @@ function Set-CippApiAuth { } -Force } - $AuthSettings.properties.globalValidation = @{ + $AuthSettings.properties | Add-Member -MemberType NoteProperty -Name 'globalValidation' -Value @{ unauthenticatedClientAction = 'Return401' - } - $AuthSettings.properties.login = @{ + } -Force + $AuthSettings.properties | Add-Member -MemberType NoteProperty -Name 'login' -Value @{ tokenStore = @{ enabled = $true tokenRefreshExtensionHours = 72 } - } + } -Force if ($PSCmdlet.ShouldProcess('Update auth settings')) { $putUri = "$BaseUri/config/authsettingsV2?api-version=2020-06-01" From 54a3a0821de35a718357f26c98be908f3bbf1a7b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:39:14 +0800 Subject: [PATCH 86/90] api auth save and get changes --- .../Public/Authentication/Get-CippApiAuth.ps1 | 89 ++++++++++++------- .../Public/Authentication/Set-CippApiAuth.ps1 | 42 +++++---- 2 files changed, 80 insertions(+), 51 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 index faea057c4fa1..a943bc149716 100644 --- a/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 @@ -4,53 +4,74 @@ function Get-CippApiAuth { [string]$FunctionAppName ) - $AuthSettings = $null + if ($env:CIPPNG) { + $AuthSettings = $null - # When the auth config is available as an env var, use it directly (no ARM call needed) - if ($env:CIPPNG -and $env:WEBSITE_AUTH_V2_CONFIG_JSON) { - $AuthSettings = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -ErrorAction SilentlyContinue - } + # When the auth config is available as an env var, use it directly (no ARM call needed) + if ($env:WEBSITE_AUTH_V2_CONFIG_JSON) { + $AuthSettings = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -ErrorAction SilentlyContinue + } + + # Fall back to reading via ARM REST + if (-not $AuthSettings) { + $SubscriptionId = Get-CIPPAzFunctionAppSubId + try { + $uri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2/list?api-version=2020-06-01" + $response = New-CIPPAzRestRequest -Uri $uri -Method POST -ErrorAction Stop + $AuthSettings = $response.properties + } catch { + Write-Warning "Failed to get auth settings via REST: $($_.Exception.Message)" + } + } + + if ($AuthSettings) { + $AAD = $AuthSettings.identityProviders.azureActiveDirectory + $Issuer = $AAD.registration.openIdIssuer ?? '' + $AllowedApps = @($AAD.validation.defaultAuthorizationPolicy.allowedApplications) + + # When SSO EasyAuth is in use, filter out its clientId — the frontend only tracks API clients + $SSOClientId = $AAD.registration.clientId + if ($SSOClientId) { + $AllowedApps = @($AllowedApps | Where-Object { $_ -ne $SSOClientId }) + } - # Fall back to reading via ARM REST - if (-not $AuthSettings) { + $ExtractedTenantId = $Issuer -replace 'https://sts.windows.net/', '' -replace 'https://login.microsoftonline.com/', '' -replace '/v2.0', '' + $TenantId = if ($ExtractedTenantId -eq 'common') { $env:TenantID } else { $ExtractedTenantId } + + [PSCustomObject]@{ + ApiUrl = "https://$($env:WEBSITE_HOSTNAME)" + TenantID = $TenantId + ClientIDs = $AllowedApps + Enabled = $AAD.enabled + } + } else { + throw 'No auth settings found' + } + } else { $SubscriptionId = Get-CIPPAzFunctionAppSubId + try { + # Get auth settings via REST $uri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2/list?api-version=2020-06-01" $response = New-CIPPAzRestRequest -Uri $uri -Method POST -ErrorAction Stop $AuthSettings = $response.properties } catch { Write-Warning "Failed to get auth settings via REST: $($_.Exception.Message)" } - } - # Fallback to env var if ARM failed - if (-not $AuthSettings -and $env:WEBSITE_AUTH_V2_CONFIG_JSON) { - $AuthSettings = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -ErrorAction SilentlyContinue - } - - if ($AuthSettings) { - $AAD = $AuthSettings.identityProviders.azureActiveDirectory - $Issuer = $AAD.registration.openIdIssuer ?? '' - $AllowedApps = @($AAD.validation.defaultAuthorizationPolicy.allowedApplications) - - # When SSO EasyAuth is in use, filter out its clientId — the frontend only tracks API clients - if ($env:CIPPNG) { - $SSOClientId = $AAD.registration.clientId - if ($SSOClientId) { - $AllowedApps = @($AllowedApps | Where-Object { $_ -ne $SSOClientId }) - } + if (!$AuthSettings -and $env:WEBSITE_AUTH_V2_CONFIG_JSON) { + $AuthSettings = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -ErrorAction SilentlyContinue } - $ExtractedTenantId = $Issuer -replace 'https://sts.windows.net/', '' -replace 'https://login.microsoftonline.com/', '' -replace '/v2.0', '' - $TenantId = if ($ExtractedTenantId -eq 'common') { $env:TenantID } else { $ExtractedTenantId } - - [PSCustomObject]@{ - ApiUrl = "https://$($env:WEBSITE_HOSTNAME)" - TenantID = $TenantId - ClientIDs = $AllowedApps - Enabled = $AAD.enabled + if ($AuthSettings) { + [PSCustomObject]@{ + ApiUrl = "https://$($env:WEBSITE_HOSTNAME)" + TenantID = $AuthSettings.identityProviders.azureActiveDirectory.registration.openIdIssuer -replace 'https://sts.windows.net/', '' -replace '/v2.0', '' + ClientIDs = $AuthSettings.identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedApplications + Enabled = $AuthSettings.identityProviders.azureActiveDirectory.enabled + } + } else { + throw 'No auth settings found' } - } else { - throw 'No auth settings found' } } diff --git a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 index 5b2c57c2c853..f9e421b24a31 100644 --- a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 @@ -92,21 +92,30 @@ function Set-CippApiAuth { Write-Information '[ApiAuth] Updated EasyAuth successfully' } } else { - # Full overwrite path (no SSO EasyAuth config to preserve) + # Resolve subscription ID via helper (managed identity environment assumed for ARM). $SubscriptionId = Get-CIPPAzFunctionAppSubId - $BaseUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$FunctionAppName" - $getUri = "$BaseUri/config/authsettingsV2/list?api-version=2020-06-01" - $AuthSettings = New-CIPPAzRestRequest -Uri $getUri -Method POST + # Get auth settings via ARM REST (managed identity) + $getUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2/list?api-version=2020-06-01" + $resp = New-CIPPAzRestRequest -Uri $getUri -Method 'GET' + $AuthSettings = $resp | Select-Object -ExpandProperty Content -ErrorAction SilentlyContinue + if ($AuthSettings -is [string]) { $AuthSettings = $AuthSettings | ConvertFrom-Json } + else { $AuthSettings = $resp } Write-Information "AuthSettings: $($AuthSettings | ConvertTo-Json -Depth 10)" - $AllowedAudiences = foreach ($ClientId in $ClientIds) { "api://$ClientId" } + # Set allowed audiences + $AllowedAudiences = foreach ($ClientId in $ClientIds) { + "api://$ClientId" + } + if (!$AllowedAudiences) { $AllowedAudiences = @() } if (!$ClientIds) { $ClientIds = @() } + # Set auth settings + if (($ClientIds | Measure-Object).Count -gt 0) { - $AuthSettings.properties.identityProviders | Add-Member -MemberType NoteProperty -Name 'azureActiveDirectory' -Value @{ + $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ enabled = $true registration = @{ clientId = $ClientIds[0] ?? $ClientIds @@ -118,30 +127,29 @@ function Set-CippApiAuth { allowedApplications = @($ClientIds) } } - } -Force + } } else { - #Replaced with add-member -force - $AuthSettings.properties.identityProviders | Add-Member -MemberType NoteProperty -Name 'azureActiveDirectory' -Value @{ + $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ enabled = $false registration = @{} validation = @{} - } -Force + } } - $AuthSettings.properties | Add-Member -MemberType NoteProperty -Name 'globalValidation' -Value @{ + $AuthSettings.properties.globalValidation = @{ unauthenticatedClientAction = 'Return401' - } -Force - $AuthSettings.properties | Add-Member -MemberType NoteProperty -Name 'login' -Value @{ + } + $AuthSettings.properties.login = @{ tokenStore = @{ enabled = $true tokenRefreshExtensionHours = 72 } - } -Force + } if ($PSCmdlet.ShouldProcess('Update auth settings')) { - $putUri = "$BaseUri/config/authsettingsV2?api-version=2020-06-01" - $Body = $AuthSettings | ConvertTo-Json -Depth 20 - $null = New-CIPPAzRestRequest -Uri $putUri -Method PUT -Body $Body -ContentType 'application/json' + # Update auth settings via ARM REST + $putUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$RGName/providers/Microsoft.Web/sites/$($FunctionAppName)/config/authsettingsV2?api-version=2020-06-01" + $null = New-CIPPAzRestRequest -Uri $putUri -Method 'PUT' -Body $AuthSettings -ContentType 'application/json' } if ($PSCmdlet.ShouldProcess('Update allowed tenants')) { From c16557ff6587406decf70f6d47d7fe3014da473b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:26:01 +0800 Subject: [PATCH 87/90] Guarding for cache collection items --- .../DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 | 16 +++++++++++++--- .../Set-CIPPDBCacheSharePointSiteUsage.ps1 | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 index 2000025ebd5d..d0f3cc2c58f1 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheOneDriveUsage.ps1 @@ -34,11 +34,21 @@ function Set-CIPPDBCacheOneDriveUsage { $Result = New-GraphBulkRequest -tenantid $TenantFilter -Requests @($BulkRequests) -asapp $true $Sites = @(($Result | Where-Object { $_.id -eq 'listAllSites' }).body.value) - $UsageBase64 = ($Result | Where-Object { $_.id -eq 'usage' }).body - $UsageJson = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($UsageBase64)) - $OneDriveUsage = @(($UsageJson | ConvertFrom-Json).value) + + $UsageResponse = $Result | Where-Object { $_.id -eq 'usage' } + if ($UsageResponse.status -and $UsageResponse.status -ne 200) { + throw ($UsageResponse.body.error.message ?? "Usage report request failed with status $($UsageResponse.status)") + } + $UsageBody = $UsageResponse.body + if ($UsageBody -is [string]) { + $UsageJson = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($UsageBody)) + $OneDriveUsage = @(($UsageJson | ConvertFrom-Json).value) + } else { + $OneDriveUsage = @($UsageBody.value) + } foreach ($UsageRow in $OneDriveUsage) { + if ($null -eq $UsageRow) { continue } $UsageRow | Add-Member -NotePropertyName 'id' -NotePropertyValue $UsageRow.siteId -Force $UsageRow | Add-Member -NotePropertyName 'userPrincipalName' -NotePropertyValue $UsageRow.ownerPrincipalName -Force } diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 index 561a01721236..71f072a4eb74 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 @@ -51,6 +51,7 @@ function Set-CIPPDBCacheSharePointSiteUsage { # Ensure a stable row key for usage rows. foreach ($UsageRow in $UsageRows) { + if ($null -eq $UsageRow) { continue } $UsageRow | Add-Member -NotePropertyName 'id' -NotePropertyValue $UsageRow.siteId -Force } From 4b4a2d1d7e97aa30525e84a11ef3fc8c89d9dae9 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 1 Jun 2026 23:01:23 +0800 Subject: [PATCH 88/90] Update Set-CIPPDBCacheSharePointSiteUsage.ps1 --- .../Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 index 71f072a4eb74..8b7bb765b71b 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 @@ -100,4 +100,4 @@ function Set-CIPPDBCacheSharePointSiteUsage { } catch { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to cache SharePoint site usage: $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_) } -} \ No newline at end of file +} From f3393cb9b163ff916b8882dbaaaaf981d3ae80c8 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 2 Jun 2026 00:09:02 +0800 Subject: [PATCH 89/90] Update Invoke-ExecUniversalSearchV2.ps1 --- .../Invoke-ExecUniversalSearchV2.ps1 | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 index 8c3b9d954fb1..c133159efb85 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 @@ -31,6 +31,60 @@ function Invoke-ExecUniversalSearchV2 { 'Applications' { $Results = Search-CIPPDbData -SearchTerms $SearchTerms -Types 'Apps', 'ServicePrincipals' -Limit $Limit -Properties 'id', 'appId', 'displayName', 'publisherName', 'appOwnerOrganizationId' -TenantFilter $TenantFilter } + 'Licenses' { + # SKU lookup is universal — always search across all tenants regardless of caller scope. + # No Properties filter so service plan names / friendly names embedded in the JSON + # still pass the secondary verification pass. + $Raw = Search-CIPPDbData -SearchTerms $SearchTerms -Types 'LicenseOverview' -TenantFilter 'allTenants' + + $BySku = [ordered]@{} + foreach ($Row in $Raw) { + $Data = $Row.Data + if (-not $Data -or [string]::IsNullOrWhiteSpace($Data.skuId)) { continue } + $Key = ([string]$Data.skuId).ToLowerInvariant() + + if (-not $BySku.Contains($Key)) { + $BySku[$Key] = [PSCustomObject]@{ + skuId = [string]$Data.skuId + skuPartNumber = [string]$Data.skuPartNumber + displayName = [string]$Data.License + servicePlans = @($Data.ServicePlans) + tenantCount = 0 + totalAssigned = 0 + totalAvailable = 0 + tenants = [System.Collections.Generic.List[object]]::new() + } + } + + $Entry = $BySku[$Key] + if ([string]::IsNullOrWhiteSpace($Entry.skuPartNumber) -and $Data.skuPartNumber) { $Entry.skuPartNumber = [string]$Data.skuPartNumber } + if ([string]::IsNullOrWhiteSpace($Entry.displayName) -and $Data.License) { $Entry.displayName = [string]$Data.License } + if ((-not $Entry.servicePlans -or $Entry.servicePlans.Count -eq 0) -and $Data.ServicePlans) { $Entry.servicePlans = @($Data.ServicePlans) } + + $Entry.tenantCount++ + $Used = 0; [int]::TryParse([string]$Data.CountUsed, [ref]$Used) | Out-Null + $Total = 0; [int]::TryParse([string]$Data.TotalLicenses, [ref]$Total) | Out-Null + $Entry.totalAssigned += $Used + $Entry.totalAvailable += $Total + $Entry.tenants.Add([PSCustomObject]@{ + tenant = [string]$Row.Tenant + used = $Used + total = $Total + }) + } + + $Aggregated = $BySku.Values | Sort-Object -Property tenantCount -Descending | Select-Object -First $Limit + + # Shape into the same envelope as other types so the frontend can use match.Data + $Results = foreach ($Item in $Aggregated) { + [PSCustomObject]@{ + Tenant = '' + Type = 'Licenses' + RowKey = "Licenses-$($Item.skuId)" + Data = $Item + } + } + } default { $Results = Search-CIPPDbData -SearchTerms $SearchTerms -Types 'Users' -Limit $Limit -Properties 'id', 'userPrincipalName', 'displayName' -TenantFilter $TenantFilter } From 9d8f1a0dafc1dff686e1501e0bdf9e493b3dea3b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 2 Jun 2026 00:12:25 +0800 Subject: [PATCH 90/90] correct incorrect pathing --- Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1 b/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1 index dc9298da4356..77518b24d11d 100644 --- a/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1 @@ -45,15 +45,15 @@ function Add-CIPPW32ScriptApplication { ) # Get the standard Chocolatey package location (relative to function app root) - $IntuneWinFile = 'AddChocoApp\IntunePackage.intunewin' - $ChocoXmlFile = 'AddChocoApp\Choco.App.xml' + $IntuneWinFile = Join-Path $env:CIPPRootPath 'AddChocoApp\IntunePackage.intunewin' + $ChocoXmlFile = Join-Path $env:CIPPRootPath 'AddChocoApp\Choco.App.xml' if (-not (Test-Path $IntuneWinFile)) { - throw "Chocolatey IntunePackage.intunewin not found at: $IntuneWinFile (Current directory: $PWD)" + throw "Chocolatey IntunePackage.intunewin not found at: $IntuneWinFile (CIPPRootPath: $env:CIPPRootPath)" } if (-not (Test-Path $ChocoXmlFile)) { - throw "Choco.App.xml not found at: $ChocoXmlFile (Current directory: $PWD)" + throw "Choco.App.xml not found at: $ChocoXmlFile (CIPPRootPath: $env:CIPPRootPath)" } # Parse the Choco XML to get encryption info. We need a wrapper around the application and this is a tiny intune file, perfect for our purpose.