From 29685d69b00fcc1cb774ae60600cd7607d2520b1 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Mon, 4 May 2026 22:54:54 +0200 Subject: [PATCH 01/28] feat(api): add license capability presets Centralize common Test-CIPPStandardLicense capability sets behind presets and migrate exact-match call sites to use them. --- .github/agents/CIPP-Alert-Agent.md | 13 +- .github/agents/CIPP-Standards-Agent.md | 1 + .github/instructions/alerts.instructions.md | 7 +- .github/instructions/cippdb.instructions.md | 4 +- .../instructions/standards.instructions.md | 8 +- .../Push-CIPPDBCacheData.ps1 | 14 +- .../Standards/Push-CIPPStandardsList.ps1 | 4 +- .../Get-CIPPAlertIntunePolicyConflicts.ps1 | 19 +-- ...Get-CIPPAlertQuarantineReleaseRequests.ps1 | 8 +- .../Functions/Test-CIPPStandardLicense.ps1 | 39 ++++- Modules/CIPPCore/Public/Get-CIPPDrift.ps1 | 4 +- .../CIPPCore/Public/Test-CIPPAccessTenant.ps1 | 2 +- ...t-CIPPDBCacheConditionalAccessPolicies.ps1 | 2 +- .../Set-CIPPDBCacheDlpCompliancePolicies.ps1 | 2 +- .../DBCache/Set-CIPPDBCacheIntunePolicies.ps1 | 2 +- .../DBCache/Set-CIPPDBCachePIMSettings.ps1 | 2 +- .../Set-CIPPDBCacheSensitivityLabels.ps1 | 2 +- .../Public/DBCache/Set-CIPPDBCacheUsers.ps1 | 2 +- .../Standards/Invoke-CIPPStandardAddDKIM.ps1 | 2 +- .../Invoke-CIPPStandardAntiPhishPolicy.ps1 | 2 +- .../Invoke-CIPPStandardAntiSpamSafeList.ps1 | 2 +- ...e-CIPPStandardAssignmentFilterTemplate.ps1 | 2 +- .../Invoke-CIPPStandardAtpPolicyForO365.ps1 | 2 +- .../Standards/Invoke-CIPPStandardAuditLog.ps1 | 2 +- .../Invoke-CIPPStandardAutoArchive.ps1 | 2 +- .../Invoke-CIPPStandardAutoArchiveMailbox.ps1 | 2 +- .../Invoke-CIPPStandardAutoExpandArchive.ps1 | 2 +- .../Invoke-CIPPStandardAutopilotProfile.ps1 | 2 +- ...Invoke-CIPPStandardAutopilotStatusPage.ps1 | 2 +- .../Standards/Invoke-CIPPStandardBookings.ps1 | 2 +- .../Standards/Invoke-CIPPStandardBranding.ps1 | 2 +- .../Invoke-CIPPStandardCloudMessageRecall.ps1 | 2 +- ...IPPStandardColleagueImpersonationAlert.ps1 | 2 +- ...-CIPPStandardConditionalAccessTemplate.ps1 | 6 +- ...e-CIPPStandardCustomBannedPasswordList.ps1 | 2 +- ...IPPStandardDefaultPlatformRestrictions.ps1 | 2 +- .../Invoke-CIPPStandardDefaultSharingLink.ps1 | 2 +- .../Invoke-CIPPStandardDelegateSentItems.ps1 | 2 +- ...voke-CIPPStandardDeletedUserRentention.ps1 | 2 +- ...CIPPStandardDeployCheckChromeExtension.ps1 | 2 +- ...oke-CIPPStandardDeployContactTemplates.ps1 | 2 +- .../Invoke-CIPPStandardDeployMailContact.ps1 | 2 +- ...PStandardDisableAddShortcutsToOneDrive.ps1 | 2 +- ...ndardDisableAdditionalStorageProviders.ps1 | 2 +- ...nvoke-CIPPStandardDisableBasicAuthSMTP.ps1 | 2 +- .../Invoke-CIPPStandardDisableEWS.ps1 | 2 +- ...tandardDisableExchangeOnlinePowerShell.ps1 | 2 +- ...StandardDisableExternalCalendarSharing.ps1 | 2 +- .../Invoke-CIPPStandardDisableGuests.ps1 | 2 +- ...voke-CIPPStandardDisableM365GroupUsers.ps1 | 2 +- ...nvoke-CIPPStandardDisableOutlookAddins.ps1 | 2 +- .../Invoke-CIPPStandardDisableReshare.ps1 | 2 +- ...oke-CIPPStandardDisableResourceMailbox.ps1 | 2 +- ...IPPStandardDisableSharePointLegacyAuth.ps1 | 2 +- .../Invoke-CIPPStandardDisableTNEF.ps1 | 2 +- ...voke-CIPPStandardDisableUserSiteCreate.ps1 | 2 +- ...e-CIPPStandardEXODisableAutoForwarding.ps1 | 2 +- ...voke-CIPPStandardEXOOutboundSpamLimits.ps1 | 2 +- ...PStandardEnableExchangeCloudManagement.ps1 | 2 +- ...nvoke-CIPPStandardEnableLitigationHold.ps1 | 2 +- .../Invoke-CIPPStandardEnableMailTips.ps1 | 2 +- ...voke-CIPPStandardEnableMailboxAuditing.ps1 | 2 +- ...voke-CIPPStandardEnableOnlineArchiving.ps1 | 2 +- ...ntWindowsHelloForBusinessConfiguration.ps1 | 2 +- ...-CIPPStandardExchangeConnectorTemplate.ps1 | 2 +- .../Invoke-CIPPStandardExcludedfileExt.ps1 | 2 +- .../Invoke-CIPPStandardFocusedInbox.ps1 | 2 +- ...PStandardGlobalQuarantineNotifications.ps1 | 2 +- ...e-CIPPStandardGlobalQuarantineSettings.ps1 | 2 +- .../Invoke-CIPPStandardGroupTemplate.ps1 | 4 +- ...e-CIPPStandardIntuneComplianceSettings.ps1 | 2 +- ...ke-CIPPStandardIntuneWindowsDiagnostic.ps1 | 2 +- ...tandardMDMEnrollmentDuringRegistration.ps1 | 2 +- .../Standards/Invoke-CIPPStandardMDMScope.ps1 | 2 +- ...oke-CIPPStandardMailboxRecipientLimits.ps1 | 2 +- ...Invoke-CIPPStandardMalwareFilterPolicy.ps1 | 2 +- .../Invoke-CIPPStandardMessageExpiration.ps1 | 2 +- .../Invoke-CIPPStandardOMEBranding.ps1 | 2 +- ...-CIPPStandardOWAAttachmentRestrictions.ps1 | 2 +- .../Invoke-CIPPStandardOutBoundSpamAlert.ps1 | 2 +- .../Invoke-CIPPStandardPhishProtection.ps1 | 2 +- ...-CIPPStandardPhishSimSpoofIntelligence.ps1 | 2 +- ...Invoke-CIPPStandardPhishingSimulations.ps1 | 2 +- .../Invoke-CIPPStandardProfilePhotos.ps1 | 2 +- ...oke-CIPPStandardQuarantineRequestAlert.ps1 | 2 +- .../Invoke-CIPPStandardQuarantineTemplate.ps1 | 2 +- ...ndardRestrictThirdPartyStorageServices.ps1 | 2 +- .../Invoke-CIPPStandardRetentionPolicyTag.ps1 | 2 +- ...e-CIPPStandardReusableSettingsTemplate.ps1 | 5 +- .../Invoke-CIPPStandardRotateDKIM.ps1 | 2 +- .../Invoke-CIPPStandardSPAzureB2B.ps1 | 2 +- .../Invoke-CIPPStandardSPDirectSharing.ps1 | 2 +- ...oke-CIPPStandardSPDisableCustomScripts.ps1 | 2 +- ...e-CIPPStandardSPDisableLegacyWorkflows.ps1 | 2 +- ...nvoke-CIPPStandardSPDisableStoreAccess.ps1 | 2 +- ...ke-CIPPStandardSPDisallowInfectedFiles.ps1 | 2 +- .../Invoke-CIPPStandardSPEmailAttestation.ps1 | 2 +- ...e-CIPPStandardSPExternalUserExpiration.ps1 | 2 +- .../Invoke-CIPPStandardSPFileRequests.ps1 | 2 +- .../Invoke-CIPPStandardSPSyncButtonState.ps1 | 2 +- ...nvoke-CIPPStandardSafeAttachmentPolicy.ps1 | 2 +- .../Invoke-CIPPStandardSafeLinksPolicy.ps1 | 2 +- ...ke-CIPPStandardSafeLinksTemplatePolicy.ps1 | 2 +- .../Invoke-CIPPStandardSafeSendersDisable.ps1 | 2 +- .../Invoke-CIPPStandardSendFromAlias.ps1 | 2 +- ...oke-CIPPStandardSendReceiveLimitTenant.ps1 | 2 +- .../Invoke-CIPPStandardShortenMeetings.ps1 | 2 +- .../Invoke-CIPPStandardSpamFilterPolicy.ps1 | 2 +- .../Invoke-CIPPStandardSpoofWarn.ps1 | 2 +- .../Invoke-CIPPStandardStaleEntraDevices.ps1 | 2 +- ...Invoke-CIPPStandardTeamsChatProtection.ps1 | 2 +- ...voke-CIPPStandardTeamsEmailIntegration.ps1 | 2 +- .../Invoke-CIPPStandardTeamsEnrollUser.ps1 | 2 +- ...-CIPPStandardTeamsExternalAccessPolicy.ps1 | 2 +- ...IPPStandardTeamsExternalChatWithAnyone.ps1 | 2 +- ...e-CIPPStandardTeamsExternalFileSharing.ps1 | 2 +- ...PPStandardTeamsFederationConfiguration.ps1 | 2 +- ...e-CIPPStandardTeamsGlobalMeetingPolicy.ps1 | 2 +- .../Invoke-CIPPStandardTeamsGuestAccess.ps1 | 2 +- ...tandardTeamsMeetingRecordingExpiration.ps1 | 2 +- ...e-CIPPStandardTeamsMeetingVerification.ps1 | 2 +- ...oke-CIPPStandardTeamsMeetingsByDefault.ps1 | 2 +- ...nvoke-CIPPStandardTeamsMessagingPolicy.ps1 | 2 +- ...PPStandardTenantAllowBlockListTemplate.ps1 | 2 +- ...voke-CIPPStandardTenantDefaultTimezone.ps1 | 2 +- ...voke-CIPPStandardTransportRuleTemplate.ps1 | 2 +- ...ke-CIPPStandardTwoClickEmailProtection.ps1 | 2 +- .../Invoke-CIPPStandardUserSubmissions.ps1 | 2 +- ...nvoke-CIPPStandardWindowsBackupRestore.ps1 | 2 +- .../Invoke-CIPPStandardcalDefault.ps1 | 2 +- .../Invoke-CIPPStandarddisableMacSync.ps1 | 2 +- ...voke-CIPPStandardintuneBrandingProfile.ps1 | 2 +- .../Invoke-CIPPStandardintuneDeviceReg.ps1 | 2 +- ...CIPPStandardintuneDeviceRetirementDays.ps1 | 2 +- .../Invoke-CIPPStandardsharingCapability.ps1 | 2 +- ...e-CIPPStandardsharingDomainRestriction.ps1 | 2 +- .../Invoke-CIPPStandardunmanagedSync.ps1 | 2 +- Tools/Update-StandardsComments.ps1 | 153 ++++++++++++++++-- 138 files changed, 337 insertions(+), 198 deletions(-) diff --git a/.github/agents/CIPP-Alert-Agent.md b/.github/agents/CIPP-Alert-Agent.md index b698cfef4eb5..b2aa3e90c887 100644 --- a/.github/agents/CIPP-Alert-Agent.md +++ b/.github/agents/CIPP-Alert-Agent.md @@ -102,16 +102,13 @@ When adding or modifying alerts: When an alert depends on a tenant having certain SKUs or capabilities, you **must**: -- Use `Test-CIPPStandardLicense` +- Use `Test-CIPPStandardLicense` +- Prefer `-Preset` for common capability sets: `Exchange`, `SharePoint`, `Intune`, `Entra`, `EntraP2`, `Teams`, `Compliance` +- Use `-RequiredCapabilities` only when no preset matches, or combine it with `-Preset` for extra edge-case capabilities - Do **not** manually inspect SKUs, raw license IDs, or raw capability lists. Example pattern (adapt to the specific feature): ```powershell -$TestResult = Test-CIPPStandardLicense -StandardName 'AutopilotProfile' -TenantFilter $Tenant -RequiredCapabilities @( - 'INTUNE_A', - 'MDM_Services', - 'EMS', - 'SCCM', - 'MICROSOFTINTUNEPLAN1' -) +$TestResult = Test-CIPPStandardLicense -StandardName 'AutopilotProfile' -TenantFilter $Tenant -Preset Intune +``` diff --git a/.github/agents/CIPP-Standards-Agent.md b/.github/agents/CIPP-Standards-Agent.md index ed5529cefe5c..506a3ee4edb8 100644 --- a/.github/agents/CIPP-Standards-Agent.md +++ b/.github/agents/CIPP-Standards-Agent.md @@ -82,6 +82,7 @@ When adding or modifying standards: - Similar logging and error handling - Reuse helper functions instead of inlining raw Graph calls or custom HTTP code. - Keep behaviour predictable. +- If a standard needs license gating, use `Test-CIPPStandardLicense` with `-Preset` for common capability sets (`Exchange`, `SharePoint`, `Intune`, `Entra`, `EntraP2`, `Teams`, `Compliance`). Use `-RequiredCapabilities` only when no preset matches, or combine it with `-Preset` for extra edge-case capabilities. ### 2. Return the code for the frontend. diff --git a/.github/instructions/alerts.instructions.md b/.github/instructions/alerts.instructions.md index c5633d083528..a05c31e3721e 100644 --- a/.github/instructions/alerts.instructions.md +++ b/.github/instructions/alerts.instructions.md @@ -97,14 +97,11 @@ if ($InputValue -is [string] -and $InputValue.Trim().StartsWith('{')) { If the alert depends on a specific M365 capability (Intune, Exchange, Defender, etc.), gate it early with `Test-CIPPStandardLicense`. Never inspect raw SKU IDs manually. ```powershell -$Licensed = Test-CIPPStandardLicense -StandardName '' -TenantFilter $TenantFilter -RequiredCapabilities @( - 'INTUNE_A', - 'MDM_Services' -) +$Licensed = Test-CIPPStandardLicense -StandardName '' -TenantFilter $TenantFilter -Preset Intune if (-not $Licensed) { return } ``` -Reference existing alerts in the same domain for common capability strings. The `Test-CIPPStandardLicense` function source documents the capability matching logic. +Use presets for common service families: `Exchange`, `SharePoint`, `Intune`, `Entra`, `EntraP2`, `Teams`, and `Compliance`. Use `-RequiredCapabilities` only when no preset matches, or combine it with `-Preset` when an alert needs a preset plus extra edge-case capabilities. ## Querying data diff --git a/.github/instructions/cippdb.instructions.md b/.github/instructions/cippdb.instructions.md index 19d31e78dcdd..fab2c73c1dad 100644 --- a/.github/instructions/cippdb.instructions.md +++ b/.github/instructions/cippdb.instructions.md @@ -141,7 +141,7 @@ function Set-CIPPDBCacheMyNewType { try { # 1. Optional license check - $Licensed = Test-CIPPStandardLicense -StandardName 'MyFeature' -TenantFilter $TenantFilter -RequiredCapabilities @('REQUIRED_SKU') + $Licensed = Test-CIPPStandardLicense -StandardName 'MyFeature' -TenantFilter $TenantFilter -Preset Intune if (-not $Licensed) { return } # 2. Fetch data from API @@ -160,7 +160,7 @@ function Set-CIPPDBCacheMyNewType { - **Always use `-AddCount`** unless you handle count rows manually - **Pipeline streaming** for large datasets: pipe directly from `New-GraphGetRequest` into `Add-CIPPDbItem` -- **License gating**: use `Test-CIPPStandardLicense` when the API requires specific SKUs +- **License gating**: use `Test-CIPPStandardLicense -Preset ` for common capability sets (`Exchange`, `SharePoint`, `Intune`, `Entra`, `EntraP2`, `Teams`, `Compliance`); use `-RequiredCapabilities` only for non-preset capabilities or additional edge-case capabilities - **Conditional `$select`**: expand Graph `$select` fields based on license capabilities - **Error handling**: catch, log with `Write-LogMessage`, do not rethrow (allows other types in the collection to continue) - **No explicit return** of data — these functions write to the table as a side effect diff --git a/.github/instructions/standards.instructions.md b/.github/instructions/standards.instructions.md index 5897a3bded4d..87c7df972c06 100644 --- a/.github/instructions/standards.instructions.md +++ b/.github/instructions/standards.instructions.md @@ -67,7 +67,7 @@ function Invoke-CIPPStandard { # 1. License gate (if the data source requires a specific SKU) $TestResult = Test-CIPPStandardLicense -StandardName '' -TenantFilter $Tenant ` - -RequiredCapabilities @('CAPABILITY_1', 'CAPABILITY_2') + -Preset Exchange if ($TestResult -eq $false) { return $true } # 2. Get current state @@ -235,13 +235,13 @@ Gate early using `Test-CIPPStandardLicense`. Never inspect raw SKU IDs. ```powershell $TestResult = Test-CIPPStandardLicense -StandardName '' -TenantFilter $Tenant ` - -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE') + -Preset Exchange if ($TestResult -eq $false) { return $true } ``` The function checks tenant capabilities, logs if missing, and automatically sets the `Set-CIPPStandardsCompareField` with `LicenseAvailable = $false`. -Reference existing standards in the same domain for common capability strings. The `Test-CIPPStandardLicense` function source documents the capability matching logic. +Use presets for common service families: `Exchange`, `SharePoint`, `Intune`, `Entra`, `EntraP2`, `Teams`, and `Compliance`. Use `-RequiredCapabilities` only when no preset matches, or combine it with `-Preset` when a standard needs a preset plus extra edge-case capabilities. ## API call patterns @@ -337,7 +337,7 @@ The comment-based help `.NOTES` block drives the frontend UI. Each field maps to | `RECOMMENDEDBY` | `recommendedBy` | `"CIS"`, `"CIPP"`, etc. | | `MULTIPLE` | `multiple` | `True` for template-based standards (can have multiple instances) | | `DISABLEDFEATURES` | `disabledFeatures` | JSON object disabling specific action modes | -| `REQUIREDCAPABILITIES` | *(discovery only)* | One capability string per line; parsed for standards metadata/JSON generation. The explicit `Test-CIPPStandardLicense` call in the function body still performs the actual runtime license check. | +| `REQUIREDCAPABILITIES` | *(discovery only)* | One capability string per line; generated from `Test-CIPPStandardLicense -Preset` and/or `-RequiredCapabilities` for standards metadata/JSON generation. The explicit `Test-CIPPStandardLicense` call in the function body still performs the actual runtime license check. | | `UPDATECOMMENTBLOCK` | *(tooling only)* | Always include with the literal value `Run the Tools\Update-StandardsComments.ps1 script to update this comment block`. Signals the comment-update tooling to regenerate this block. | ### Valid CAT values diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheData.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheData.ps1 index b52647862dc3..f4f236eca9a8 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheData.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheData.ps1 @@ -27,7 +27,7 @@ function Push-CIPPDBCacheData { # Check tenant capabilities for license-specific features $IntuneCapable = $false try { - $IntuneCapable = Test-CIPPStandardLicense -StandardName 'IntuneLicenseCheck' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $IntuneCapable = Test-CIPPStandardLicense -StandardName 'IntuneLicenseCheck' -TenantFilter $TenantFilter -Preset Intune -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Intune license check failed: $($_.Exception.Message)" -sev Warning -LogData $ErrorMessage @@ -35,7 +35,7 @@ function Push-CIPPDBCacheData { $ConditionalAccessCapable = $false try { - $ConditionalAccessCapable = Test-CIPPStandardLicense -StandardName 'ConditionalAccessLicenseCheck' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') -SkipLog + $ConditionalAccessCapable = Test-CIPPStandardLicense -StandardName 'ConditionalAccessLicenseCheck' -TenantFilter $TenantFilter -Preset Entra -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Conditional Access license check failed: $($_.Exception.Message)" -sev Warning -LogData $ErrorMessage @@ -43,7 +43,7 @@ function Push-CIPPDBCacheData { $AzureADPremiumP2Capable = $false try { - $AzureADPremiumP2Capable = Test-CIPPStandardLicense -StandardName 'AzureADPremiumP2LicenseCheck' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM_P2') -SkipLog + $AzureADPremiumP2Capable = Test-CIPPStandardLicense -StandardName 'AzureADPremiumP2LicenseCheck' -TenantFilter $TenantFilter -Preset EntraP2 -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Azure AD Premium P2 license check failed: $($_.Exception.Message)" -sev Warning -LogData $ErrorMessage @@ -51,7 +51,7 @@ function Push-CIPPDBCacheData { $ExchangeCapable = $false try { - $ExchangeCapable = Test-CIPPStandardLicense -StandardName 'ExchangeLicenseCheck' -TenantFilter $TenantFilter -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') -SkipLog + $ExchangeCapable = Test-CIPPStandardLicense -StandardName 'ExchangeLicenseCheck' -TenantFilter $TenantFilter -Preset Exchange -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Exchange license check failed: $($_.Exception.Message)" -sev Warning -LogData $ErrorMessage @@ -59,7 +59,7 @@ function Push-CIPPDBCacheData { $ComplianceCapable = $false try { - $ComplianceCapable = Test-CIPPStandardLicense -StandardName 'ComplianceLicenseCheck' -TenantFilter $TenantFilter -RequiredCapabilities @('RMS_S_PREMIUM', 'RMS_S_PREMIUM2', 'MIP_S_CLP1', 'MIP_S_CLP2') -SkipLog + $ComplianceCapable = Test-CIPPStandardLicense -StandardName 'ComplianceLicenseCheck' -TenantFilter $TenantFilter -Preset Compliance -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Compliance license check failed: $($_.Exception.Message)" -sev Warning -LogData $ErrorMessage @@ -67,7 +67,7 @@ function Push-CIPPDBCacheData { $SharePointCapable = $false try { - $SharePointCapable = Test-CIPPStandardLicense -StandardName 'SharePointLicenseCheck' -TenantFilter $TenantFilter -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') -SkipLog + $SharePointCapable = Test-CIPPStandardLicense -StandardName 'SharePointLicenseCheck' -TenantFilter $TenantFilter -Preset SharePoint -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "SharePoint license check failed: $($_.Exception.Message)" -sev Warning -LogData $ErrorMessage @@ -75,7 +75,7 @@ function Push-CIPPDBCacheData { $TeamsCapable = $false try { - $TeamsCapable = Test-CIPPStandardLicense -StandardName 'TeamsLicenseCheck' -TenantFilter $TenantFilter -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') -SkipLog + $TeamsCapable = Test-CIPPStandardLicense -StandardName 'TeamsLicenseCheck' -TenantFilter $TenantFilter -Preset Teams -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Teams license check failed: $($_.Exception.Message)" -sev Warning -LogData $ErrorMessage diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1 index c07b70ba5489..491bab19fae5 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandardsList.ps1 @@ -38,7 +38,7 @@ function Push-CIPPStandardsList { if ($IntuneTemplateFound) { # Perform license check - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneTemplate_general' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneTemplate_general' -TenantFilter $TenantFilter -Preset Intune if (-not $TestResult) { # Remove IntuneTemplate standards and set compare fields @@ -253,7 +253,7 @@ function Push-CIPPStandardsList { $CAStandardFound = ($ComputedStandards.Keys.Where({ $_ -like '*ConditionalAccessTemplate*' }, 'First').Count -gt 0) if ($CAStandardFound) { - $TestResult = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_general' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + $TestResult = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_general' -TenantFilter $TenantFilter -Preset Entra if (-not $TestResult) { $CAKeys = @($ComputedStandards.Keys | Where-Object { $_ -like '*ConditionalAccessTemplate*' }) $BulkFields = [System.Collections.Generic.List[object]]::new() diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertIntunePolicyConflicts.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertIntunePolicyConflicts.ps1 index d9b2239b1bcb..31469e4d25b9 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertIntunePolicyConflicts.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertIntunePolicyConflicts.ps1 @@ -56,14 +56,7 @@ function Get-CIPPAlertIntunePolicyConflicts { return } - $HasLicense = Test-CIPPStandardLicense -StandardName 'IntunePolicyStatus' -TenantFilter $TenantFilter -RequiredCapabilities @( - 'INTUNE_A', - 'MDM_Services', - 'EMS', - 'SCCM', - 'MICROSOFTINTUNEPLAN1' - ) - + $HasLicense = Test-CIPPStandardLicense -StandardName 'IntunePolicyStatus' -TenantFilter $TenantFilter -Preset Intune if (-not $HasLicense) { return } @@ -132,11 +125,11 @@ function Get-CIPPAlertIntunePolicyConflicts { $AppCount = ($Issues | Where-Object { $_.Type -eq 'Application' }).Count $AlertData = @([PSCustomObject]@{ - Message = "Found $PolicyCount policy issues and $AppCount application issues in Intune." - Tenant = $TenantFilter - PolicyIssues = $PolicyCount - AppIssues = $AppCount - Issues = $Issues + Message = "Found $PolicyCount policy issues and $AppCount application issues in Intune." + Tenant = $TenantFilter + PolicyIssues = $PolicyCount + AppIssues = $AppCount + Issues = $Issues }) } else { $AlertData = $Issues diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 index 34e45a3cb853..cc6795457cf1 100644 --- a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuarantineReleaseRequests.ps1 @@ -16,13 +16,7 @@ if ($Rerun) { return } - $HasLicense = Test-CIPPStandardLicense -StandardName 'QuarantineReleaseRequests' -TenantFilter $TenantFilter -RequiredCapabilities @( - 'EXCHANGE_S_STANDARD', - 'EXCHANGE_S_ENTERPRISE', - 'EXCHANGE_S_STANDARD_GOV', - 'EXCHANGE_S_ENTERPRISE_GOV', - 'EXCHANGE_LITE' - ) + $HasLicense = Test-CIPPStandardLicense -StandardName 'QuarantineReleaseRequests' -TenantFilter $TenantFilter -Preset Exchange if (-not $HasLicense) { return diff --git a/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 b/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 index 851822afe56c..f6e745ddd3e9 100644 --- a/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 +++ b/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 @@ -10,13 +10,17 @@ function Test-CIPPStandardLicense { .PARAMETER TenantFilter The tenant to check licensing for .PARAMETER RequiredCapabilities - Array of required capabilities for the standard + Array of required capabilities for the standard. Can be combined with Preset for edge cases. + .PARAMETER Preset + One or more predefined capability sets to check for the standard .FUNCTIONALITY Internal .EXAMPLE Test-CIPPStandardLicense -StandardName "ConditionalAccessTemplate" -TenantFilter "contoso.onmicrosoft.com" -RequiredCapabilities @('AADPremiumService') .EXAMPLE Test-CIPPStandardLicense -StandardName "SafeLinksPolicy" -TenantFilter "contoso.onmicrosoft.com" -RequiredCapabilities @('DEFENDER_FOR_OFFICE_365_PLAN_1', 'DEFENDER_FOR_OFFICE_365_PLAN_2') + .EXAMPLE + Test-CIPPStandardLicense -StandardName "TeamsGuestAccess" -TenantFilter "contoso.onmicrosoft.com" -Preset Teams #> [CmdletBinding()] param( @@ -26,13 +30,44 @@ function Test-CIPPStandardLicense { [Parameter(Mandatory = $true)] [string]$TenantFilter, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [string[]]$RequiredCapabilities, + [Parameter(Mandatory = $false)] + [ValidateSet('Exchange', 'SharePoint', 'Intune', 'Entra', 'EntraP2', 'Teams', 'Compliance')] + [string[]]$Preset, + [Parameter(Mandatory = $false)] [switch]$SkipLog ) + $Presets = @{ + Exchange = @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', + 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', + 'EXCHANGE_LITE') + SharePoint = @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', + 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', + 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + Intune = @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + Entra = @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + EntraP2 = @('AAD_PREMIUM_P2') + Teams = @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + Compliance = @('RMS_S_PREMIUM', 'RMS_S_PREMIUM2', 'MIP_S_CLP1', 'MIP_S_CLP2') + } + + if ((!$Preset -or $Preset.Count -eq 0) -and (!$RequiredCapabilities -or $RequiredCapabilities.Count -eq 0)) { + throw 'Test-CIPPStandardLicense requires either -Preset or -RequiredCapabilities.' + } + + if ($Preset) { + $RequiredCapabilities = @( + $RequiredCapabilities + foreach ($CapabilityPreset in $Preset) { + $Presets[$CapabilityPreset] + } + ) | Where-Object { $_ } | Select-Object -Unique + } + try { $TenantCapabilities = Get-CIPPTenantCapabilities -TenantFilter $TenantFilter diff --git a/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 b/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 index 0c9f4fb305bd..8c185c51602d 100644 --- a/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPDrift.ps1 @@ -29,8 +29,8 @@ function Get-CIPPDrift { [switch]$AllTenants ) - $IntuneCapable = Test-CIPPStandardLicense -StandardName 'IntuneTemplate_general' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') - $ConditionalAccessCapable = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_general' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + $IntuneCapable = Test-CIPPStandardLicense -StandardName 'IntuneTemplate_general' -TenantFilter $TenantFilter -Preset Intune + $ConditionalAccessCapable = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_general' -TenantFilter $TenantFilter -Preset Entra $IntuneTable = Get-CippTable -tablename 'templates' # Always load templates for display name resolution, even if tenant doesn't have licenses diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 index c3eda8370f7f..66c987c0d00f 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 @@ -116,7 +116,7 @@ function Test-CIPPAccessTenant { $ExchangeLicenseCapable = $true $ExchangeSkippedByLicense = $false try { - $ExchangeLicenseCapable = Test-CIPPStandardLicense -StandardName 'ExchangeAccessCheck' -TenantFilter $Tenant.customerId -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') -SkipLog + $ExchangeLicenseCapable = Test-CIPPStandardLicense -StandardName 'ExchangeAccessCheck' -TenantFilter $Tenant.customerId -Preset Exchange -SkipLog } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -headers $Headers -API $APINAME -tenant $tenant.defaultDomainName -message "Exchange license capability check failed. Continuing with Exchange access validation. Error: $($ErrorMessage.NormalizedError)" -Sev 'Warning' -LogData $ErrorMessage diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 index bb516877c6c8..d5bd3ec19cde 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheConditionalAccessPolicies.ps1 @@ -17,7 +17,7 @@ function Set-CIPPDBCacheConditionalAccessPolicies { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'ConditionalAccessCache' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'ConditionalAccessCache' -TenantFilter $TenantFilter -Preset Entra -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Azure AD Premium license, skipping CA' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 index 52df23f538dd..776b868f163d 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheDlpCompliancePolicies.ps1 @@ -17,7 +17,7 @@ function Set-CIPPDBCacheDlpCompliancePolicies { ) try { - $LicenseCheck = Test-CIPPStandardLicense -StandardName 'DlpCompliancePoliciesCache' -TenantFilter $TenantFilter -RequiredCapabilities @('RMS_S_PREMIUM', 'RMS_S_PREMIUM2', 'MIP_S_CLP1', 'MIP_S_CLP2') -SkipLog + $LicenseCheck = Test-CIPPStandardLicense -StandardName 'DlpCompliancePoliciesCache' -TenantFilter $TenantFilter -Preset Compliance -SkipLog if ($LicenseCheck -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have a Purview/AIP license, skipping DLP compliance policies' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 index f770d4c37cea..427965a7b3a0 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntunePolicies.ps1 @@ -17,7 +17,7 @@ function Set-CIPPDBCacheIntunePolicies { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'IntunePoliciesCache' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'IntunePoliciesCache' -TenantFilter $TenantFilter -Preset Intune -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Intune license, skipping' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 index 768525f69e94..d5da952cec5c 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCachePIMSettings.ps1 @@ -17,7 +17,7 @@ function Set-CIPPDBCachePIMSettings { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'PIMSettingsCache' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM_P2') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'PIMSettingsCache' -TenantFilter $TenantFilter -Preset EntraP2 -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Azure AD Premium P2 license, skipping PIM' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 index deafb9b25f9c..b9d89242742a 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSensitivityLabels.ps1 @@ -17,7 +17,7 @@ function Set-CIPPDBCacheSensitivityLabels { ) try { - $LicenseCheck = Test-CIPPStandardLicense -StandardName 'SensitivityLabelsCache' -TenantFilter $TenantFilter -RequiredCapabilities @('RMS_S_PREMIUM', 'RMS_S_PREMIUM2', 'MIP_S_CLP1', 'MIP_S_CLP2') -SkipLog + $LicenseCheck = Test-CIPPStandardLicense -StandardName 'SensitivityLabelsCache' -TenantFilter $TenantFilter -Preset Compliance -SkipLog if ($LicenseCheck -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have a Purview/AIP license, skipping sensitivity labels' -sev Debug diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUsers.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUsers.ps1 index e987260ab09b..e78271e0650f 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUsers.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheUsers.ps1 @@ -19,7 +19,7 @@ function Set-CIPPDBCacheUsers { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching users' -sev Debug - $SignInLogsCapable = Test-CIPPStandardLicense -StandardName 'UserSignInLogsCapable' -TenantFilter $TenantFilter -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') -SkipLog + $SignInLogsCapable = Test-CIPPStandardLicense -StandardName 'UserSignInLogsCapable' -TenantFilter $TenantFilter -Preset Entra -SkipLog # Base properties needed by tests, standards, reports, UI, and integrations (Hudu, NinjaOne) $BaseSelect = @( diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 index 4597ea7d6068..e45c433b2244 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 @@ -42,7 +42,7 @@ function Invoke-CIPPStandardAddDKIM { param($Tenant, $Settings) #$Rerun -Type Standard -Tenant $Tenant -API 'AddDKIM' -Settings $Settings - $TestResult = Test-CIPPStandardLicense -StandardName 'AddDKIM' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'AddDKIM' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index b35a16b21431..9c9ef1ace003 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -81,7 +81,7 @@ function Invoke-CIPPStandardAntiPhishPolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AntiPhishPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'AntiPhishPolicy' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 index 1a68a1be5acf..18de5c1bb8a1 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardAntiSpamSafeList { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AntiSpamSafeList' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'AntiSpamSafeList' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1 index 10cccd9d1527..79dbf1204143 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAssignmentFilterTemplate.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardAssignmentFilterTemplate { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AssignmentFilterTemplate' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'AssignmentFilterTemplate' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 index 26e28f6443b3..1f7db14eb657 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardAtpPolicyForO365 { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AtpPolicyForO365' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'AtpPolicyForO365' -TenantFilter $Tenant -Preset SharePoint ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AtpPolicyForO365' if ($TestResult -eq $false) { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 index df4fb6e2f223..c5193f0307e0 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 @@ -43,7 +43,7 @@ function Invoke-CIPPStandardAuditLog { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AuditLog' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'AuditLog' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1 index 33174b679ab8..500f6fc16cfd 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchive.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardAutoArchive { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchive' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchive' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1 index 639d1eb35a32..f5b249cce7fd 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoArchiveMailbox.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardAutoArchiveMailbox { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchiveMailbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchiveMailbox' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 index 8ba91b371d16..d8eed08d8e9a 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 @@ -36,7 +36,7 @@ function Invoke-CIPPStandardAutoExpandArchive { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AutoExpandArchive' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'AutoExpandArchive' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 index 9834a03673f0..9becbd6c0533 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 @@ -46,7 +46,7 @@ function Invoke-CIPPStandardAutopilotProfile { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AutopilotProfile' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'AutopilotProfile' -TenantFilter $Tenant -Preset Intune # Get the current configuration diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 index 780b1457cfab..5f0011303a48 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 @@ -44,7 +44,7 @@ function Invoke-CIPPStandardAutopilotStatusPage { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'AutopilotStatusPage' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'AutopilotStatusPage' -TenantFilter $Tenant -Preset Intune # Get current Autopilot enrollment status page configuration diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBookings.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBookings.ps1 index 80e95a84c45f..39026d801a93 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBookings.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBookings.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardBookings { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'Bookings' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'Bookings' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 index 0a28c4747562..6a3c80fa9bf3 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardBranding.ps1 @@ -41,7 +41,7 @@ function Invoke-CIPPStandardBranding { param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'Branding' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2', 'OFFICE_BUSINESS') + $TestResult = Test-CIPPStandardLicense -StandardName 'Branding' -TenantFilter $Tenant -Preset Entra -RequiredCapabilities @('OFFICE_BUSINESS') if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 index 360fb92d8666..7d4153591afc 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardCloudMessageRecall { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'CloudMessageRecall' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'CloudMessageRecall' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardColleagueImpersonationAlert.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardColleagueImpersonationAlert.ps1 index 8656f31602b4..8971ff8afd1e 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardColleagueImpersonationAlert.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardColleagueImpersonationAlert.ps1 @@ -45,7 +45,7 @@ function Invoke-CIPPStandardColleagueImpersonationAlert { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'ColleagueImpersonationAlert' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'ColleagueImpersonationAlert' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 index fe65856b730b..5a34cfde7368 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 @@ -47,7 +47,7 @@ function Invoke-CIPPStandardConditionalAccessTemplate { } - $TestResult = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_general' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + $TestResult = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_general' -TenantFilter $Tenant -Preset Entra if ($TestResult -eq $false) { Set-CIPPStandardsCompareField -FieldName "standards.ConditionalAccessTemplate.$($Settings.TemplateList.value)" -FieldValue 'This tenant does not have the required license for this standard.' -Tenant $Tenant return $true @@ -71,7 +71,7 @@ function Invoke-CIPPStandardConditionalAccessTemplate { $JSONObj = (Get-CippAzDataTableEntity @Table -Filter $Filter).JSON $Policy = $JSONObj | ConvertFrom-Json if ($Policy.conditions.userRiskLevels.count -gt 0 -or $Policy.conditions.signInRiskLevels.count -gt 0) { - $TestP2 = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_p2' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM_P2') -SkipLog + $TestP2 = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_p2' -TenantFilter $Tenant -Preset EntraP2 -SkipLog if (!$TestP2) { Write-Information "Skipping policy $($Policy.displayName) as it requires AAD Premium P2 license." Set-CIPPStandardsCompareField -FieldName "standards.ConditionalAccessTemplate.$($Settings.TemplateList.value)" -FieldValue "Policy $($Policy.displayName) requires AAD Premium P2 license." -Tenant $Tenant @@ -112,7 +112,7 @@ function Invoke-CIPPStandardConditionalAccessTemplate { $CheckExististing = $AllCAPolicies | Where-Object -Property displayName -EQ $Settings.TemplateList.label if (!$CheckExististing) { if ($Policy.conditions.userRiskLevels.Count -gt 0 -or $Policy.conditions.signInRiskLevels.Count -gt 0) { - $TestP2 = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_p2' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM_P2') -SkipLog + $TestP2 = Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate_p2' -TenantFilter $Tenant -Preset EntraP2 -SkipLog if (!$TestP2) { Set-CIPPStandardsCompareField -FieldName "standards.ConditionalAccessTemplate.$($Settings.TemplateList.value)" -FieldValue "Policy $($Settings.TemplateList.label) requires AAD Premium P2 license." -Tenant $Tenant } else { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 index 1f1f5041c4c6..dc4c11afd0bb 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 @@ -43,7 +43,7 @@ function Invoke-CIPPStandardCustomBannedPasswordList { param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'CustomBannedPasswordList' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + $TestResult = Test-CIPPStandardLicense -StandardName 'CustomBannedPasswordList' -TenantFilter $Tenant -Preset Entra if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 index ee5ff2d213f5..37db79585cd2 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 @@ -47,7 +47,7 @@ function Invoke-CIPPStandardDefaultPlatformRestrictions { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DefaultPlatformRestrictions' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'DefaultPlatformRestrictions' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 index e866fed9b306..e7d3b7be7e7c 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 @@ -46,7 +46,7 @@ function Invoke-CIPPStandardDefaultSharingLink { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DefaultSharingLink' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DefaultSharingLink' -TenantFilter $Tenant -Preset SharePoint # Determine the desired sharing link type (default to Internal if not specified) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 index fb2bb660567b..8597dfef3c1d 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardDelegateSentItems { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DelegateSentItems' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DelegateSentItems' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 index 19e1728bf25e..f895933d6f64 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardDeletedUserRentention { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DeletedUserRentention' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DeletedUserRentention' -TenantFilter $Tenant -Preset SharePoint ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DeletedUserRetention' if ($TestResult -eq $false) { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 index 8c67aaa53158..ba17adb24c50 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 @@ -63,7 +63,7 @@ function Invoke-CIPPStandardDeployCheckChromeExtension { param($Tenant, $Settings) # Check for required Intune license - $TestResult = Test-CIPPStandardLicense -StandardName 'DeployCheckChromeExtension' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'DeployCheckChromeExtension' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { Set-CIPPStandardsCompareField -FieldName 'standards.DeployCheckChromeExtension' -FieldValue 'This tenant does not have the required license for this standard.' -Tenant $Tenant diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 index aad3a0183aed..c8e121eee1b9 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardDeployContactTemplates { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DeployContactTemplates' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DeployContactTemplates' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 index 3a66cb07f26f..a5e4d2467983 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 @@ -41,7 +41,7 @@ function Invoke-CIPPStandardDeployMailContact { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DeployMailContact' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DeployMailContact' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 index d0d2f576c3d7..e314ca52b592 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardDisableAddShortcutsToOneDrive { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableAddShortcutsToOneDrive' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableAddShortcutsToOneDrive' -TenantFilter $Tenant -Preset SharePoint ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableAddShortcutsToOneDrive' if ($TestResult -eq $false) { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 index 2e9d5fe1a8a5..af833e5127f1 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardDisableAdditionalStorageProviders { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableAdditionalStorageProviders' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableAdditionalStorageProviders' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 index dafcd82880e2..2606292ebd66 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 @@ -42,7 +42,7 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableBasicAuthSMTP' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableBasicAuthSMTP' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableEWS.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableEWS.ps1 index 871aacadaef7..b462e0720915 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableEWS.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableEWS.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardDisableEWS { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableEWS' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableEWS' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 index 568f367e6e58..a39f506886a0 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardDisableExchangeOnlinePowerShell { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableExchangeOnlinePowerShell' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableExchangeOnlinePowerShell' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 index 9d645e3c8be2..8707a5e75a4e 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 @@ -41,7 +41,7 @@ function Invoke-CIPPStandardDisableExternalCalendarSharing { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableExternalCalendarSharing' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableExternalCalendarSharing' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 index 60531cf79033..84acf28a82b2 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableGuests.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardDisableGuests { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableGuests' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableGuests' -TenantFilter $Tenant -Preset Entra if ($TestResult -eq $false) { #writing to each item that the license is not present. diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 index c798736dfe5a..9263217655f4 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardDisableM365GroupUsers { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableM365GroupUsers' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableM365GroupUsers' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 index 746171c6cd91..b4e7e95d034f 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 @@ -42,7 +42,7 @@ function Invoke-CIPPStandardDisableOutlookAddins { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableOutlookAddins' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableOutlookAddins' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 index b1cfdda20880..6c1bf35618d4 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 @@ -45,7 +45,7 @@ function Invoke-CIPPStandardDisableReshare { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableReshare' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableReshare' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 index d270cdce3675..46730ecd32b1 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardDisableResourceMailbox { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableResourceMailbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableResourceMailbox' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 index 33e453583b34..9703daa08f4e 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 @@ -47,7 +47,7 @@ function Invoke-CIPPStandardDisableSharePointLegacyAuth { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableSharePointLegacyAuth' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableSharePointLegacyAuth' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 index 91b761a7ab3b..c81ae466d2a7 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardDisableTNEF { #> param ($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableTNEF' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableTNEF' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 index 94748b7df36a..b5bfa5ff04e1 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardDisableUserSiteCreate { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'DisableUserSiteCreate' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'DisableUserSiteCreate' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 index 61aae3eaddd8..f2b134cbfb77 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 @@ -43,7 +43,7 @@ function Invoke-CIPPStandardEXODisableAutoForwarding { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EXODisableAutoForwarding' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'EXODisableAutoForwarding' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 index 786acd96dc6a..5a949daed90e 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 @@ -43,7 +43,7 @@ function Invoke-CIPPStandardEXOOutboundSpamLimits { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EXOOutboundSpamLimits' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'EXOOutboundSpamLimits' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableExchangeCloudManagement.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableExchangeCloudManagement.ps1 index 848b545789eb..a1dae1e109ca 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableExchangeCloudManagement.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableExchangeCloudManagement.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardEnableExchangeCloudManagement { param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EnableExchangeCloudManagement' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV') + $TestResult = Test-CIPPStandardLicense -StandardName 'EnableExchangeCloudManagement' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { Write-Host "We're exiting as the correct license is not present for this standard." diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 index 8818a8e0bd1f..2f8c0e326634 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardEnableLitigationHold { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EnableLitigationHold' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'EnableLitigationHold' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 index d61e0d6b9058..4b67c83fc4e1 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 @@ -41,7 +41,7 @@ function Invoke-CIPPStandardEnableMailTips { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EnableMailTips' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'EnableMailTips' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 index 70249dcc7b97..62262a2b9d7f 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 @@ -46,7 +46,7 @@ function Invoke-CIPPStandardEnableMailboxAuditing { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EnableMailboxAuditing' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'EnableMailboxAuditing' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 index a13f0fd22075..9d75cd84746e 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardEnableOnlineArchiving { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EnableOnlineArchiving' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'EnableOnlineArchiving' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 index 6d89b3031904..2339a92bc47e 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration.ps1 @@ -48,7 +48,7 @@ function Invoke-CIPPStandardEnrollmentWindowsHelloForBusinessConfiguration { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EnrollmentWindowsHelloForBusinessConfiguration' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'EnrollmentWindowsHelloForBusinessConfiguration' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1 index 4461ad335873..5f3faba79e67 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExchangeConnectorTemplate.ps1 @@ -34,7 +34,7 @@ function Invoke-CIPPStandardExchangeConnectorTemplate { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'ExConnector' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'ExConnector' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 index d6b6202cb318..ba6cfbaf908a 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardExcludedfileExt { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'ExcludedfileExt' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'ExcludedfileExt' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 index 1bc5314fe35e..6eede7fc53ee 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardFocusedInbox { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'FocusedInbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'FocusedInbox' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 index b5864df1fc8a..2ce49074209f 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 @@ -34,7 +34,7 @@ function Invoke-CIPPStandardGlobalQuarantineNotifications { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param ($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'GlobalQuarantineNotifications' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'GlobalQuarantineNotifications' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineSettings.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineSettings.ps1 index 2235e1493340..bfc88c729612 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineSettings.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGlobalQuarantineSettings.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardGlobalQuarantineSettings { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineTemplate' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 index ee2f5bbcf78e..f89137e5a324 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 @@ -67,7 +67,7 @@ function Invoke-CIPPStandardGroupTemplate { # Check if Exchange license is required for distribution groups if ($groupobj.groupType -in @('distribution', 'dynamicdistribution')) { - $TestResult = Test-CIPPStandardLicense -StandardName 'GroupTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'GroupTemplate' -TenantFilter $Tenant -Preset Exchange -SkipLog if (!$TestResult) { Write-LogMessage -API 'Standards' -tenant $tenant -message "Cannot create group $($groupobj.displayname) as the tenant is not licensed for Exchange." -Sev 'Error' continue @@ -132,7 +132,7 @@ function Invoke-CIPPStandardGroupTemplate { } else { # Handle Exchange Online groups (Distribution, DynamicDistribution) - $TestResult = Test-CIPPStandardLicense -StandardName 'GroupTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'GroupTemplate' -TenantFilter $Tenant -Preset Exchange -SkipLog if (!$TestResult) { Write-LogMessage -API 'Standards' -tenant $tenant -message "Cannot update group $($groupobj.displayName) as the tenant is not licensed for Exchange." -Sev 'Error' continue diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 index 32874304857a..a32803fbf620 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardIntuneComplianceSettings { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneComplianceSettings' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneComplianceSettings' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneWindowsDiagnostic.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneWindowsDiagnostic.ps1 index a8085874bb02..cc259250d075 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneWindowsDiagnostic.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardIntuneWindowsDiagnostic.ps1 @@ -39,7 +39,7 @@ Function Invoke-CIPPStandardIntuneWindowsDiagnostic { [CmdletBinding()] param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneWindowsDiagnostic' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneWindowsDiagnostic' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1 index 363d24b158c5..5d2c43e167c1 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMEnrollmentDuringRegistration.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardMDMEnrollmentDuringRegistration { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'MDMEnrollmentDuringRegistration' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'MDMEnrollmentDuringRegistration' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 index c1ca2137b68a..d26434af0c5c 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardMDMScope { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'MDMScope' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'MDMScope' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 index 22807d8cc1a9..4ab2a5b0809d 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardMailboxRecipientLimits { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'MailboxRecipientLimits' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'MailboxRecipientLimits' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 index 978103c47d88..ce7762e09640 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -58,7 +58,7 @@ function Invoke-CIPPStandardMalwareFilterPolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'MalwareFilterPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'MalwareFilterPolicy' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 index 034dce5c97db..e5d6b42375ec 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 @@ -34,7 +34,7 @@ function Invoke-CIPPStandardMessageExpiration { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'MessageExpiration' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'MessageExpiration' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOMEBranding.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOMEBranding.ps1 index 00d7a642232e..a295685e5129 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOMEBranding.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOMEBranding.ps1 @@ -41,7 +41,7 @@ function Invoke-CIPPStandardOMEBranding { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'OMEBranding' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'OMEBranding' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1 index d7e712a525e4..06a6d08aeda2 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOWAAttachmentRestrictions.ps1 @@ -42,7 +42,7 @@ function Invoke-CIPPStandardOWAAttachmentRestrictions { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'OWAAttachmentRestrictions' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'OWAAttachmentRestrictions' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 index 5727b25ddb3b..c920d59712d8 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardOutBoundSpamAlert { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'OutBoundSpamAlert' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'OutBoundSpamAlert' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 index b908df1e30e2..b8cb3c15afbf 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardPhishProtection { param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'PhishProtection' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2', 'OFFICE_BUSINESS') + $TestResult = Test-CIPPStandardLicense -StandardName 'PhishProtection' -TenantFilter $Tenant -Preset Entra -RequiredCapabilities @('OFFICE_BUSINESS') if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 index 6665b4749cf6..cd36ab3cb82b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 @@ -36,7 +36,7 @@ function Invoke-CIPPStandardPhishSimSpoofIntelligence { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'PhishSimSpoofIntelligence' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'PhishSimSpoofIntelligence' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 index dd1721a81709..ba0fe12509ac 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardPhishingSimulations { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'PhishingSimulations' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'PhishingSimulations' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 index 6ce4e87cab35..d001a96e0d41 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardProfilePhotos { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'ProfilePhotos' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'ProfilePhotos' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 index 3f3eb3e06a59..dc0144141d44 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardQuarantineRequestAlert { #> param ($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineRequestAlert' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineRequestAlert' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 index 95cae003f66b..63ecc8f0e119 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 @@ -48,7 +48,7 @@ function Invoke-CIPPStandardQuarantineTemplate { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'QuarantineTemplate' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 index eacdaf30c731..8bd8a6e6bc55 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardRestrictThirdPartyStorageServices { #> param ($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'ThirdPartyStorageServicesRestricted' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'ThirdPartyStorageServicesRestricted' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 index 348e62d9fec5..00b89883ab27 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardRetentionPolicyTag { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'RetentionPolicyTag' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'RetentionPolicyTag' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardReusableSettingsTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardReusableSettingsTemplate.ps1 index f9a0c9965bec..2e74d71dafcc 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardReusableSettingsTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardReusableSettingsTemplate.ps1 @@ -25,7 +25,7 @@ function Invoke-CIPPStandardReusableSettingsTemplate { ADDEDCOMPONENT {"type":"autoComplete","multiple":true,"creatable":false,"required":true,"name":"TemplateList","label":"Select Reusable Settings Template","api":{"queryKey":"ListIntuneReusableSettingTemplates","url":"/api/ListIntuneReusableSettingTemplates","labelField":"displayName","valueField":"GUID","showRefresh":true,"templateView":{"title":"Reusable Settings","property":"RawJSON","type":"intune"}}} POWERSHELLEQUIVALENT - + RECOMMENDEDBY UPDATECOMMENTBLOCK Run the Tools\Update-StandardsComments.ps1 script to update this comment block @@ -62,8 +62,7 @@ function Invoke-CIPPStandardReusableSettingsTemplate { return $InputObject } - $RequiredCapabilities = @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') - $TestResult = Test-CIPPStandardLicense -StandardName 'ReusableSettingsTemplate_general' -TenantFilter $Tenant -RequiredCapabilities $RequiredCapabilities + $TestResult = Test-CIPPStandardLicense -StandardName 'ReusableSettingsTemplate_general' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { $settings.TemplateList | ForEach-Object { $MissingLicenseMessage = "This tenant is missing one or more required licenses for this standard: $($RequiredCapabilities -join ', ')." diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 index 9d6e16d9c2cf..9b999224c4d9 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardRotateDKIM { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'RotateDKIM' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'RotateDKIM' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 index 24917859e8c4..30e25407bf09 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardSPAzureB2B { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPAzureB2B' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPAzureB2B' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 index db4c72109298..f4abb8224b3d 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardSPDirectSharing { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPDirectSharing' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPDirectSharing' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableCustomScripts.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableCustomScripts.ps1 index d9344d5f2ff3..3b798632add4 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableCustomScripts.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableCustomScripts.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardSPDisableCustomScripts { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisableCustomScripts' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisableCustomScripts' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 index 6bfff643ffcb..fc050627c395 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardSPDisableLegacyWorkflows { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisableLegacyWorkflows' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisableLegacyWorkflows' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableStoreAccess.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableStoreAccess.ps1 index 24064a809d62..faa1079cdb03 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableStoreAccess.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisableStoreAccess.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardSPDisableStoreAccess { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisableStoreAccess' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisableStoreAccess' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 index 03db2dc19e47..c3e5d617ebe9 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 @@ -44,7 +44,7 @@ function Invoke-CIPPStandardSPDisallowInfectedFiles { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisallowInfectedFiles' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV','ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPDisallowInfectedFiles' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 index bc99fc1f5c89..a62aadd15d2b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 @@ -45,7 +45,7 @@ function Invoke-CIPPStandardSPEmailAttestation { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPEmailAttestation' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPEmailAttestation' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 index edaff7e2817f..980eb79b1b61 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 @@ -45,7 +45,7 @@ function Invoke-CIPPStandardSPExternalUserExpiration { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPExternalUserExpiration' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPExternalUserExpiration' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPFileRequests.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPFileRequests.ps1 index 099d196ac80e..784625e25add 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPFileRequests.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPFileRequests.ps1 @@ -41,7 +41,7 @@ function Invoke-CIPPStandardSPFileRequests { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPFileRequests' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPFileRequests' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { Write-LogMessage -API 'Standards' -tenant $tenant -message 'The tenant is not licenced for this standard SPFileRequests' -sev Error diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 index e6e70dd58b4c..8f730b08f3f2 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardSPSyncButtonState { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SPSyncButtonState' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'SPSyncButtonState' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 index f4e8a73738f4..17d986547ebb 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 @@ -47,7 +47,7 @@ function Invoke-CIPPStandardSafeAttachmentPolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SafeAttachmentPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SafeAttachmentPolicy' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 index 04527ce524d6..02b15bcad276 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 @@ -61,7 +61,7 @@ function Invoke-CIPPStandardSafeLinksPolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksPolicy' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 index 8d7f74b28706..b1a4bbfd7cf4 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardSafeLinksTemplatePolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksTemplatePolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksTemplatePolicy' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 index 4a75aa37768c..7252c9623ac2 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardSafeSendersDisable { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SafeSendersDisable' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SafeSendersDisable' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 index 1e993982fdad..5f45f242ef9b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardSendFromAlias { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SendFromAlias' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SendFromAlias' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 index ea594cdef299..f8345408d603 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardSendReceiveLimitTenant { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SendReceiveLimitTenant' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SendReceiveLimitTenant' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 index fdf996a9e9e8..96ec95382810 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardShortenMeetings { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'ShortenMeetings' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'ShortenMeetings' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index eab8864f1eb6..27b7c6e49ea4 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -75,7 +75,7 @@ function Invoke-CIPPStandardSpamFilterPolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SpamFilterPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SpamFilterPolicy' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 index 6fa95268d1cc..71c60d44244d 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 @@ -44,7 +44,7 @@ function Invoke-CIPPStandardSpoofWarn { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'SpoofWarn' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'SpoofWarn' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 index 43beaef30d6a..74cdcd52abfc 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 @@ -42,7 +42,7 @@ function Invoke-CIPPStandardStaleEntraDevices { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'StaleEntraDevices' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'StaleEntraDevices' -TenantFilter $Tenant -Preset Intune # Get all Entra devices diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1 index e79e3f25d112..603b8662b4c2 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsChatProtection.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardTeamsChatProtection { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsChatProtection' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsChatProtection' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 index 640b39c5c4b9..61b0216c06ce 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardTeamsEmailIntegration { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsEmailIntegration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsEmailIntegration' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 index 2240f7cd2514..37a845e72388 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardTeamsEnrollUser { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsEnrollUser' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsEnrollUser' -TenantFilter $Tenant -Preset Teams # Get EnrollUserOverride value using null-coalescing operator diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 index e2892d93f170..f4ed299c9094 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardTeamsExternalAccessPolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalAccessPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalAccessPolicy' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1 index db221c618f9f..89322c6142f3 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalChatWithAnyone.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardTeamsExternalChatWithAnyone { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalChatWithAnyone' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalChatWithAnyone' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 index 7ea4bf6e7ae5..fb4aed237cd6 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 @@ -43,7 +43,7 @@ function Invoke-CIPPStandardTeamsExternalFileSharing { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalFileSharing' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsExternalFileSharing' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 index ab245b7fd939..f5714c8be50d 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardTeamsFederationConfiguration { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsFederationConfiguration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsFederationConfiguration' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 index 646ea312797a..01e6ac4b6513 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 @@ -50,7 +50,7 @@ function Invoke-CIPPStandardTeamsGlobalMeetingPolicy { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsGlobalMeetingPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsGlobalMeetingPolicy' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 index 5530de4b9382..e15bc2c1c0f8 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardTeamsGuestAccess { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsGuestAccess' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsGuestAccess' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 index e84885986aee..c977b0f648b9 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardTeamsMeetingRecordingExpiration { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingRecordingExpiration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingRecordingExpiration' -TenantFilter $Tenant -Preset Teams # Input validation diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 index c7baffc456f2..c07c3413ebbb 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardTeamsMeetingVerification { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingVerification' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingVerification' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 index 48b8009428b1..19f1c089defd 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingsByDefault' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMeetingsByDefault' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 index 0137a96fda7d..8e2a0cbfa0e1 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 @@ -45,7 +45,7 @@ function Invoke-CIPPStandardTeamsMessagingPolicy { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMessagingPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsMessagingPolicy' -TenantFilter $Tenant -Preset Teams if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantAllowBlockListTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantAllowBlockListTemplate.ps1 index 38ab5cedcbbc..238f2f71353a 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantAllowBlockListTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantAllowBlockListTemplate.ps1 @@ -35,7 +35,7 @@ function Invoke-CIPPStandardTenantAllowBlockListTemplate { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TenantAllowBlockListTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'TenantAllowBlockListTemplate' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 index 12aa7af173b6..71e5a2a8c6b5 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardTenantDefaultTimezone { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TenantDefaultTimezone' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'TenantDefaultTimezone' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 index 21b57514d49c..4f82cc7a3690 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 @@ -35,7 +35,7 @@ function Invoke-CIPPStandardTransportRuleTemplate { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TransportRuleTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'TransportRuleTemplate' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 index eab1d38a1ef4..65cf25ce9ae0 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 @@ -37,7 +37,7 @@ function Invoke-CIPPStandardTwoClickEmailProtection { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TwoClickEmailProtection' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'TwoClickEmailProtection' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 index cbf1546f3280..4e50340dd22a 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardUserSubmissions { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'UserSubmissions' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'UserSubmissions' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardWindowsBackupRestore.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardWindowsBackupRestore.ps1 index afeff9e56375..eff979d61a0f 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardWindowsBackupRestore.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardWindowsBackupRestore.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardWindowsBackupRestore { [CmdletBinding()] param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'WindowsBackupRestore' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'WindowsBackupRestore' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 index 455a4468c8e1..c0890e120c6c 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardcalDefault { param($Tenant, $Settings, $QueueItem) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'calDefault' - $TestResult = Test-CIPPStandardLicense -StandardName 'calDefault' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + $TestResult = Test-CIPPStandardLicense -StandardName 'calDefault' -TenantFilter $Tenant -Preset Exchange #No Foundation because that does not allow powershell access if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 index 09b60695d93d..b9d949bc470c 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandarddisableMacSync { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'disableMacSync' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'disableMacSync' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 index 1ebe6cfd68f1..8a40218f5931 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 @@ -46,7 +46,7 @@ function Invoke-CIPPStandardintuneBrandingProfile { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'intuneBrandingProfile' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'intuneBrandingProfile' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 index 60bcafbcabdb..e35a76013961 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 @@ -40,7 +40,7 @@ function Invoke-CIPPStandardintuneDeviceReg { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'intuneDeviceReg' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'intuneDeviceReg' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 index 3628c3396dc0..14e6f9b37c34 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardintuneDeviceRetirementDays { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'intuneDeviceRetirementDays' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'intuneDeviceRetirementDays' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 index 6cb9f00abcfb..d1ef515b2026 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 @@ -46,7 +46,7 @@ function Invoke-CIPPStandardsharingCapability { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'sharingCapability' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'sharingCapability' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 index a69778d30a12..934f6943d9bc 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 @@ -45,7 +45,7 @@ function Invoke-CIPPStandardsharingDomainRestriction { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'sharingDomainRestriction' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'sharingDomainRestriction' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 index 9e1a7433d62f..b089148a4e46 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 @@ -42,7 +42,7 @@ function Invoke-CIPPStandardunmanagedSync { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'unmanagedSync' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + $TestResult = Test-CIPPStandardLicense -StandardName 'unmanagedSync' -TenantFilter $Tenant -Preset Intune if ($TestResult -eq $false) { return $true diff --git a/Tools/Update-StandardsComments.ps1 b/Tools/Update-StandardsComments.ps1 index 4c45f54624d9..25b182bca27e 100644 --- a/Tools/Update-StandardsComments.ps1 +++ b/Tools/Update-StandardsComments.ps1 @@ -47,9 +47,137 @@ function EscapeMarkdown([object]$InputObject) { return $Temp.Replace('\', '\\').Replace('*', '\*').Replace('_', '\_').Replace("``", "\``").Replace('$', '\$').Replace('|', '\|').Replace('<', '\<').Replace('>', '\>').Replace([System.Environment]::NewLine, '
') } +function Get-StringValuesFromAst { + param( + [Parameter(Mandatory = $true)] + [System.Management.Automation.Language.Ast]$ArgumentAst + ) + + try { + $Value = $ArgumentAst.SafeGetValue() + if ($Value -is [array]) { + return @($Value | ForEach-Object { $_.ToString() }) + } + + if ($Value -is [string]) { + return @($Value) + } + } catch {} + + if ($ArgumentAst -is [System.Management.Automation.Language.StringConstantExpressionAst]) { + return @($ArgumentAst.Value) + } + + if ($ArgumentAst -is [System.Management.Automation.Language.ExpandableStringExpressionAst]) { + return @($ArgumentAst.Value) + } + + if ($ArgumentAst -is [System.Management.Automation.Language.ArrayLiteralAst]) { + return @($ArgumentAst.Elements | ForEach-Object { Get-StringValuesFromAst -ArgumentAst $_ }) + } + + if ($ArgumentAst -is [System.Management.Automation.Language.PipelineAst]) { + return @($ArgumentAst.PipelineElements | ForEach-Object { Get-StringValuesFromAst -ArgumentAst $_ }) + } + + if ($ArgumentAst -is [System.Management.Automation.Language.CommandExpressionAst]) { + return @(Get-StringValuesFromAst -ArgumentAst $ArgumentAst.Expression) + } + + return @() +} + +function Get-CIPPCapabilityPresets { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + $Tokens = $null + $ParseErrors = $null + $Ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$Tokens, [ref]$ParseErrors) + $LicenseFunction = $Ast.Find({ + param($Node) + $Node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $Node.Name -eq 'Test-CIPPStandardLicense' + }, $true) + + $PresetAssignment = $LicenseFunction.Body.Find({ + param($Node) + $Node -is [System.Management.Automation.Language.AssignmentStatementAst] -and + $Node.Left -is [System.Management.Automation.Language.VariableExpressionAst] -and + $Node.Left.VariablePath.UserPath -eq 'Presets' -and + $Node.Right -is [System.Management.Automation.Language.HashtableAst] + }, $true) + + $Presets = @{} + foreach ($Pair in $PresetAssignment.Right.KeyValuePairs) { + $PresetName = (Get-StringValuesFromAst -ArgumentAst $Pair.Item1)[0] + $Presets[$PresetName] = @(Get-StringValuesFromAst -ArgumentAst $Pair.Item2) + } + + return $Presets +} + +function Get-LicenseCheckCapabilities { + param( + [Parameter(Mandatory = $true)] + [string]$Content, + + [Parameter(Mandatory = $true)] + [hashtable]$CapabilityPresets + ) + + $Tokens = $null + $ParseErrors = $null + $Ast = [System.Management.Automation.Language.Parser]::ParseInput($Content, [ref]$Tokens, [ref]$ParseErrors) + $LicenseCheck = $Ast.Find({ + param($Node) + $Node -is [System.Management.Automation.Language.CommandAst] -and $Node.GetCommandName() -eq 'Test-CIPPStandardLicense' + }, $true) + + if (!$LicenseCheck) { + return @() + } + + $Capabilities = [System.Collections.Generic.List[string]]::new() + for ($Index = 0; $Index -lt $LicenseCheck.CommandElements.Count; $Index++) { + $Element = $LicenseCheck.CommandElements[$Index] + if ($Element -isnot [System.Management.Automation.Language.CommandParameterAst]) { + continue + } + + $Argument = if (($Index + 1) -lt $LicenseCheck.CommandElements.Count -and $LicenseCheck.CommandElements[$Index + 1] -isnot [System.Management.Automation.Language.CommandParameterAst]) { + $LicenseCheck.CommandElements[$Index + 1] + } + + if (!$Argument) { + continue + } + + switch ($Element.ParameterName) { + 'Preset' { + foreach ($PresetName in (Get-StringValuesFromAst -ArgumentAst $Argument)) { + foreach ($Capability in $CapabilityPresets[$PresetName]) { + $Capabilities.Add($Capability) + } + } + } + 'RequiredCapabilities' { + foreach ($Capability in (Get-StringValuesFromAst -ArgumentAst $Argument)) { + $Capabilities.Add($Capability) + } + } + } + } + + return @($Capabilities | Where-Object { $_ } | Select-Object -Unique) +} # Find the paths to the standards.json file based on the current script path -$StandardsJSONPath = Split-Path (Split-Path $PSScriptRoot) +$CIPPApiRoot = Split-Path $PSScriptRoot +$CapabilityPresets = Get-CIPPCapabilityPresets -Path (Join-Path $CIPPApiRoot 'Modules\CIPPCore\Public\Functions\Test-CIPPStandardLicense.ps1') + +$StandardsJSONPath = Split-Path $CIPPApiRoot $StandardsJSONPath = Resolve-Path "$StandardsJSONPath\*\src\data\standards.json" $StandardsInfo = Get-Content -Path $StandardsJSONPath | ConvertFrom-Json -Depth 10 @@ -57,7 +185,7 @@ foreach ($Standard in $StandardsInfo) { # Calculate the standards file name and path $StandardFileName = $Standard.name -replace 'standards.', 'Invoke-CIPPStandard' - $StandardsFilePath = Resolve-Path "$(Split-Path $PSScriptRoot)\Modules\CIPPStandards\Public\Standards\$StandardFileName.ps1" + $StandardsFilePath = Resolve-Path "$CIPPApiRoot\Modules\CIPPStandards\Public\Standards\$StandardFileName.ps1" if (-not (Test-Path $StandardsFilePath)) { Write-Host "No file found for standard $($Standard.name)" -ForegroundColor Yellow continue @@ -120,20 +248,15 @@ foreach ($Standard in $StandardsInfo) { } - # Extract RequiredCapabilities from Test-CIPPStandardLicense in the function body - # Match the first occurrence of -RequiredCapabilities @(...) in the file - $CapabilitiesRegex = 'Test-CIPPStandardLicense\s[^}]*-RequiredCapabilities\s+@\(([^)]+)\)' - if ($Content -match $CapabilitiesRegex) { - $RawCapabilities = $Matches[1] - $Capabilities = @($RawCapabilities -split ',' | ForEach-Object { $_.Trim().Trim("'").Trim('"') } | Where-Object { $_ }) - if ($Capabilities.Count -gt 0) { - $NewComment.Add(" REQUIREDCAPABILITIES`n") - foreach ($Cap in $Capabilities) { - $NewComment.Add(" `"$Cap`"`n") - } - # Update the standard object for JSON output - $Standard | Add-Member -NotePropertyName 'requiredCapabilities' -NotePropertyValue $Capabilities -Force + $Capabilities = Get-LicenseCheckCapabilities -Content $Content -CapabilityPresets $CapabilityPresets + if ($Capabilities.Count -gt 0) { + $NewComment.Add(" REQUIREDCAPABILITIES`n") + foreach ($Cap in $Capabilities) { + $NewComment.Add(" `"$Cap`"`n") } + + # Update the standard object for JSON output + $Standard | Add-Member -NotePropertyName 'requiredCapabilities' -NotePropertyValue $Capabilities -Force } else { # No license check — remove stale property if present if ($Standard.PSObject.Properties['requiredCapabilities']) { From 2152641640709ecfe211f3c38752edad0d3f5f13 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Mon, 11 May 2026 22:01:50 +0200 Subject: [PATCH 02/28] fix: add the presets to the rest of the standards --- ...CIPPDBCacheIntuneAppProtectionPolicies.ps1 | 2 +- .../Set-CIPPDBCacheIntuneApplications.ps1 | 2 +- ...Set-CIPPDBCacheIntuneAssignmentFilters.ps1 | 2 +- ...et-CIPPDBCacheIntuneCompliancePolicies.ps1 | 2 +- .../Set-CIPPDBCacheIntuneReusableSettings.ps1 | 2 +- .../DBCache/Set-CIPPDBCacheIntuneScripts.ps1 | 2 +- .../Invoke-CIPPStandardAutoAddProxy.ps1 | 30 +++++++++---------- ...oke-CIPPStandardEmptyFilterIPAllowList.ps1 | 2 +- ...nvoke-CIPPStandardEnforcePrivateGroups.ps1 | 3 +- .../Invoke-CIPPStandardExternalMFATrusted.ps1 | 2 +- .../Standards/Invoke-CIPPStandardTeamsZAP.ps1 | 2 +- 11 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 index 97696ddf8b52..d3dd14ad33fa 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAppProtectionPolicies.ps1 @@ -7,7 +7,7 @@ function Set-CIPPDBCacheIntuneAppProtectionPolicies { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneAppProtectionPoliciesCache' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneAppProtectionPoliciesCache' -TenantFilter $TenantFilter -Preset Intune -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Intune license, skipping app protection policies cache' -sev Debug return diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 index 35b802197442..811f2af821d3 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneApplications.ps1 @@ -7,7 +7,7 @@ function Set-CIPPDBCacheIntuneApplications { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneApplicationsCache' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneApplicationsCache' -TenantFilter $TenantFilter -Preset Intune -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Intune license, skipping applications cache' -sev Debug return diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 index 6edfc3d9704e..882021cfb4ea 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneAssignmentFilters.ps1 @@ -7,7 +7,7 @@ function Set-CIPPDBCacheIntuneAssignmentFilters { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneAssignmentFiltersCache' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneAssignmentFiltersCache' -TenantFilter $TenantFilter -Preset Intune -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Intune license, skipping assignment filters cache' -sev Debug return diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 index 6f0a7770b308..80256d0ce92c 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneCompliancePolicies.ps1 @@ -7,7 +7,7 @@ function Set-CIPPDBCacheIntuneCompliancePolicies { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneCompliancePoliciesCache' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneCompliancePoliciesCache' -TenantFilter $TenantFilter -Preset Intune -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Intune license, skipping compliance policies cache' -sev Debug return diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 index e27fe5770dc4..77a964147403 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneReusableSettings.ps1 @@ -7,7 +7,7 @@ function Set-CIPPDBCacheIntuneReusableSettings { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneReusableSettingsCache' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneReusableSettingsCache' -TenantFilter $TenantFilter -Preset Intune -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Intune license, skipping reusable settings cache' -sev Debug return diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 index 47a074116886..8fb780b7d4ff 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheIntuneScripts.ps1 @@ -7,7 +7,7 @@ function Set-CIPPDBCacheIntuneScripts { ) try { - $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneScriptsCache' -TenantFilter $TenantFilter -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') -SkipLog + $TestResult = Test-CIPPStandardLicense -StandardName 'IntuneScriptsCache' -TenantFilter $TenantFilter -Preset Intune -SkipLog if ($TestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have Intune license, skipping scripts cache' -sev Debug return diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1 index efb8efdee4a3..6acdb097014f 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAutoAddProxy.ps1 @@ -36,7 +36,7 @@ function Invoke-CIPPStandardAutoAddProxy { $QueueItem ) - $TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchive' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'AutoArchive' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true } @@ -66,13 +66,13 @@ function Invoke-CIPPStandardAutoAddProxy { $MissingProxies = 0 foreach ($Domain in $Domains) { $ProcessMailboxes = @($AllMailboxes | Where-Object { - $AllAddresses = @($_.primarySmtpAddress) - if (-not [string]::IsNullOrWhiteSpace($_.AdditionalEmailAddresses)) { - $AllAddresses += @($_.AdditionalEmailAddresses -split ',\s*') - } - $HasDomain = $AllAddresses | Where-Object { $_ -like "*@$Domain" } - -not $HasDomain - }) + $AllAddresses = @($_.primarySmtpAddress) + if (-not [string]::IsNullOrWhiteSpace($_.AdditionalEmailAddresses)) { + $AllAddresses += @($_.AdditionalEmailAddresses -split ',\s*') + } + $HasDomain = $AllAddresses | Where-Object { $_ -like "*@$Domain" } + -not $HasDomain + }) $MissingProxies += $ProcessMailboxes.Count } @@ -104,13 +104,13 @@ function Invoke-CIPPStandardAutoAddProxy { } else { foreach ($Domain in $Domains) { $ProcessMailboxes = @($AllMailboxes | Where-Object { - $AllAddresses = @($_.primarySmtpAddress) - if (-not [string]::IsNullOrWhiteSpace($_.AdditionalEmailAddresses)) { - $AllAddresses += @($_.AdditionalEmailAddresses -split ',\s*') - } - $HasDomain = $AllAddresses | Where-Object { $_ -like "*@$Domain" } - -not $HasDomain - }) + $AllAddresses = @($_.primarySmtpAddress) + if (-not [string]::IsNullOrWhiteSpace($_.AdditionalEmailAddresses)) { + $AllAddresses += @($_.AdditionalEmailAddresses -split ',\s*') + } + $HasDomain = $AllAddresses | Where-Object { $_ -like "*@$Domain" } + -not $HasDomain + }) $bulkRequest = foreach ($Mailbox in $ProcessMailboxes) { if ([string]::IsNullOrWhiteSpace($Mailbox.UPN)) { continue } diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEmptyFilterIPAllowList.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEmptyFilterIPAllowList.ps1 index 834a84e735ec..f8bb29881803 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEmptyFilterIPAllowList.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEmptyFilterIPAllowList.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardEmptyFilterIPAllowList { param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EmptyFilterIPAllowList' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'EmptyFilterIPAllowList' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true } try { diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnforcePrivateGroups.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnforcePrivateGroups.ps1 index 6318cb73f1c1..18a682fbe48b 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnforcePrivateGroups.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardEnforcePrivateGroups.ps1 @@ -42,8 +42,7 @@ function Invoke-CIPPStandardEnforcePrivateGroups { param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'EnforcePrivateGroups' -TenantFilter $Tenant ` - -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + $TestResult = Test-CIPPStandardLicense -StandardName 'EnforcePrivateGroups' -TenantFilter $Tenant -Preset SharePoint if ($TestResult -eq $false) { return $true } # Parse exclusion keywords from settings diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 index 1ab15ee45e88..d87b9e22c26f 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 @@ -36,7 +36,7 @@ function Invoke-CIPPStandardExternalMFATrusted { #> param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'ExternalMFATrusted' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + $TestResult = Test-CIPPStandardLicense -StandardName 'ExternalMFATrusted' -TenantFilter $Tenant -Preset Entra if ($TestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsZAP.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsZAP.ps1 index 3b8b0ea9b22d..7b3c19c146d2 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsZAP.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardTeamsZAP.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardTeamsZAP { param($Tenant, $Settings) - $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsZAP' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') + $TestResult = Test-CIPPStandardLicense -StandardName 'TeamsZAP' -TenantFilter $Tenant -Preset Exchange if ($TestResult -eq $false) { return $true } try { From bc4abb53441d530ef84896da8f2a8414b744840c Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Mon, 11 May 2026 22:23:28 +0200 Subject: [PATCH 03/28] feat: add DefenderForOffice365 preset to license tests --- .../Functions/Test-CIPPStandardLicense.ps1 | 20 +++++++++++-------- ...Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 | 2 +- .../Invoke-CIPPStandardAtpPolicyForO365.ps1 | 2 +- ...nvoke-CIPPStandardSafeAttachmentPolicy.ps1 | 6 +++--- .../Invoke-CIPPStandardSafeLinksPolicy.ps1 | 2 +- ...ke-CIPPStandardSafeLinksTemplatePolicy.ps1 | 2 +- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 b/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 index f6e745ddd3e9..4a998a6e6a9f 100644 --- a/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 +++ b/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 @@ -34,7 +34,7 @@ function Test-CIPPStandardLicense { [string[]]$RequiredCapabilities, [Parameter(Mandatory = $false)] - [ValidateSet('Exchange', 'SharePoint', 'Intune', 'Entra', 'EntraP2', 'Teams', 'Compliance')] + [ValidateSet('Exchange', 'SharePoint', 'Intune', 'Entra', 'EntraP2', 'Teams', 'Compliance', 'DefenderForOffice365')] [string[]]$Preset, [Parameter(Mandatory = $false)] @@ -42,17 +42,21 @@ function Test-CIPPStandardLicense { ) $Presets = @{ - Exchange = @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', + Exchange = @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_S_STANDARD_GOV', 'EXCHANGE_S_ENTERPRISE_GOV', 'EXCHANGE_LITE') - SharePoint = @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', + SharePoint = @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'SHAREPOINTENTERPRISE_EDU', 'SHAREPOINTENTERPRISE_GOV', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') - Intune = @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') - Entra = @('AAD_PREMIUM', 'AAD_PREMIUM_P2') - EntraP2 = @('AAD_PREMIUM_P2') - Teams = @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') - Compliance = @('RMS_S_PREMIUM', 'RMS_S_PREMIUM2', 'MIP_S_CLP1', 'MIP_S_CLP2') + Intune = @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') + Entra = @('AAD_PREMIUM', 'AAD_PREMIUM_P2') + EntraP2 = @('AAD_PREMIUM_P2') + Teams = @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1', 'Teams_Room_Standard') + Compliance = @('RMS_S_PREMIUM', 'RMS_S_PREMIUM2', 'MIP_S_CLP1', 'MIP_S_CLP2') + DefenderForOffice365 = @( + 'ATP_ENTERPRISE', 'ATP_ENTERPRISE_GOV', + 'THREAT_INTELLIGENCE', 'THREAT_INTELLIGENCE_GOV' + ) } if ((!$Preset -or $Preset.Count -eq 0) -and (!$RequiredCapabilities -or $RequiredCapabilities.Count -eq 0)) { diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 index 5476d515bbe9..0810cf69ade7 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheExoPresetSecurityPolicy.ps1 @@ -19,7 +19,7 @@ function Set-CIPPDBCacheExoPresetSecurityPolicy { try { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Caching Exchange Preset Security Policies' -sev Debug - $MDOTestResult = Test-CIPPStandardLicense -StandardName 'ExoPresetSecurityPolicy' -TenantFilter $TenantFilter -RequiredCapabilities @('ATP_ENTERPRISE', 'ATP_ENTERPRISE_GOV', 'THREAT_INTELLIGENCE') -SkipLog + $MDOTestResult = Test-CIPPStandardLicense -StandardName 'ExoPresetSecurityPolicy' -TenantFilter $TenantFilter -Preset DefenderForOffice365 -SkipLog if ($MDOTestResult -eq $false) { Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Skipping Preset Security Policy cache: tenant lacks Microsoft Defender for Office 365' -sev Debug return diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 index a7aa4b0982d4..e89461cf1430 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 @@ -47,7 +47,7 @@ function Invoke-CIPPStandardAtpPolicyForO365 { return $true } #we're done. - $MDOTestResult = Test-CIPPStandardLicense -StandardName 'AtpPolicyForO365' -TenantFilter $Tenant -RequiredCapabilities @('ATP_ENTERPRISE', 'ATP_ENTERPRISE_GOV', 'THREAT_INTELLIGENCE') + $MDOTestResult = Test-CIPPStandardLicense -StandardName 'AtpPolicyForO365' -TenantFilter $Tenant -Preset DefenderForOffice365 if ($MDOTestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 index 89939e00d904..61b515a26761 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 @@ -53,7 +53,7 @@ function Invoke-CIPPStandardSafeAttachmentPolicy { return $true } #we're done. - $MDOTestResult = Test-CIPPStandardLicense -StandardName 'SafeAttachmentPolicy' -TenantFilter $Tenant -RequiredCapabilities @('ATP_ENTERPRISE', 'ATP_ENTERPRISE_GOV', 'THREAT_INTELLIGENCE') + $MDOTestResult = Test-CIPPStandardLicense -StandardName 'SafeAttachmentPolicy' -TenantFilter $Tenant -Preset DefenderForOffice365 if ($MDOTestResult -eq $false) { return $true @@ -113,8 +113,8 @@ function Invoke-CIPPStandardSafeAttachmentPolicy { $AcceptedDomains = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AcceptedDomain' $RuleState = $AllSafeAttachmentRule | - Where-Object -Property Name -EQ $RuleName | - Select-Object Name, SafeAttachmentPolicy, Priority, RecipientDomainIs + Where-Object -Property Name -EQ $RuleName | + Select-Object Name, SafeAttachmentPolicy, Priority, RecipientDomainIs $RuleStateIsCorrect = ($RuleState.Name -eq $RuleName) -and ($RuleState.SafeAttachmentPolicy -eq $PolicyName) -and diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 index 59012f028253..3e10dc15f1d8 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 @@ -67,7 +67,7 @@ function Invoke-CIPPStandardSafeLinksPolicy { return $true } #we're done. - $MDOTestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksPolicy' -TenantFilter $Tenant -RequiredCapabilities @('ATP_ENTERPRISE', 'ATP_ENTERPRISE_GOV', 'THREAT_INTELLIGENCE') + $MDOTestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksPolicy' -TenantFilter $Tenant -Preset DefenderForOffice365 if ($MDOTestResult -eq $false) { return $true diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 index 2f671e1b4db0..475de0053c67 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 @@ -45,7 +45,7 @@ function Invoke-CIPPStandardSafeLinksTemplatePolicy { Write-LogMessage -API 'Standards' -tenant $Tenant -message "Processing SafeLinks template with settings: $($Settings | ConvertTo-Json -Compress)" -sev Debug - $MDOTestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksTemplatePolicy' -TenantFilter $Tenant -RequiredCapabilities @('ATP_ENTERPRISE', 'ATP_ENTERPRISE_GOV', 'THREAT_INTELLIGENCE') + $MDOTestResult = Test-CIPPStandardLicense -StandardName 'SafeLinksTemplatePolicy' -TenantFilter $Tenant -Preset DefenderForOffice365 if ($MDOTestResult -eq $false) { return } #tenant lacks Microsoft Defender for Office 365 — Test-CIPPStandardLicense logs and sets LicenseAvailable=false. From f5702f41f1fa33ab7cfa8be9989c913d88d7d880 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 11 May 2026 23:57:27 -0400 Subject: [PATCH 04/28] feat: Enhance Invoke-ListIntuneTemplates to include usage tracking for standards templates --- .../MEM/Invoke-ListIntuneTemplates.ps1 | 109 +++++++++++++----- 1 file changed, 83 insertions(+), 26 deletions(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 index 2b0da1d4a696..3120a5d863c4 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 @@ -48,37 +48,94 @@ function Invoke-ListIntuneTemplates { } } | Sort-Object -Property displayName + + # Build a lookup of which standards templates reference each Intune template (by GUID or package) + $UsageByGuid = @{} + $UsageByPackage = @{} + $StdTemplates = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'StandardsTemplateV2'" + foreach ($StdRaw in $StdTemplates) { + try { + $StdData = $StdRaw.JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue + if (-not $StdData -or -not $StdData.standards) { continue } + $IsDrift = $StdData.type -eq 'drift' + $StdInfo = [pscustomobject]@{ + templateName = $StdData.templateName + templateId = $StdRaw.RowKey + isDrift = $IsDrift + cippLink = "/tenant/standards/templates/template?id=$($StdRaw.RowKey)$(if ($IsDrift) { '&type=drift' })" + } + $IntuneEntries = $StdData.standards.IntuneTemplate + if (-not $IntuneEntries) { continue } + $Items = if ($IntuneEntries -is [System.Collections.IEnumerable] -and $IntuneEntries -isnot [string]) { $IntuneEntries } else { @($IntuneEntries) } + foreach ($Item in $Items) { + if ($Item.TemplateList.value) { + $Guid = $Item.TemplateList.value + if (-not $UsageByGuid.ContainsKey($Guid)) { $UsageByGuid[$Guid] = [System.Collections.Generic.List[object]]::new() } + $UsageByGuid[$Guid].Add($StdInfo) + } + if ($Item.'TemplateList-Tags'.value) { + $Pkg = $Item.'TemplateList-Tags'.value + if (-not $UsageByPackage.ContainsKey($Pkg)) { $UsageByPackage[$Pkg] = [System.Collections.Generic.List[object]]::new() } + $UsageByPackage[$Pkg].Add($StdInfo) + } + } + } catch {} + } + + # Attach usage list to each Intune template + foreach ($Tpl in $Templates) { + $Usage = [System.Collections.Generic.List[object]]::new() + if ($Tpl.GUID -and $UsageByGuid.ContainsKey($Tpl.GUID)) { + foreach ($U in $UsageByGuid[$Tpl.GUID]) { + $Entry = $U | Select-Object * + $Entry | Add-Member -NotePropertyName 'matchType' -NotePropertyValue 'direct' -Force + $Usage.Add($Entry) + } + } + if ($Tpl.package -and $UsageByPackage.ContainsKey($Tpl.package)) { + foreach ($U in $UsageByPackage[$Tpl.package]) { + # Avoid duplicates when the same template matches both GUID and package + if (-not ($Usage | Where-Object { $_.templateId -eq $U.templateId })) { + $Entry = $U | Select-Object * + $Entry | Add-Member -NotePropertyName 'matchType' -NotePropertyValue 'package' -Force + $Entry | Add-Member -NotePropertyName 'package' -NotePropertyValue $Tpl.package -Force + $Usage.Add($Entry) + } + } + } + $Tpl | Add-Member -NotePropertyName 'usedInTemplates' -NotePropertyValue @($Usage) -Force + } } else { if ($Request.query.mode -eq 'Tag') { #when the mode is tag, show all the potential tags, return the object with: label: tag, value: tag, count: number of templates with that tag, unique only $Templates = @($RawTemplates | Where-Object { $_.Package } | Group-Object -Property Package | ForEach-Object { - $package = $_.Name - $packageTemplates = @($_.Group) - $templateCount = $packageTemplates.Count - [pscustomobject]@{ - label = "$($package) ($templateCount Templates)" - value = $package - type = 'tag' - templateCount = $templateCount - templates = @($packageTemplates | ForEach-Object { - try { - $JSONData = $_.JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue - $data = $JSONData.RAWJson | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue - $data | Add-Member -NotePropertyName 'displayName' -NotePropertyValue $JSONData.Displayname -Force - $data | Add-Member -NotePropertyName 'description' -NotePropertyValue $JSONData.Description -Force - $data | Add-Member -NotePropertyName 'Type' -NotePropertyValue $JSONData.Type -Force - $data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.RowKey -Force - $data | Add-Member -NotePropertyName 'package' -NotePropertyValue $_.Package -Force - $data | Add-Member -NotePropertyName 'source' -NotePropertyValue $_.Source -Force - $data | Add-Member -NotePropertyName 'isSynced' -NotePropertyValue (![string]::IsNullOrEmpty($_.SHA)) -Force - $data | Add-Member -NotePropertyName 'reusableSettings' -NotePropertyValue $JSONData.ReusableSettings -Force - $data - } catch { + $package = $_.Name + $packageTemplates = @($_.Group) + $templateCount = $packageTemplates.Count + [pscustomobject]@{ + label = "$($package) ($templateCount Templates)" + value = $package + type = 'tag' + templateCount = $templateCount + templates = @($packageTemplates | ForEach-Object { + try { + $JSONData = $_.JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue + $data = $JSONData.RAWJson | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue + $data | Add-Member -NotePropertyName 'displayName' -NotePropertyValue $JSONData.Displayname -Force + $data | Add-Member -NotePropertyName 'description' -NotePropertyValue $JSONData.Description -Force + $data | Add-Member -NotePropertyName 'Type' -NotePropertyValue $JSONData.Type -Force + $data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.RowKey -Force + $data | Add-Member -NotePropertyName 'package' -NotePropertyValue $_.Package -Force + $data | Add-Member -NotePropertyName 'source' -NotePropertyValue $_.Source -Force + $data | Add-Member -NotePropertyName 'isSynced' -NotePropertyValue (![string]::IsNullOrEmpty($_.SHA)) -Force + $data | Add-Member -NotePropertyName 'reusableSettings' -NotePropertyValue $JSONData.ReusableSettings -Force + $data + } catch { - } - }) - } - } | Sort-Object -Property label) + } + }) + } + } | Sort-Object -Property label) } else { $Templates = $RawTemplates.JSON | ForEach-Object { try { ConvertFrom-Json -InputObject $_ -Depth 100 -ErrorAction SilentlyContinue } catch {} } From ecbc9a50ac0584e0d0ce59259214ebe6d9cf525a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 11 May 2026 23:58:56 -0400 Subject: [PATCH 05/28] fix: Add error handling for missing standard functions in Push-CIPPStandard --- .../Activity Triggers/Standards/Push-CIPPStandard.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 index e7df2cc7f723..1e52ae35375a 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Standards/Push-CIPPStandard.ps1 @@ -40,6 +40,12 @@ function Push-CIPPStandard { $StandardInfo.ConditionalAccessTemplateId = $Item.Settings.TemplateList.value } + if (-not (Get-Command -Name $FunctionName -Module CIPPStandards -ErrorAction SilentlyContinue)) { + Write-LogMessage -tenant $Tenant -message "The standard $Standard was not found. This may have been deprecated or replaced with a new standard." -sev 'Warning' -API 'Standards' + Write-Warning "Function $FunctionName not found" + return + } + # Initialize AsyncLocal storage for thread-safe per-invocation context # Uses $global: so Write-LogMessage (CIPPCore module) can read it across module boundaries if (-not $global:CippStandardInfoStorage) { From 57b7de1f31bcffa3ebc3ba1f05d5d47fa1b6bffe Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 12 May 2026 00:07:30 -0400 Subject: [PATCH 06/28] fix: Rename 'usedInTemplates' property to 'usage' for clarity in Invoke-ListIntuneTemplates --- .../HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 index 3120a5d863c4..4944d2f2f7cb 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-ListIntuneTemplates.ps1 @@ -103,7 +103,7 @@ function Invoke-ListIntuneTemplates { } } } - $Tpl | Add-Member -NotePropertyName 'usedInTemplates' -NotePropertyValue @($Usage) -Force + $Tpl | Add-Member -NotePropertyName 'usage' -NotePropertyValue @($Usage) -Force } } else { if ($Request.query.mode -eq 'Tag') { From 7fd7d097882ad258695905e15c19addd288d493c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 12 May 2026 14:28:11 +0200 Subject: [PATCH 07/28] fixes sharepoint response stuff --- .../DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 | 14 +++++++++++--- .../Teams-Sharepoint/Invoke-ListSites.ps1 | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 index 90ab81351b7a..5722f962b7a0 100644 --- a/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 +++ b/Modules/CIPPDB/Public/DBCache/Set-CIPPDBCacheSharePointSiteUsage.ps1 @@ -37,9 +37,17 @@ function Set-CIPPDBCacheSharePointSiteUsage { $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)) - $UsageRows = @(($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)) + $UsageRows = @(($UsageJson | ConvertFrom-Json).value) + } else { + $UsageRows = @($UsageBody.value) + } # Ensure a stable row key for usage rows. foreach ($UsageRow in $UsageRows) { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 index d8aae26f5998..0335d3357557 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 @@ -78,9 +78,17 @@ function Invoke-ListSites { $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)) - $Usage = ($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)) + $Usage = ($UsageJson | ConvertFrom-Json).value + } else { + $Usage = @($UsageBody.value) + } $GraphRequest = foreach ($Site in $Sites) { $SiteUsage = $Usage | Where-Object { $_.siteId -eq $Site.sharepointIds.siteId } From 23c8994da9a523a6fbce594181f0c74e5fb1a69e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 12 May 2026 14:33:28 +0200 Subject: [PATCH 08/28] fixes defaultr_hidden vs hidden #5990 --- .../Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 index 413c5b6f9594..84f5099dd7b7 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDeployCheckChromeExtension.ps1 @@ -137,7 +137,7 @@ function Invoke-CIPPStandardDeployCheckChromeExtension { ExtSettingsKey = "HKLM:\SOFTWARE\Policies\Microsoft\Edge\ExtensionSettings\`$edgeExtensionId" ToolbarProp = 'toolbar_state' ToolbarPinned = 'force_shown' - ToolbarUnpinned = 'hidden' + ToolbarUnpinned = 'default_hidden' } ) From ad0d096c727ec2b881f0ea8a4e5ff33a8950f0a8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 12 May 2026 16:03:49 +0200 Subject: [PATCH 09/28] OneDrive Sharing disable --- .../Public/Invoke-CIPPOffboardingJob.ps1 | 11 ++++ .../Public/Set-CIPPOneDriveSharing.ps1 | 65 +++++++++++++++++++ .../Users/Invoke-ExecBECRemediate.ps1 | 15 +++++ .../Invoke-ExecSetOneDriveSharing.ps1 | 37 +++++++++++ 4 files changed, 128 insertions(+) create mode 100644 Modules/CIPPCore/Public/Set-CIPPOneDriveSharing.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetOneDriveSharing.ps1 diff --git a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 index 10f00534abae..2724877e0557 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPOffboardingJob.ps1 @@ -165,6 +165,17 @@ function Invoke-CIPPOffboardingJob { Headers = $Headers } } + @{ + Condition = { $Options.DisableOneDriveSharing -eq $true } + Cmdlet = 'Set-CIPPOneDriveSharing' + Parameters = @{ + TenantFilter = $TenantFilter + UserId = $Username + SharingCapability = 'Disabled' + APIName = $APIName + Headers = $Headers + } + } @{ Condition = { $Options.AccessNoAutomap.Count -gt 0 } Cmdlet = 'Set-CIPPMailboxAccess' diff --git a/Modules/CIPPCore/Public/Set-CIPPOneDriveSharing.ps1 b/Modules/CIPPCore/Public/Set-CIPPOneDriveSharing.ps1 new file mode 100644 index 000000000000..8b0c8d4ed439 --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPOneDriveSharing.ps1 @@ -0,0 +1,65 @@ +function Set-CIPPOneDriveSharing { + [CmdletBinding()] + param ( + $UserId, + $TenantFilter, + [ValidateSet('Disabled', 'ExternalUserSharingOnly', 'ExternalUserAndGuestSharing', 'ExistingExternalUserSharingOnly')] + [string]$SharingCapability = 'Disabled', + $APIName = 'Set OneDrive Sharing', + $Headers, + $URL + ) + + $SharingCapabilityMap = @{ + 'Disabled' = 0 + 'ExternalUserSharingOnly' = 1 + 'ExternalUserAndGuestSharing' = 2 + 'ExistingExternalUserSharingOnly' = 3 + } + $EnumValue = $SharingCapabilityMap[$SharingCapability] + + try { + if (!$URL) { + #Grab url, get root level, strip /documents + $URL = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserId)/drive" -asapp $true -tenantid $TenantFilter).webUrl -replace '/documents', '' + } + + $SharePointInfo = Get-SharePointAdminLink -Public $false -tenantFilter $TenantFilter + + $XML = @" + + + + $EnumValue + + + + + + + $URL + false + + + + + +"@ + $Request = New-GraphPostRequest -scope "$($SharePointInfo.AdminUrl)/.default" -tenantid $TenantFilter -Uri "$($SharePointInfo.AdminUrl)/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' + + if (!$Request.ErrorInfo.ErrorMessage) { + $Message = "Successfully set OneDrive sharing to '$SharingCapability' for $UserId ($URL)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev Info -tenant $TenantFilter + return $Message + } else { + $Message = "Failed to set OneDrive sharing for $UserId : $($Request.ErrorInfo.ErrorMessage)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev Error -tenant $TenantFilter + return $Message + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Message = "Failed to set OneDrive sharing for $UserId. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev Error -tenant $TenantFilter -LogData $ErrorMessage + throw $Message + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 index 29bf889d3773..f9516ce5ab29 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 @@ -180,6 +180,21 @@ function Invoke-ExecBECRemediate { }) } + # Step 6: Disable OneDrive Sharing + $Step = 'Disable OneDrive Sharing' + try { + $OneDriveResult = Set-CIPPOneDriveSharing -UserId $Username -TenantFilter $TenantFilter -SharingCapability 'Disabled' -APIName $APIName -Headers $Headers + $AllResults.Add([pscustomobject]@{ + resultText = $OneDriveResult + state = if ($OneDriveResult -like '*Successfully*') { 'success' } else { 'error' } + }) + } catch { + $AllResults.Add([pscustomobject]@{ + resultText = "Failed to disable OneDrive sharing: $($_.Exception.Message)" + state = 'error' + }) + } + $StatusCode = [HttpStatusCode]::OK Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username" -sev 'Info' -LogData @($AllResults) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetOneDriveSharing.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetOneDriveSharing.ps1 new file mode 100644 index 000000000000..3bffa6f834d0 --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetOneDriveSharing.ps1 @@ -0,0 +1,37 @@ +function Invoke-ExecSetOneDriveSharing { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Teams.SharePoint.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $TenantFilter = $Request.Body.tenantFilter + $UserPrincipalName = $Request.Body.UPN + $SharingCapability = $Request.Body.SharingCapability.value ?? $Request.Body.SharingCapability + + try { + $Result = Set-CIPPOneDriveSharing ` + -UserId $UserPrincipalName ` + -TenantFilter $TenantFilter ` + -SharingCapability $SharingCapability ` + -APIName $APIName ` + -Headers $Request.Headers + + $Body = @{ Results = $Result } + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -tenant $TenantFilter -message "Failed to set OneDrive sharing: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $Body = @{ Results = "Failed to set OneDrive sharing: $($ErrorMessage.NormalizedError)" } + $StatusCode = [HttpStatusCode]::InternalServerError + } + + return [HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $Body + } +} From 2869564fb904deced3eb5c076f19519db12c3769 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 12 May 2026 16:32:19 +0200 Subject: [PATCH 10/28] Add AlertUserReportPhising --- Config/SAMManifest.json | 4 ++ .../Get-CIPPAlertUserReportedPhishing.ps1 | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertUserReportedPhishing.ps1 diff --git a/Config/SAMManifest.json b/Config/SAMManifest.json index 87e3978f2483..ab325ce85ae1 100644 --- a/Config/SAMManifest.json +++ b/Config/SAMManifest.json @@ -571,6 +571,10 @@ { "id": "a94a502d-0281-4d15-8cd2-682ac9362c4c", "type": "Role" + }, + { + "id": "d72bdbf4-a59b-405c-8b04-5995895819ac", + "type": "Role" } ] }, diff --git a/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertUserReportedPhishing.ps1 b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertUserReportedPhishing.ps1 new file mode 100644 index 000000000000..60138097fc7b --- /dev/null +++ b/Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertUserReportedPhishing.ps1 @@ -0,0 +1,47 @@ +function Get-CIPPAlertUserReportedPhishing { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $false)] + [Alias('input')] + $InputValue, + $TenantFilter + ) + + try { + [int]$HoursBack = if ($InputValue.HoursBack) { [int]$InputValue.HoursBack } else { 24 } + $Since = (Get-Date).AddHours(-$HoursBack).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + + $Submissions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/security/threatSubmission/emailThreats?`$filter=createdDateTime ge $Since" -tenantid $TenantFilter -AsApp $true + + $AlertData = foreach ($Submission in $Submissions) { + # Only include user-reported submissions + if ($Submission.source -ne 'user') { continue } + + [PSCustomObject]@{ + ReportedBy = $Submission.createdBy.user.displayName + ReporterEmail = $Submission.createdBy.user.email + Sender = $Submission.sender + Subject = $Submission.emailSubject + Category = $Submission.category + ReceivedDateTime = $Submission.receivedDateTime + ReportedAt = $Submission.createdDateTime + Status = $Submission.status + ResultCategory = $Submission.result.category + ResultDetail = $Submission.result.detail + InternetMsgId = $Submission.internetMessageId + SubmissionId = $Submission.id + Tenant = $TenantFilter + } + } + if ($AlertData) { + Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-AlertMessage -message "User-reported phishing alert failed for $($TenantFilter): $($ErrorMessage.NormalizedError)" -tenant $TenantFilter -LogData $ErrorMessage + } +} From ddb498fe81df0262aa834db9617ed08dad38aeed Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 12 May 2026 11:32:15 -0400 Subject: [PATCH 11/28] chore: bump version to 10.4.5 --- host.json | 2 +- version_latest.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/host.json b/host.json index 473ab1bfce11..5635adf20ae2 100644 --- a/host.json +++ b/host.json @@ -16,7 +16,7 @@ "distributedTracingEnabled": false, "version": "None" }, - "defaultVersion": "10.4.4", + "defaultVersion": "10.4.5", "versionMatchStrategy": "Strict", "versionFailureStrategy": "Fail" } diff --git a/version_latest.txt b/version_latest.txt index 622a6f75b792..aa725fbb1929 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -10.4.4 +10.4.5 From 35437558e4699479e303f4245fe14d3e258cdba2 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 12 May 2026 23:39:36 +0800 Subject: [PATCH 12/28] Update Viva standard --- .../Public/Standards/Invoke-CIPPStandardDisableViva.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 index 542d8edc54cb..6cf276f3ce92 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardDisableViva.ps1 @@ -46,7 +46,7 @@ function Invoke-CIPPStandardDisableViva { } else { try { # TODO This does not work without Global Admin permissions for some reason. Throws an "EXCEPTION: Tenant admin role is required" error. -Bobby - New-GraphPOSTRequest -Uri "https://graph.microsoft.com/beta/organization/$Tenant/settings/peopleInsights" -tenantid $Tenant -Type PATCH -Body '{"isEnabledInOrganization": false}' -ContentType 'application/json' + New-GraphPOSTRequest -Uri "https://graph.microsoft.com/beta/organization/$Tenant/settings/peopleInsights" -tenantid $Tenant -Type PATCH -Body '{"isEnabledInOrganization": false}' -ContentType 'application/json' -AsApp $true Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Disabled Viva insights' -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message From 781930205c67fc17a5ca887755361987a407012a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 12 May 2026 12:20:47 -0400 Subject: [PATCH 13/28] fix name --- .../Activity Triggers/Push-OrchestratorBatchItems.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-OrchestratorBatchItems.ps1 b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-OrchestratorBatchItems.ps1 index 9e8fe4561a1d..df59afa91619 100644 --- a/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-OrchestratorBatchItems.ps1 +++ b/Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-OrchestratorBatchItems.ps1 @@ -11,8 +11,8 @@ function Push-OrchestratorBatchItems { $Entities = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$($Item.Parameters.BatchId)'" $BatchItems = [system.Collections.Generic.List[object]]::new() $Entities | ForEach-Object { - $Item = $_.BatchItem | ConvertFrom-Json - $BatchItems.Add($Item) + $BatchItem = $_.BatchItem | ConvertFrom-Json + $BatchItems.Add($BatchItem) } Write-Information "Retrieved $($BatchItems.Count) batch items for BatchId: $($Item.Parameters.BatchId)" } else { From 785e71c530a39b46f56cea7c598f7d609d8a8518 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 12 May 2026 18:26:46 +0200 Subject: [PATCH 14/28] fix user select --- .../Public/Set-CIPPDefaultAPDeploymentProfile.ps1 | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index 7fa56473ec0b..770b24be8003 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -24,9 +24,7 @@ function Set-CIPPDefaultAPDeploymentProfile { # 'user-select' -> empty string (lets user choose during OOBE) # 'os-default' or $null -> $null (uses operating system default) # Specific tag (e.g. 'en-US') -> passed through as-is - if ($Language -eq 'user-select') { - $Language = '' - } elseif ($Language -eq 'os-default' -or $null -eq $Language) { + if ($Language -eq 'os-default') { $Language = $null } @@ -48,15 +46,19 @@ function Set-CIPPDefaultAPDeploymentProfile { 'displayName' = "$($DisplayName)" 'description' = "$($Description)" 'deviceNameTemplate' = "$($DeviceNameTemplate)" - 'locale' = $Language + 'locale' = "$($Language)" 'preprovisioningAllowed' = $([bool]($AllowWhiteGlove)) 'deviceType' = 'windowsPc' 'hardwareHashExtractionEnabled' = $([bool]($CollectHash)) 'roleScopeTagIds' = @() 'outOfBoxExperienceSetting' = $OutOfBoxSetting } + if ($Language -eq 'user-select') { + #Add language query to body only if user-select, as Graph API treats empty string differently than null + $ObjBody.locale = '' + $ObjBody | Add-Member -MemberType NoteProperty -Name 'language' -Value '' -Force + } $Body = ConvertTo-Json -InputObject $ObjBody -Depth 10 - Write-Information $Body $Profiles = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles' -tenantid $TenantFilter | Where-Object -Property displayName -EQ $DisplayName From 2dbc480336773976fb1140194c4c5de348afe170 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 13 May 2026 04:19:45 +0800 Subject: [PATCH 15/28] auth configs --- Config/FeatureFlags.json | 19 + .../Public/Authentication/Get-CippApiAuth.ps1 | 45 +- .../Authentication/Initialize-CIPPAuth.ps1 | 100 +++- .../Public/Authentication/New-CIPPSSOApp.ps1 | 156 +++++ .../Remove-CIPPMigrationAppSetting.ps1 | 63 +++ .../Authentication/Set-CIPPSSOEasyAuth.ps1 | 182 ++++++ .../Public/Authentication/Set-CippApiAuth.ps1 | 175 ++++-- .../Public/Authentication/Test-CIPPAccess.ps1 | 39 +- .../Update-CIPPSSORedirectUri.ps1 | 126 +++++ .../Update-AppManagementPolicy.ps1 | 6 +- .../CIPP/Core/Invoke-ListFeatureFlags.ps1 | 13 +- .../CIPP/Settings/Invoke-ExecCIPPUsers.ps1 | 114 ++++ .../Invoke-ExecContainerManagement.ps1 | 173 ++++++ .../CIPP/Settings/Invoke-ListCIPPUsers.ps1 | 80 +++ .../CIPP/Setup/Invoke-ExecSSOSetup.ps1 | 531 ++++++++++++++++++ 15 files changed, 1748 insertions(+), 74 deletions(-) create mode 100644 Modules/CIPPCore/Public/Authentication/New-CIPPSSOApp.ps1 create mode 100644 Modules/CIPPCore/Public/Authentication/Remove-CIPPMigrationAppSetting.ps1 create mode 100644 Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 create mode 100644 Modules/CIPPCore/Public/Authentication/Update-CIPPSSORedirectUri.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCIPPUsers.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSSOSetup.ps1 diff --git a/Config/FeatureFlags.json b/Config/FeatureFlags.json index 61a49d453458..858f8bc58bcf 100644 --- a/Config/FeatureFlags.json +++ b/Config/FeatureFlags.json @@ -20,5 +20,24 @@ "/tenant/standards/bpa-report/builder", "/tenant/standards/bpa-report/view" ] + }, + { + "Id": "SuperAdminNG", + "Name": "Super Admin", + "Description": "Additional super admin pages for CIPP instances (CIPP Users, SSO, Container management).", + "Enabled": false, + "AllowUserToggle": false, + "Timers": [], + "Endpoints": [ + "ExecCIPPUsers", + "ListCIPPUsers", + "ExecSSOSetup", + "ExecContainerManagement" + ], + "Pages": [ + "/cipp/advanced/super-admin/cipp-users", + "/cipp/advanced/super-admin/sso", + "/cipp/advanced/super-admin/container" + ] } ] diff --git a/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 index 1c5880fd8be1..6d822746d6cf 100644 --- a/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Get-CippApiAuth.ps1 @@ -4,27 +4,48 @@ function Get-CippApiAuth { [string]$FunctionAppName ) - $SubscriptionId = Get-CIPPAzFunctionAppSubId + $AuthSettings = $null - 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)" + # 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 + } + + # 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 -and $env:WEBSITE_AUTH_V2_CONFIG_JSON) { + # 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 }) + } + } + [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 + TenantID = $Issuer -replace 'https://sts.windows.net/', '' -replace 'https://login.microsoftonline.com/', '' -replace '/v2.0', '' + ClientIDs = $AllowedApps + Enabled = $AAD.enabled } } else { throw 'No auth settings found' diff --git a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 index bf0b55902563..f5f48e325add 100644 --- a/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Initialize-CIPPAuth.ps1 @@ -4,8 +4,10 @@ function Initialize-CIPPAuth { Bootstraps authentication state for CIPP. .DESCRIPTION - Loads SAM credentials from Key Vault (or DevSecrets table) - and auto-patches redirect URIs on the SAM app registration. + 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. #> [CmdletBinding()] param() @@ -41,7 +43,99 @@ function Initialize-CIPPAuth { try { Update-CIPPSAMRedirectUri } catch { - Write-Information "[Auth-Init] Redirect URI patch failed (non-fatal): $_" + Write-Information "[Auth-Init] SAM redirect URI patch failed (non-fatal): $_" + } + + try { + Update-CIPPSSORedirectUri + } catch { + Write-Information "[Auth-Init] SSO redirect URI patch failed (non-fatal): $_" + } + } + + # 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. + 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 { + $Configured = Set-CIPPSSOEasyAuth -AppId $env:CIPP_SSO_MIGRATION_APPID -MultiTenant $false -TenantId $env:TenantID -UseKvReferences -ImplicitAuth + if ($Configured) { + Write-Information '[Auth-Init] Implicit auth EasyAuth configured — requesting restart' + [Craft.Services.AppLifecycleBridge]::RequestRestart('Implicit auth EasyAuth configured with central migration app during warmup') + } + } catch { + Write-Information "[Auth-Init] Implicit auth EasyAuth setup failed (non-fatal): $_" + } + return $AuthState + } + + Write-Information '[Auth-Init] EasyAuth not configured — checking for SSO credentials...' + try { + $SSOAppId = $null + $SSOMultiTenant = $false + + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + $SSOAppId = $Secret.SSOAppId + $SSOMultiTenant = $Secret.SSOMultiTenant -eq 'True' + } elseif ($KVName) { + try { $SSOAppId = Get-CippKeyVaultSecret -VaultName $KVName -Name 'SSOAppId' -AsPlainText -ErrorAction Stop } catch { } + try { + $mtVal = Get-CippKeyVaultSecret -VaultName $KVName -Name 'SSOMultiTenant' -AsPlainText -ErrorAction Stop + $SSOMultiTenant = $mtVal -eq 'True' + } catch { } + } + + if ($SSOAppId) { + Write-Information "[Auth-Init] Found SSO AppId ($SSOAppId) — configuring EasyAuth via ARM" + $Configured = Set-CIPPSSOEasyAuth -AppId $SSOAppId -MultiTenant $SSOMultiTenant -TenantId $env:TenantID -UseKvReferences + if ($Configured) { + Write-Information '[Auth-Init] EasyAuth configured — requesting container restart' + [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') + } + } catch { + Write-Information "[Auth-Init] SSO EasyAuth setup failed (non-fatal): $_" + } + } + + # 5. 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): $_" } } diff --git a/Modules/CIPPCore/Public/Authentication/New-CIPPSSOApp.ps1 b/Modules/CIPPCore/Public/Authentication/New-CIPPSSOApp.ps1 new file mode 100644 index 000000000000..d8291eacd06b --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/New-CIPPSSOApp.ps1 @@ -0,0 +1,156 @@ +function New-CIPPSSOApp { + <# + .SYNOPSIS + Creates or updates the CIPP-SSO app registration for EasyAuth SSO migration. + .DESCRIPTION + Creates a new or updates an existing Entra ID app registration for CIPP-SSO with + openid, profile, and email delegated permissions. If ExistingAppId is provided, + looks up that specific app by clientId. If the app no longer exists in the tenant, + creates a new one. Generates a client secret and returns the details needed to + configure EasyAuth. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$RedirectUri, + + [Parameter(Mandatory = $false)] + [bool]$MultiTenant = $false, + + [Parameter(Mandatory = $false)] + [string]$ExistingAppId + ) + + $AppDisplayName = 'CIPP-SSO' + $CallbackUri = $RedirectUri.TrimEnd('/') + '/.auth/login/aad/callback' + $SignInAudience = if ($MultiTenant) { 'AzureADMultipleOrgs' } else { 'AzureADMyOrg' } + + # Microsoft Graph resource ID and delegated permission GUIDs + $GraphResourceId = '00000003-0000-0000-c000-000000000000' + $Permissions = @( + @{ id = '37f7f235-527c-4136-accd-4a02d197296e'; type = 'Scope' } # openid + @{ id = '14dad69e-099b-42c9-810b-d002981feec1'; type = 'Scope' } # profile + @{ id = '64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0'; type = 'Scope' } # email + ) + + # Look up existing app by stored AppId (not by name — supports multiple CIPP instances) + $ExistingApp = $null + if ($ExistingAppId) { + try { + $ExistingApp = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appId='$ExistingAppId')?`$select=id,appId,displayName,web" -NoAuthCheck $true -AsApp $true + Write-Information "[SSO-App] Found existing app by AppId: $ExistingAppId" + } catch { + Write-Information "[SSO-App] Stored AppId $ExistingAppId not found in tenant — will create new app" + } + } + + $AppObjectId = $null + $AppClientId = $null + $State = $null + + if ($ExistingApp) { + # Reuse existing app — patch redirect URIs and audience + $AppObjectId = $ExistingApp.id + $AppClientId = $ExistingApp.appId + $State = 'updated' + Write-Information "[SSO-App] Updating existing app: $AppClientId" + + $PatchBody = @{ + web = @{ + redirectUris = @($CallbackUri) + implicitGrantSettings = @{ enableIdTokenIssuance = $true } + } + signInAudience = $SignInAudience + requiredResourceAccess = @( + @{ + resourceAppId = $GraphResourceId + resourceAccess = $Permissions + } + ) + } | ConvertTo-Json -Depth 10 -Compress + + New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$AppObjectId" -body $PatchBody -type PATCH -NoAuthCheck $true -AsApp $true + } else { + # Create new app registration + $State = 'created' + Write-Information "[SSO-App] Creating new app registration: $AppDisplayName" + + $CreateBody = @{ + displayName = $AppDisplayName + signInAudience = $SignInAudience + web = @{ + redirectUris = @($CallbackUri) + implicitGrantSettings = @{ enableIdTokenIssuance = $true } + } + requiredResourceAccess = @( + @{ + resourceAppId = $GraphResourceId + resourceAccess = $Permissions + } + ) + } | ConvertTo-Json -Depth 10 -Compress + + $NewApp = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/applications' -body $CreateBody -type POST -NoAuthCheck $true -AsApp $true + $AppObjectId = $NewApp.id + $AppClientId = $NewApp.appId + Write-Information "[SSO-App] Created app: $AppClientId (objectId: $AppObjectId)" + + # Create service principal (idempotent — catch conflict) + $Attempt = 0 + $SpnCreated = $false + while ($Attempt -lt 3 -and -not $SpnCreated) { + try { + Start-Sleep -Seconds 2 + $SpnBody = @{ appId = $AppClientId } | ConvertTo-Json -Compress + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/servicePrincipals' -body $SpnBody -type POST -NoAuthCheck $true -AsApp $true | Out-Null + $SpnCreated = $true + Write-Information "[SSO-App] Service principal created for $AppClientId" + } catch { + $Attempt++ + Write-Information "[SSO-App] SPN creation attempt $Attempt failed (may already exist): $($_.Exception.Message)" + } + } + } + + # Handle app management policy exemption (same pattern as SAM setup) + try { + $PolicyStatus = Update-AppManagementPolicy -ApplicationId $AppClientId + Write-Information "[SSO-App] Policy exemption: $($PolicyStatus.PolicyAction)" + } catch { + Write-Warning "[SSO-App] App management policy update failed (secret creation may still work): $($_.Exception.Message)" + } + + # Create client secret with retry + $SecretText = $null + $SecretAttempt = 0 + $MaxSecretRetries = 5 + while ($SecretAttempt -lt $MaxSecretRetries -and -not $SecretText) { + try { + $PasswordBody = '{"passwordCredential":{"displayName":"CIPP-SSO-Secret"}}' + $PasswordResult = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$AppObjectId/addPassword" -body $PasswordBody -type POST -NoAuthCheck $true -AsApp $true + $SecretText = $PasswordResult.secretText + Write-Information "[SSO-App] Client secret created" + } catch { + $SecretAttempt++ + Write-Warning "[SSO-App] Secret creation attempt $SecretAttempt/$MaxSecretRetries failed: $($_.Exception.Message)" + if ($SecretAttempt -lt $MaxSecretRetries) { + $Delay = @(2, 5, 10, 15, 30)[$SecretAttempt - 1] + Start-Sleep -Seconds $Delay + } + } + } + + if (-not $SecretText) { + throw "Failed to create client secret for $AppDisplayName after $MaxSecretRetries attempts" + } + + return [PSCustomObject]@{ + AppId = $AppClientId + ObjectId = $AppObjectId + ClientSecret = $SecretText + TenantId = $env:TenantID + DisplayName = $AppDisplayName + State = $State + MultiTenant = $MultiTenant + } +} diff --git a/Modules/CIPPCore/Public/Authentication/Remove-CIPPMigrationAppSetting.ps1 b/Modules/CIPPCore/Public/Authentication/Remove-CIPPMigrationAppSetting.ps1 new file mode 100644 index 000000000000..118012870caf --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/Remove-CIPPMigrationAppSetting.ps1 @@ -0,0 +1,63 @@ +function Remove-CIPPMigrationAppSetting { + <# + .SYNOPSIS + Removes an app setting from the current App Service via ARM. + .DESCRIPTION + Reads the current app settings from ARM, removes the specified key, + and writes the updated settings back. Uses the managed identity for + authentication. Silently returns $false when not running in App Service. + .FUNCTIONALITY + Internal + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$SettingName + ) + + $SiteName = $env:WEBSITE_SITE_NAME + $ResourceGroup = $env:WEBSITE_RESOURCE_GROUP + $SubscriptionId = if ($env:WEBSITE_OWNER_NAME) { ($env:WEBSITE_OWNER_NAME -split '\+')[0] } else { $null } + + if (-not $SiteName -or -not $ResourceGroup -or -not $SubscriptionId) { + Write-Information "[Migration] Not running in App Service — cannot remove app setting '$SettingName'" + return $false + } + + if (-not $env:IDENTITY_ENDPOINT -or -not $env:IDENTITY_HEADER) { + Write-Information "[Migration] No managed identity available — cannot remove app setting '$SettingName'" + return $false + } + + # Get managed identity token for ARM + $TokenUri = "$($env:IDENTITY_ENDPOINT)?resource=https://management.azure.com/&api-version=2019-08-01" + $TokenResponse = Invoke-RestMethod -Uri $TokenUri -Headers @{ 'X-IDENTITY-HEADER' = $env:IDENTITY_HEADER } -Method Get + $ArmToken = $TokenResponse.access_token + + $BaseUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.Web/sites/$SiteName" + $ArmHeaders = @{ + Authorization = "Bearer $ArmToken" + 'Content-Type' = 'application/json' + } + + # Read current app settings + $CurrentSettings = Invoke-RestMethod -Uri "$BaseUri/config/appsettings/list?api-version=2024-11-01" -Method Post -Headers @{ Authorization = "Bearer $ArmToken" } + $MergedSettings = @{} + if ($CurrentSettings.properties) { + $CurrentSettings.properties.PSObject.Properties | ForEach-Object { $MergedSettings[$_.Name] = $_.Value } + } + + if (-not $MergedSettings.ContainsKey($SettingName)) { + Write-Information "[Migration] App setting '$SettingName' not found — nothing to remove" + return $true + } + + $MergedSettings.Remove($SettingName) + + $SettingsBody = @{ properties = $MergedSettings } | ConvertTo-Json -Depth 5 + Invoke-RestMethod -Uri "$BaseUri/config/appsettings?api-version=2024-11-01" -Method Put -Headers $ArmHeaders -Body $SettingsBody + + Write-Information "[Migration] Removed app setting '$SettingName'" + Write-LogMessage -API 'SSO-Migration' -message "Removed app setting '$SettingName' after successful SSO migration" -sev Info + return $true +} diff --git a/Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 new file mode 100644 index 000000000000..5ca8abadae37 --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/Set-CIPPSSOEasyAuth.ps1 @@ -0,0 +1,182 @@ +function Set-CIPPSSOEasyAuth { + <# + .SYNOPSIS + Configures or updates EasyAuth (authsettingsV2) on the current App Service. + .DESCRIPTION + Handles both initial EasyAuth setup and ongoing updates. For initial setup, + creates the full authsettingsV2 config with KV references for the client secret. + For updates, reads the existing config and patches the issuer URL. + Also manages the AUTH_SECRET app setting (using KV references) and + WEBSITE_AUTH_AAD_ALLOWED_TENANTS. + Only works inside an Azure App Service with a managed identity. + .FUNCTIONALITY + Internal + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$AppId, + + [Parameter(Mandatory)] + [bool]$MultiTenant, + + [Parameter(Mandatory)] + [string]$TenantId, + + [Parameter()] + [switch]$UseKvReferences, + + [Parameter()] + [switch]$ImplicitAuth + ) + + $SiteName = $env:WEBSITE_SITE_NAME + $ResourceGroup = $env:WEBSITE_RESOURCE_GROUP + $SubscriptionId = if ($env:WEBSITE_OWNER_NAME) { ($env:WEBSITE_OWNER_NAME -split '\+')[0] } else { $null } + + if (-not $SiteName -or -not $ResourceGroup -or -not $SubscriptionId) { + Write-Information '[SSO-EasyAuth] Not running in App Service — skipping EasyAuth config' + return $false + } + + if (-not $env:IDENTITY_ENDPOINT -or -not $env:IDENTITY_HEADER) { + Write-Information '[SSO-EasyAuth] No managed identity available — skipping EasyAuth config' + return $false + } + + # Get managed identity token for ARM + $TokenUri = "$($env:IDENTITY_ENDPOINT)?resource=https://management.azure.com/&api-version=2019-08-01" + $TokenResponse = Invoke-RestMethod -Uri $TokenUri -Headers @{ 'X-IDENTITY-HEADER' = $env:IDENTITY_HEADER } -Method Get + $ArmToken = $TokenResponse.access_token + + $BaseUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.Web/sites/$SiteName" + $ArmHeaders = @{ + Authorization = "Bearer $ArmToken" + 'Content-Type' = 'application/json' + } + + $IssuerUrl = if ($MultiTenant) { + 'https://login.microsoftonline.com/common/v2.0' + } else { + "https://login.microsoftonline.com/$TenantId/v2.0" + } + + # Read current app settings and merge AUTH_SECRET + $CurrentSettings = Invoke-RestMethod -Uri "$BaseUri/config/appsettings/list?api-version=2024-11-01" -Method Post -Headers @{ Authorization = "Bearer $ArmToken" } + $MergedSettings = @{} + if ($CurrentSettings.properties) { + $CurrentSettings.properties.PSObject.Properties | ForEach-Object { $MergedSettings[$_.Name] = $_.Value } + } + + # Set AUTH_SECRET as a KV reference when requested (initial setup) + # Skip for implicit auth (no client secret needed — e.g. central migration app) + if ($UseKvReferences -and -not $ImplicitAuth) { + $KV = $env:WEBSITE_DEPLOYMENT_ID + $VaultName = if ($KV) { ($KV -split '-')[0] } else { $null } + if ($VaultName) { + $MergedSettings['AUTH_SECRET'] = "@Microsoft.KeyVault(VaultName=$VaultName;SecretName=SSOAppSecret)" + } + } + + # Always remove WEBSITE_AUTH_AAD_ALLOWED_TENANTS — we rely on the issuer URL + # for tenant restriction ("Use default restrictions based on issuer" in the portal). + # Multi-tenant uses common/v2.0 issuer, single-tenant uses {tenantId}/v2.0. + $MergedSettings.Remove('WEBSITE_AUTH_AAD_ALLOWED_TENANTS') + + $SettingsBody = @{ properties = $MergedSettings } | ConvertTo-Json -Depth 5 + Invoke-RestMethod -Uri "$BaseUri/config/appsettings?api-version=2024-11-01" -Method Put -Headers $ArmHeaders -Body $SettingsBody + + # Determine if we can read-modify-write (update) or need a full overwrite (initial setup) + if (-not $UseKvReferences -and $env:WEBSITE_AUTH_V2_CONFIG_JSON) { + # Read-modify-write: only patch the issuer URL, preserving existing allowedAudiences, + # allowedApplications, excludedPaths, tokenStore, etc. + $Current = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -AsHashtable -Depth 20 + $ArmPayload = @{ properties = $Current } + + # 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 = @{} } + $AAD = $Current.identityProviders.azureActiveDirectory + + if (-not $AAD.ContainsKey('registration') -or $null -eq $AAD.registration) { $AAD.registration = @{} } + $AAD.registration.openIdIssuer = $IssuerUrl + + # Ensure the SSO app's own clientId is always in allowedAudiences and allowedApplications + if (-not $AAD.ContainsKey('validation') -or $null -eq $AAD.validation) { $AAD.validation = @{} } + + $ExistingAudiences = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + if ($AAD.validation.allowedAudiences) { + foreach ($a in $AAD.validation.allowedAudiences) { [void]$ExistingAudiences.Add($a) } + } + [void]$ExistingAudiences.Add("api://$AppId") + $AAD.validation.allowedAudiences = @($ExistingAudiences) + + if (-not $AAD.validation.ContainsKey('defaultAuthorizationPolicy') -or $null -eq $AAD.validation.defaultAuthorizationPolicy) { + $AAD.validation.defaultAuthorizationPolicy = @{} + } + $ExistingApps = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + if ($AAD.validation.defaultAuthorizationPolicy.allowedApplications) { + foreach ($a in $AAD.validation.defaultAuthorizationPolicy.allowedApplications) { [void]$ExistingApps.Add($a) } + } + [void]$ExistingApps.Add($AppId) + $AAD.validation.defaultAuthorizationPolicy.allowedApplications = @($ExistingApps) + + if (-not $AAD.validation.defaultAuthorizationPolicy.ContainsKey('allowedPrincipals')) { + $AAD.validation.defaultAuthorizationPolicy.allowedPrincipals = @{} + } + + $AuthConfig = $ArmPayload | ConvertTo-Json -Depth 20 + Write-Information "[SSO-EasyAuth] Read-modify-write: patching issuer to $IssuerUrl (preserving $(($ExistingAudiences).Count) audiences, $(($ExistingApps).Count) allowed apps)" + } else { + # Full overwrite: initial setup — build the entire authsettingsV2 from scratch + $AuthConfig = @{ + properties = @{ + platform = @{ enabled = $true } + globalValidation = @{ + unauthenticatedClientAction = 'RedirectToLoginPage' + redirectToProvider = 'azureactivedirectory' + excludedPaths = @( + '/api/Public*' + '/api/setup/health' + ) + } + identityProviders = @{ + azureActiveDirectory = @{ + enabled = $true + registration = $(if ($ImplicitAuth) { + @{ + clientId = $AppId + openIdIssuer = $IssuerUrl + } + } else { + @{ + clientId = $AppId + clientSecretSettingName = 'AUTH_SECRET' + openIdIssuer = $IssuerUrl + } + }) + validation = @{ + allowedAudiences = @("api://$AppId") + defaultAuthorizationPolicy = @{ + allowedPrincipals = @{} + allowedApplications = @($AppId) + } + } + } + } + login = @{ + tokenStore = @{ + enabled = $true + tokenRefreshExtensionHours = 72 + } + } + } + } | ConvertTo-Json -Depth 20 + } + + Invoke-RestMethod -Uri "$BaseUri/config/authsettingsV2?api-version=2020-06-01" -Method Put -Headers $ArmHeaders -Body $AuthConfig + + Write-Information "[SSO-EasyAuth] Configured EasyAuth: appId=$AppId, issuer=$IssuerUrl, multiTenant=$MultiTenant" + Write-LogMessage -API 'SSO-EasyAuth' -message "EasyAuth configured: appId=$AppId, issuer=$IssuerUrl" -sev Info + return $true +} diff --git a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 index 91e40acb6290..1e1dc84a1fb6 100644 --- a/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Set-CippApiAuth.ps1 @@ -7,67 +7,144 @@ function Set-CippApiAuth { [string[]]$ClientIds ) - # Resolve subscription ID via helper (managed identity environment assumed for ARM). - $SubscriptionId = Get-CIPPAzFunctionAppSubId + if ($env:CIPPNG) { + # Read-modify-write — only patch allowedApplications + allowedAudiences, + # preserving the SSO EasyAuth config (clientSecretSettingName, excludedPaths, tokenStore, etc.) - # 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 } + # Resolve env vars directly (same pattern as Set-CIPPSSOEasyAuth which works) + $SiteName = $env:WEBSITE_SITE_NAME + $ResourceGroup = $env:WEBSITE_RESOURCE_GROUP + $SubscriptionId = if ($env:WEBSITE_OWNER_NAME) { ($env:WEBSITE_OWNER_NAME -split '\+')[0] } else { $null } - Write-Information "AuthSettings: $($AuthSettings | ConvertTo-Json -Depth 10)" + Write-Information "[ApiAuth] SiteName=$SiteName, ResourceGroup=$ResourceGroup, SubscriptionId=$SubscriptionId" + Write-Information "[ApiAuth] ClientIds to set: $($ClientIds -join ', ')" - # Set allowed audiences - $AllowedAudiences = foreach ($ClientId in $ClientIds) { - "api://$ClientId" - } + if (-not $SiteName -or -not $ResourceGroup -or -not $SubscriptionId) { + throw "[ApiAuth] Missing App Service env vars: WEBSITE_SITE_NAME=$SiteName, WEBSITE_RESOURCE_GROUP=$ResourceGroup, SubscriptionId=$SubscriptionId" + } - if (!$AllowedAudiences) { $AllowedAudiences = @() } - if (!$ClientIds) { $ClientIds = @() } + $BaseUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.Web/sites/$SiteName" + Write-Information "[ApiAuth] BaseUri=$BaseUri" - # Set auth settings + # Read current authsettingsV2 from platform-injected env var (reliable, no ARM call needed) + # Then convert to deep hashtable for safe mutation + if (-not $env:WEBSITE_AUTH_V2_CONFIG_JSON) { + throw '[ApiAuth] WEBSITE_AUTH_V2_CONFIG_JSON env var not set — EasyAuth may not be configured yet.' + } - if (($ClientIds | Measure-Object).Count -gt 0) { - $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ - enabled = $true - registration = @{ - clientId = $ClientIds[0] ?? $ClientIds - openIdIssuer = "https://sts.windows.net/$TenantID/v2.0" - } - validation = @{ - allowedAudiences = @($AllowedAudiences) - defaultAuthorizationPolicy = @{ - allowedApplications = @($ClientIds) - } - } + $Current = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -AsHashtable -Depth 20 + # Wrap in properties envelope for the ARM PUT (env var has the raw config, ARM expects {properties: ...}) + $ArmPayload = @{ properties = $Current } + + Write-Information "[ApiAuth] Read config from env var OK. Keys=$($Current.Keys -join ', ')" + + # 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 = @{} } + + $AAD = $Current.identityProviders.azureActiveDirectory + Write-Information "[ApiAuth] AAD keys: $($AAD.Keys -join ', ')" + + # The SSO app's clientId is the registration clientId — always keep it in the lists + $SSOClientId = $AAD.registration.clientId + Write-Information "[ApiAuth] SSO clientId from registration: $SSOClientId" + + # Merge: SSO app + all enabled API clients + $AllAppIds = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + if ($SSOClientId) { [void]$AllAppIds.Add($SSOClientId) } + foreach ($id in $ClientIds) { + if (-not [string]::IsNullOrEmpty($id)) { [void]$AllAppIds.Add($id) } + } + + # Build allowed audiences: api://{id} for each + $AllAudiences = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + foreach ($id in $AllAppIds) { + [void]$AllAudiences.Add("api://$id") + } + + Write-Information "[ApiAuth] Merged allowedApplications: $($AllAppIds -join ', ')" + Write-Information "[ApiAuth] Merged allowedAudiences: $($AllAudiences -join ', ')" + + # Ensure every level of the validation path exists + if (-not $AAD.ContainsKey('validation') -or $null -eq $AAD.validation) { + $AAD.validation = @{} + } + $AAD.validation.allowedAudiences = @($AllAudiences) + + if (-not $AAD.validation.ContainsKey('defaultAuthorizationPolicy') -or $null -eq $AAD.validation.defaultAuthorizationPolicy) { + $AAD.validation.defaultAuthorizationPolicy = @{} + } + $AAD.validation.defaultAuthorizationPolicy.allowedApplications = @($AllAppIds) + + # Ensure allowedPrincipals exists (for "use default restrictions based on issuer") + if (-not $AAD.validation.defaultAuthorizationPolicy.ContainsKey('allowedPrincipals')) { + $AAD.validation.defaultAuthorizationPolicy.allowedPrincipals = @{} + } + + $PutBody = $ArmPayload | ConvertTo-Json -Depth 20 + Write-Information "[ApiAuth] PUT body: $PutBody" + + if ($PSCmdlet.ShouldProcess('Update authsettingsV2 (read-modify-write)')) { + $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" } } else { - $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ - enabled = $false - registration = @{} - validation = @{} + # Full overwrite path (no SSO EasyAuth config to preserve) + $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 + + Write-Information "AuthSettings: $($AuthSettings | ConvertTo-Json -Depth 10)" + + $AllowedAudiences = foreach ($ClientId in $ClientIds) { "api://$ClientId" } + if (!$AllowedAudiences) { $AllowedAudiences = @() } + if (!$ClientIds) { $ClientIds = @() } + + if (($ClientIds | Measure-Object).Count -gt 0) { + $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ + enabled = $true + registration = @{ + clientId = $ClientIds[0] ?? $ClientIds + openIdIssuer = "https://sts.windows.net/$TenantID/v2.0" + } + validation = @{ + allowedAudiences = @($AllowedAudiences) + defaultAuthorizationPolicy = @{ + allowedApplications = @($ClientIds) + } + } + } + } else { + $AuthSettings.properties.identityProviders.azureActiveDirectory = @{ + enabled = $false + registration = @{} + validation = @{} + } } - } - $AuthSettings.properties.globalValidation = @{ - unauthenticatedClientAction = 'Return401' - } - $AuthSettings.properties.login = @{ - tokenStore = @{ - enabled = $true - tokenRefreshExtensionHours = 72 + $AuthSettings.properties.globalValidation = @{ + unauthenticatedClientAction = 'Return401' + } + $AuthSettings.properties.login = @{ + tokenStore = @{ + enabled = $true + tokenRefreshExtensionHours = 72 + } } - } - if ($PSCmdlet.ShouldProcess('Update auth settings')) { - # 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 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' + } - if ($PSCmdlet.ShouldProcess('Update allowed tenants')) { - $null = Update-CIPPAzFunctionAppSetting -Name $FunctionAppName -ResourceGroupName $RGName -AppSetting @{ 'WEBSITE_AUTH_AAD_ALLOWED_TENANTS' = $TenantId } + if ($PSCmdlet.ShouldProcess('Update allowed tenants')) { + $null = Update-CIPPAzFunctionAppSetting -Name $FunctionAppName -ResourceGroupName $RGName -AppSetting @{ 'WEBSITE_AUTH_AAD_ALLOWED_TENANTS' = $TenantId } + } } } diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index 2ad765e41bdf..9622bf5f0c9e 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -202,13 +202,42 @@ function Test-CIPPAccess { $Permissions = Get-CippAllowedPermissions -UserRoles $User.userRoles $swPermsMe.Stop() $AccessTimings['GetPermissions(me)'] = $swPermsMe.Elapsed.TotalMilliseconds + + # Include SSO migration status for admins with AppSettings permissions + $MeResponse = @{ + 'clientPrincipal' = $User + 'permissions' = $Permissions + } + + # 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'] = @{ + appId = $env:CIPP_SSO_MIGRATION_APPID + status = 'pending' + } + } + + if ($Permissions -contains 'CIPP.AppSettings.ReadWrite' -and $env:CIPPNG -ne 'true' -and $env:CIPP_SSO_MIGRATION_PROMPT -eq 'true') { + try { + $SSOTable = Get-CIPPTable -tablename 'SSOMigration' + $SSOMigration = Get-CIPPAzDataTableEntity @SSOTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'MigrationConfig'" -ErrorAction SilentlyContinue + if ($SSOMigration) { + $MeResponse['ssoMigration'] = @{ + status = $SSOMigration.Status + appId = $SSOMigration.AppId + multiTenant = [bool]($SSOMigration.MultiTenant -eq 'true' -or $SSOMigration.MultiTenant -eq 'True') + } + } else { + $MeResponse['ssoMigration'] = @{ status = 'none' } + } + } catch { + $MeResponse['ssoMigration'] = @{ status = 'none' } + } + } + return ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = ( - @{ - 'clientPrincipal' = $User - 'permissions' = $Permissions - } | ConvertTo-Json -Depth 5) + Body = ($MeResponse | ConvertTo-Json -Depth 5) }) } diff --git a/Modules/CIPPCore/Public/Authentication/Update-CIPPSSORedirectUri.ps1 b/Modules/CIPPCore/Public/Authentication/Update-CIPPSSORedirectUri.ps1 new file mode 100644 index 000000000000..03d113d2a214 --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/Update-CIPPSSORedirectUri.ps1 @@ -0,0 +1,126 @@ +function Update-CIPPSSORedirectUri { + <# + .SYNOPSIS + Ensures the CIPP-SSO app registration includes redirect URIs for all bound hostnames + and that signInAudience matches the stored multi-tenant flag. + + .DESCRIPTION + Reads the stored SSO AppId and MultiTenant flag from Key Vault (or DevSecrets table + in dev mode), then: + 1. Queries ARM for all hostnames bound to the App Service (custom domains + default). + 2. Ensures the SSO app's web.redirectUris includes a callback URI for each hostname. + 3. Verifies and patches signInAudience on the app reg if it doesn't match the stored + multi-tenant flag (AzureADMyOrg for single-tenant, AzureADMultipleOrgs for multi). + #> + [CmdletBinding()] + param() + + $CurrentHost = $env:WEBSITE_HOSTNAME + if (-not $CurrentHost) { + Write-Information '[SSO-Redirect] WEBSITE_HOSTNAME not set, skipping redirect URI update' + return + } + + # Resolve the stored SSO AppId and MultiTenant flag + $SSOAppId = $null + $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 + $SSOAppId = $Secret.SSOAppId + $SSOMultiTenant = $Secret.SSOMultiTenant -eq 'True' + } catch { } + } else { + $KV = $env:WEBSITE_DEPLOYMENT_ID + $VaultName = if ($KV) { ($KV -split '-')[0] } else { $null } + if ($VaultName) { + try { + $SSOAppId = Get-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppId' -AsPlainText -ErrorAction Stop + } catch { } + try { + $mtVal = Get-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOMultiTenant' -AsPlainText -ErrorAction Stop + $SSOMultiTenant = $mtVal -eq 'True' + } catch { } + } + } + + if (-not $SSOAppId) { + Write-Information '[SSO-Redirect] No SSO AppId found, skipping redirect URI update' + return + } + + # Discover all bound hostnames via ARM (custom domains + default) + $AllHostnames = @($CurrentHost) + try { + $SiteName = $env:WEBSITE_SITE_NAME + $ResourceGroup = $env:WEBSITE_RESOURCE_GROUP + $SubscriptionId = if ($env:WEBSITE_OWNER_NAME) { ($env:WEBSITE_OWNER_NAME -split '\+')[0] } else { $null } + + if ($SiteName -and $ResourceGroup -and $SubscriptionId -and $env:IDENTITY_ENDPOINT -and $env:IDENTITY_HEADER) { + $TokenUri = "$($env:IDENTITY_ENDPOINT)?resource=https://management.azure.com/&api-version=2019-08-01" + $TokenResponse = Invoke-RestMethod -Uri $TokenUri -Headers @{ 'X-IDENTITY-HEADER' = $env:IDENTITY_HEADER } -Method Get + $ArmToken = $TokenResponse.access_token + + $SiteUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.Web/sites/$SiteName`?api-version=2024-11-01" + $SiteResponse = Invoke-RestMethod -Uri $SiteUri -Headers @{ Authorization = "Bearer $ArmToken" } -Method Get + + if ($SiteResponse.properties.hostNames) { + $AllHostnames = @($SiteResponse.properties.hostNames) + Write-Information "[SSO-Redirect] Discovered hostnames from ARM: $($AllHostnames -join ', ')" + } + } + } catch { + Write-Information "[SSO-Redirect] ARM hostname discovery failed (using WEBSITE_HOSTNAME only): $($_.Exception.Message)" + } + + # Build required redirect URIs from all hostnames + $RequiredUris = foreach ($Hostname in $AllHostnames) { + "https://$Hostname/.auth/login/aad/callback" + } + + try { + $AppResponse = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appId='$SSOAppId')?`$select=id,web,signInAudience" -NoAuthCheck $true -AsApp $true + $ExistingUris = @($AppResponse.web.redirectUris) + + # Determine which URIs are missing + $MissingUris = $RequiredUris | Where-Object { $_ -notin $ExistingUris } + + # Determine the expected signInAudience + $ExpectedAudience = if ($SSOMultiTenant) { 'AzureADMultipleOrgs' } else { 'AzureADMyOrg' } + $AudienceMismatch = $AppResponse.signInAudience -ne $ExpectedAudience + + if ($MissingUris.Count -eq 0 -and -not $AudienceMismatch) { + Write-Information '[SSO-Redirect] All redirect URIs present and signInAudience correct' + return + } + + # Build patch body + $PatchBody = @{} + + if ($MissingUris.Count -gt 0) { + $UpdatedUris = [System.Collections.Generic.List[string]]::new() + $ExistingUris | ForEach-Object { $UpdatedUris.Add($_) } + $MissingUris | ForEach-Object { $UpdatedUris.Add($_) } + $PatchBody.web = @{ redirectUris = $UpdatedUris } + } + + if ($AudienceMismatch) { + $PatchBody.signInAudience = $ExpectedAudience + Write-Information "[SSO-Redirect] Correcting signInAudience: $($AppResponse.signInAudience) -> $ExpectedAudience" + } + + $Body = $PatchBody | ConvertTo-Json -Depth 5 + New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($AppResponse.id)" -body $Body -type PATCH -NoAuthCheck $true -AsApp $true + + if ($MissingUris.Count -gt 0) { + Write-Information "[SSO-Redirect] Added redirect URIs: $($MissingUris -join ', ')" + Write-LogMessage -API 'SSO-Redirect' -message "Added redirect URIs: $($MissingUris -join ', ')" -sev Info + } + if ($AudienceMismatch) { + Write-LogMessage -API 'SSO-Redirect' -message "Updated signInAudience to $ExpectedAudience (multiTenant=$SSOMultiTenant)" -sev Info + } + } catch { + Write-LogMessage -API 'SSO-Redirect' -message "Failed to update SSO app registration: $_" -LogData (Get-CippException -Exception $_) -sev Warning + } +} diff --git a/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 b/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 index 5a44d65fddd5..d585e0122089 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Update-AppManagementPolicy.ps1 @@ -174,7 +174,7 @@ function Update-AppManagementPolicy { $PolicyAction = "Updated existing policy $CIPPAppPolicyId to allow credentials" } elseif ($ExistingExemptionPolicy) { # Exemption policy exists but not assigned to app - update and assign it - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/policies/appManagementPolicies/$($ExistingExemptionPolicy.id)" -type PATCH -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -headers $headers + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/policies/appManagementPolicies/$($ExistingExemptionPolicy.id)" -type PATCH -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -tenantid $TenantFilter -headers $headers if ($CIPPApp.id) { # Assign existing policy to CIPP-SAM application @@ -190,14 +190,14 @@ function Update-AppManagementPolicy { } } else { # Create new policy and assign to CIPP-SAM app - $CreatedPolicy = New-GraphPostRequest -uri 'https://graph.microsoft.com/v1.0/policies/appManagementPolicies' -type POST -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -headers $headers + $CreatedPolicy = New-GraphPostRequest -uri 'https://graph.microsoft.com/v1.0/policies/appManagementPolicies' -type POST -body ($PolicyBody | ConvertTo-Json -Depth 10) -asapp $true -NoAuthCheck $true -tenantid $TenantFilter -headers $headers if ($CIPPApp.id) { # Assign policy to CIPP-SAM application using beta endpoint $AssignBody = @{ '@odata.id' = "https://graph.microsoft.com/beta/policies/appManagementPolicies/$($CreatedPolicy.id)" } - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applications/$($CIPPApp.id)/appManagementPolicies/`$ref" -type POST -body ($AssignBody | ConvertTo-Json) -asapp $true -NoAuthCheck $true -headers $headers + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applications/$($CIPPApp.id)/appManagementPolicies/`$ref" -type POST -body ($AssignBody | ConvertTo-Json) -asapp $true -NoAuthCheck $true -tenantid $TenantFilter -headers $headers $PolicyAction = "Created new policy $($CreatedPolicy.id) and assigned to CIPP-SAM" $CIPPAppPolicyId = $CreatedPolicy.id $CIPPAppTargeted = $true diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ListFeatureFlags.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ListFeatureFlags.ps1 index 3b236cafbb4d..70304808efa0 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ListFeatureFlags.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ListFeatureFlags.ps1 @@ -11,10 +11,19 @@ function Invoke-ListFeatureFlags { try { Write-LogMessage -API 'ListFeatureFlags' -message 'Accessed feature flags list' -sev 'Debug' - $FeatureFlags = Get-CIPPFeatureFlag + $FeatureFlags = @(Get-CIPPFeatureFlag) + + # Environment-driven overrides: enable flags that depend on the runtime platform + if ($env:CIPPNG -eq 'true') { + foreach ($Flag in $FeatureFlags) { + if ($Flag.Id -eq 'SuperAdminNG') { + $Flag.Enabled = $true + } + } + } $StatusCode = [HttpStatusCode]::OK - $Body = @($FeatureFlags) + $Body = $FeatureFlags } catch { Write-LogMessage -API 'ListFeatureFlags' -message "Failed to retrieve feature flags: $($_.Exception.Message)" -sev 'Error' $StatusCode = [HttpStatusCode]::InternalServerError 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 new file mode 100644 index 000000000000..1220df4bad43 --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCIPPUsers.ps1 @@ -0,0 +1,114 @@ +function Invoke-ExecCIPPUsers { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + CIPP.SuperAdmin.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $Action = $Request.Query.Action ?? $Request.Body.Action + $Table = Get-CippTable -tablename 'allowedUsers' + + switch ($Action) { + 'AddUpdate' { + try { + $UPN = $Request.Body.UPN + if ([string]::IsNullOrWhiteSpace($UPN)) { + throw 'UPN (email) is required' + } + $UPN = $UPN.Trim() + + $Roles = @($Request.Body.Roles) + if ($Roles.Count -eq 0) { + throw 'At least one role must be assigned' + } + + # Validate roles exist (built-in + custom) + $CippRolesJson = Join-Path -Path $env:CIPPRootPath -ChildPath 'Config\cipp-roles.json' + $BuiltInRoles = if (Test-Path $CippRolesJson) { + ([System.IO.File]::ReadAllText($CippRolesJson) | ConvertFrom-Json).PSObject.Properties.Name + } else { + @('readonly', 'editor', 'admin', 'superadmin') + } + + $CustomRolesTable = Get-CippTable -tablename 'CustomRoles' + $CustomRoles = @((Get-CIPPAzDataTableEntity @CustomRolesTable).RowKey) + $AllValidRoles = @($BuiltInRoles) + @($CustomRoles) + @('anonymous', 'authenticated') + + foreach ($Role in $Roles) { + if ($Role -notin $AllValidRoles) { + throw "Invalid role: $Role. Valid roles: $($AllValidRoles -join ', ')" + } + } + + $Entity = @{ + PartitionKey = 'User' + RowKey = $UPN + Roles = [string](@($Roles) | ConvertTo-Json -Compress -AsArray) + } + Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force | Out-Null + + # Invalidate the in-memory user cache so changes apply immediately + try { [Craft.Services.AuthBridge]::InvalidateUsers() } catch {} + + $Result = "Successfully added/updated user $UPN with roles: $($Roles -join ', ')" + Write-LogMessage -API $APIName -headers $Headers -message $Result -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "Failed to add/update user: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + } + 'Delete' { + try { + $UPN = $Request.Body.UPN + if ([string]::IsNullOrWhiteSpace($UPN)) { + throw 'UPN (email) is required' + } + $UPN = $UPN.Trim() + + # Self-lockout protection: prevent removing yourself + $CurrentUser = $Request.Headers.'x-ms-client-principal-name' + if ($CurrentUser -and $UPN -ieq $CurrentUser) { + throw 'Cannot remove your own user account. This would lock you out.' + } + + $ExistingEntity = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$UPN'" + if (-not $ExistingEntity) { + throw "User $UPN not found in the allowed users table" + } + + Remove-AzDataTableEntity -Force @Table -Entity $ExistingEntity + try { [Craft.Services.AuthBridge]::InvalidateUsers() } catch {} + + $Result = "Successfully removed user $UPN" + Write-LogMessage -API $APIName -headers $Headers -message $Result -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "Failed to delete user: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + } + default { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = "Unknown action: $Action. Valid actions: AddUpdate, Delete" } + } + } + } + + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @{ Results = $Result } + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 new file mode 100644 index 000000000000..da9948164016 --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 @@ -0,0 +1,173 @@ +function Invoke-ExecContainerManagement { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + CIPP.SuperAdmin.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $Action = $Request.Query.Action ?? $Request.Body.Action + + $ValidChannels = @('latest', 'dev', 'nightly') + + switch ($Action) { + 'Status' { + try { + $CurrentVersion = $env:APP_VERSION ?? 'unknown' + $CommitSha = $env:COMMIT_SHA ?? 'unknown' + $ImageTag = $env:IMAGE_TAG ?? 'unknown' + + # The channel is the image tag baked into the container at build time + $CurrentChannel = $ImageTag + + # Try to read the full container image reference from ARM + $CurrentImage = 'unknown' + $Subscription = Get-CIPPAzFunctionAppSubId + $RGName = $env:WEBSITE_RESOURCE_GROUP + if (-not $RGName) { + $Owner = $env:WEBSITE_OWNER_NAME + if ($Owner -match '^(?[^+]+)\+(?[^-]+(?:-[^-]+)*?)(?:-[^-]+webspace(?:-Linux)?)?$') { + $RGName = $Matches.RGName + } + } + $SiteName = $env:WEBSITE_SITE_NAME + if ($Subscription -and $RGName -and $SiteName) { + try { + $apiVersion = '2024-11-01' + $uri = "https://management.azure.com/subscriptions/$Subscription/resourceGroups/$RGName/providers/Microsoft.Web/sites/$SiteName/config/web?api-version=$apiVersion" + $webConfig = New-CIPPAzRestRequest -Uri $uri -Method GET + $linuxFxVersion = $webConfig.properties.linuxFxVersion + if ($linuxFxVersion) { + $CurrentImage = $linuxFxVersion -replace '^DOCKER\|', '' + # The ARM config tag may differ from the running container's baked-in tag + # if the channel was changed but the container hasn't restarted yet + if ($CurrentImage -match ':([^:]+)$') { + $ConfiguredChannel = $Matches[1] + } + } + } catch { + Write-Information "Could not read container config from ARM: $_" + } + } + + $Body = @{ + Results = @{ + CurrentVersion = $CurrentVersion + CommitSha = $CommitSha + ImageTag = $ImageTag + CurrentChannel = $CurrentChannel + ConfiguredChannel = $ConfiguredChannel ?? $CurrentChannel + CurrentImage = $CurrentImage + SiteName = $SiteName + ValidChannels = $ValidChannels + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "Failed to get container status: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::InternalServerError + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + } + 'UpdateChannel' { + try { + $NewChannel = $Request.Body.Channel + if ([string]::IsNullOrWhiteSpace($NewChannel)) { + throw 'Channel is required' + } + if ($NewChannel -notin $ValidChannels) { + throw "Invalid channel: $NewChannel. Valid channels: $($ValidChannels -join ', ')" + } + + $Subscription = Get-CIPPAzFunctionAppSubId + $RGName = $env:WEBSITE_RESOURCE_GROUP + if (-not $RGName) { + $Owner = $env:WEBSITE_OWNER_NAME + if ($Owner -match '^(?[^+]+)\+(?[^-]+(?:-[^-]+)*?)(?:-[^-]+webspace(?:-Linux)?)?$') { + $RGName = $Matches.RGName + } + } + $SiteName = $env:WEBSITE_SITE_NAME + if (-not ($Subscription -and $RGName -and $SiteName)) { + throw 'Could not determine Azure App Service details from environment' + } + + $apiVersion = '2024-11-01' + + # Read current web config + $getUri = "https://management.azure.com/subscriptions/$Subscription/resourceGroups/$RGName/providers/Microsoft.Web/sites/$SiteName/config/web?api-version=$apiVersion" + $webConfig = New-CIPPAzRestRequest -Uri $getUri -Method GET + $currentLinuxFx = $webConfig.properties.linuxFxVersion + if (-not $currentLinuxFx) { + throw 'Could not read current linuxFxVersion — is this a Linux container app?' + } + + # Replace the tag in the image reference + $currentImage = $currentLinuxFx -replace '^DOCKER\|', '' + if ($currentImage -match '^(.+):([^:]+)$') { + $imageBase = $Matches[1] + $newLinuxFx = "DOCKER|${imageBase}:${NewChannel}" + } else { + $newLinuxFx = "DOCKER|${currentImage}:${NewChannel}" + } + + # Update the web config with new image tag + $putUri = $getUri + $putBody = @{ + properties = @{ + linuxFxVersion = $newLinuxFx + } + } + New-CIPPAzRestRequest -Uri $putUri -Method PATCH -Body $putBody -ContentType 'application/json' | Out-Null + + $Result = "Release channel updated to '$NewChannel'. Image: $newLinuxFx. The container will pull the new image on next restart." + Write-LogMessage -API $APIName -headers $Headers -message "Release channel changed to $NewChannel ($newLinuxFx)" -sev Info + $Body = @{ Results = $Result } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "Failed to update channel: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + } + 'Restart' { + try { + Write-LogMessage -API $APIName -headers $Headers -message 'Container restart requested by super admin' -sev Info + $Body = @{ Results = 'Container restart initiated. The application will be back online shortly.' } + + # Schedule restart after response is sent + try { + [Craft.Services.AppLifecycleBridge]::RequestRestart('Restart requested by super admin via container management page') + } catch { + $Body = @{ Results = 'Restart command sent but the bridge is not available. The app may need to be restarted from the Azure Portal.' } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "Failed to restart: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::InternalServerError + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + } + default { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = "Unknown action: $Action. Valid actions: Status, UpdateChannel, Restart" } + } + } + } + + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + } +} 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 new file mode 100644 index 000000000000..5ed1dff766e2 --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCIPPUsers.ps1 @@ -0,0 +1,80 @@ +function Invoke-ListCIPPUsers { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + CIPP.SuperAdmin.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Table = Get-CippTable -tablename 'allowedUsers' + + try { + # Get all users from the allowedUsers table + $Users = Get-CIPPAzDataTableEntity @Table | Where-Object { -not $_.RowKey.StartsWith('_') } + + # Get available roles (built-in + custom) + $CippRolesJson = Join-Path -Path $env:CIPPRootPath -ChildPath 'Config\cipp-roles.json' + $BuiltInRoles = if (Test-Path $CippRolesJson) { + ([System.IO.File]::ReadAllText($CippRolesJson) | ConvertFrom-Json).PSObject.Properties.Name + } else { + @('readonly', 'editor', 'admin', 'superadmin') + } + + $CustomRolesTable = Get-CippTable -tablename 'CustomRoles' + $CustomRoleEntities = Get-CIPPAzDataTableEntity @CustomRolesTable + $CustomRoleNames = @($CustomRoleEntities | ForEach-Object { $_.RowKey } | Where-Object { $_ }) + + # Build user list with parsed roles + $UserList = [System.Collections.Generic.List[pscustomobject]]::new() + foreach ($User in $Users) { + $ParsedRoles = @() + if ($User.Roles) { + try { + $ParsedRoles = @($User.Roles | ConvertFrom-Json -ErrorAction Stop) + } catch { + $ParsedRoles = @($User.Roles) + } + } + + $UserList.Add([pscustomobject]@{ + UPN = $User.RowKey + Roles = $ParsedRoles + }) + } + + # Build available roles list for frontend dropdown + $AvailableRoles = [System.Collections.Generic.List[pscustomobject]]::new() + foreach ($Role in $BuiltInRoles) { + $AvailableRoles.Add([pscustomobject]@{ + RoleName = $Role + Type = 'Built-In' + }) + } + foreach ($Role in $CustomRoleNames) { + $AvailableRoles.Add([pscustomobject]@{ + RoleName = $Role + Type = 'Custom' + }) + } + + $Body = @{ + Users = @($UserList) + AvailableRoles = @($AvailableRoles) + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -message "Failed to list CIPP users: $($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/CIPP/Setup/Invoke-ExecSSOSetup.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSSOSetup.ps1 new file mode 100644 index 000000000000..a0d29329799d --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Setup/Invoke-ExecSSOSetup.ps1 @@ -0,0 +1,531 @@ +function Invoke-ExecSSOSetup { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + CIPP.AppSettings.ReadWrite + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $Action = $Request.Body.Action ?? $Request.Query.Action ?? 'Status' + $MigrationTable = Get-CIPPTable -tablename 'SSOMigration' + + switch ($Action) { + 'Status' { + # Read live EasyAuth config from the platform-injected env var when available + if ($env:CIPPNG) { + try { + $EasyAuthEnabled = $env:WEBSITE_AUTH_ENABLED -eq 'True' + $ConfigJson = $env:WEBSITE_AUTH_V2_CONFIG_JSON + if ($EasyAuthEnabled -and $ConfigJson) { + $Config = $ConfigJson | ConvertFrom-Json -ErrorAction Stop + $AAD = $Config.identityProviders.azureActiveDirectory + $Issuer = $AAD.registration.openIdIssuer ?? '' + $ClientId = $AAD.registration.clientId ?? '' + $IsMultiTenant = $Issuer -match '/common/' + $IssuerTenantId = if (-not $IsMultiTenant -and $Issuer -match 'microsoftonline\.com/([^/]+)/') { $Matches[1] } else { $null } + $AllowedAudiences = @($AAD.validation.allowedAudiences) + $AllowedApps = @($AAD.validation.defaultAuthorizationPolicy.allowedApplications) + $ExcludedPaths = @($Config.globalValidation.excludedPaths) + + $Body = @{ + Results = @{ + configured = $true + status = 'complete' + appId = $ClientId + multiTenant = $IsMultiTenant + tenantId = $IssuerTenantId + issuer = $Issuer + audiences = $AllowedAudiences + allowedApps = $AllowedApps + excludedPaths = $ExcludedPaths + easyAuthActive = $true + } + } + } else { + $Body = @{ Results = @{ configured = $false; status = 'none'; easyAuthActive = $false } } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -message "Failed to parse EasyAuth config: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $Body = @{ Results = @{ configured = $false; status = 'error'; error = $ErrorMessage.NormalizedError } } + } + } else { + # Otherwise read from migration table + try { + $Migration = Get-CIPPAzDataTableEntity @MigrationTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'MigrationConfig'" -ErrorAction SilentlyContinue + if ($Migration) { + $Body = @{ + Results = @{ + configured = $true + status = $Migration.Status + appId = $Migration.AppId + multiTenant = [bool]($Migration.MultiTenant -eq 'true') + createdAt = $Migration.CreatedAt + lastChecked = $Migration.LastChecked + lastError = $Migration.LastError + } + } + } else { + $Body = @{ Results = @{ configured = $false; status = 'none' } } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -message "Failed to get SSO status: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $Body = @{ Results = @{ configured = $false; status = 'error'; error = $ErrorMessage.NormalizedError } } + } + } + } + + 'Create' { + $MultiTenant = [bool]($Request.Body.multiTenant) + $TargetUrl = $Request.Body.targetUrl + + # Determine redirect URI — prefer explicit targetUrl, fall back to current host + if (-not $TargetUrl) { + $TargetUrl = $Request.Headers.origin ?? $Request.Headers.referer?.TrimEnd('/') + } + if (-not $TargetUrl) { + $TargetUrl = "https://$($env:WEBSITE_HOSTNAME)" + } + + try { + # Check if already provisioned + $Existing = Get-CIPPAzDataTableEntity @MigrationTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'MigrationConfig'" -ErrorAction SilentlyContinue + if ($Existing -and $Existing.Status -eq 'complete') { + $Body = @{ + Results = @{ + message = 'SSO migration already completed.' + appId = $Existing.AppId + severity = 'info' + } + } + break + } + + # If we have an existing record that isn't complete, pick up from where we left off + $AppId = $Existing.AppId + $AppSecret = $null + + # Step 1: Create/update the app registration (idempotent) + # Pass stored AppId so we look up by clientId rather than name + $SSOAppParams = @{ + RedirectUri = $TargetUrl + MultiTenant = $MultiTenant + } + if ($AppId) { $SSOAppParams.ExistingAppId = $AppId } + + $SSOApp = New-CIPPSSOApp @SSOAppParams + $AppId = $SSOApp.AppId + $AppSecret = $SSOApp.ClientSecret + Write-LogMessage -API $APIName -headers $Headers -message "CIPP-SSO app $($SSOApp.State): $AppId" -sev Info + + # Save progress immediately + $MigrationRow = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + AppId = $AppId + MultiTenant = [string]$MultiTenant + RedirectUri = $TargetUrl + Status = 'app_created' + CreatedAt = $Existing.CreatedAt ?? (Get-Date).ToUniversalTime().ToString('o') + LastChecked = (Get-Date).ToUniversalTime().ToString('o') + LastError = '' + } + Add-CIPPAzDataTableEntity @MigrationTable -Entity $MigrationRow -Force | Out-Null + + $KV = $env:WEBSITE_DEPLOYMENT_ID + $VaultName = if ($KV) { ($KV -split '-')[0] } else { $null } + + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + # Dev mode — store in DevSecrets table + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + if (-not $Secret) { $Secret = [PSCustomObject]@{} } + $Secret | Add-Member -MemberType NoteProperty -Name 'PartitionKey' -Value 'SSO' -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'RowKey' -Value 'SSO' -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOAppId' -Value $AppId -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOMultiTenant' -Value ([string]$MultiTenant) -Force + if ($AppSecret) { + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOAppSecret' -Value $AppSecret -Force + } + Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force | Out-Null + Write-Information '[SSO-Setup] Stored SSO credentials in DevSecrets table' + } else { + # Production — store in Key Vault + if (-not $VaultName) { + throw 'Cannot determine Key Vault name from WEBSITE_DEPLOYMENT_ID' + } + + # Step 2: Store AppId in KV (idempotent — Set overwrites) + $ExistingAppIdSecret = $null + try { + $ExistingAppIdSecret = Get-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppId' -AsPlainText -ErrorAction Stop + } catch { } + + if (-not $ExistingAppIdSecret -or $ExistingAppIdSecret -ne $AppId) { + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppId' -SecretValue (ConvertTo-SecureString -String $AppId -AsPlainText -Force) + Write-Information "[SSO-Setup] Stored SSOAppId in Key Vault" + } + + # Update status + $UpdateRow = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + Status = 'appid_stored' + LastChecked = (Get-Date).ToUniversalTime().ToString('o') + } + Add-CIPPAzDataTableEntity @MigrationTable -Entity $UpdateRow -Force | Out-Null + + # Step 3: Store AppSecret in KV + if ($AppSecret) { + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppSecret' -SecretValue (ConvertTo-SecureString -String $AppSecret -AsPlainText -Force) + Write-Information "[SSO-Setup] Stored SSOAppSecret in Key Vault" + } + + # Step 4: Verify TenantID exists in KV (should already be there from SAM setup) + $ExistingTenantId = $null + try { + $ExistingTenantId = Get-CippKeyVaultSecret -VaultName $VaultName -Name 'TenantID' -AsPlainText -ErrorAction Stop + } catch { } + + if (-not $ExistingTenantId) { + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'TenantID' -SecretValue (ConvertTo-SecureString -String $env:TenantID -AsPlainText -Force) + Write-Information "[SSO-Setup] Stored TenantID in Key Vault (was missing)" + } + + # Step 5: Store MultiTenant flag in KV (used for initial EasyAuth setup on startup) + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOMultiTenant' -SecretValue (ConvertTo-SecureString -String ([string]$MultiTenant) -AsPlainText -Force) + Write-Information "[SSO-Setup] Stored SSOMultiTenant=$MultiTenant in Key Vault" + } + + # Mark migration as secrets_stored + $FinalRow = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + AppId = $AppId + MultiTenant = [string]$MultiTenant + RedirectUri = $TargetUrl + Status = 'secrets_stored' + CreatedAt = $Existing.CreatedAt ?? (Get-Date).ToUniversalTime().ToString('o') + LastChecked = (Get-Date).ToUniversalTime().ToString('o') + LastError = '' + } + Add-CIPPAzDataTableEntity @MigrationTable -Entity $FinalRow -Force | Out-Null + + Write-LogMessage -API $APIName -headers $Headers -message "SSO migration credentials stored for app $AppId" -sev Info + $Body = @{ + Results = @{ + message = 'CIPP-SSO app created and credentials stored. EasyAuth will be configured automatically on next startup.' + appId = $AppId + multiTenant = $MultiTenant + severity = 'success' + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "SSO setup failed: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + + # Save error state so the scheduled task can retry + $ErrorRow = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + Status = 'error' + LastChecked = (Get-Date).ToUniversalTime().ToString('o') + LastError = $ErrorMessage.NormalizedError + } + try { Add-CIPPAzDataTableEntity @MigrationTable -Entity $ErrorRow -Force | Out-Null } catch { } + + $StatusCode = [HttpStatusCode]::InternalServerError + $Body = @{ Results = "SSO setup failed: $($ErrorMessage.NormalizedError)" } + } + } + + 'Update' { + # Update existing SSO app configuration (e.g. switch single ↔ multi-tenant) + try { + $Existing = Get-CIPPAzDataTableEntity @MigrationTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'MigrationConfig'" -ErrorAction SilentlyContinue + # Fall back to live EasyAuth config if migration table has no entry + if ((-not $Existing -or -not $Existing.AppId) -and $env:CIPPNG -and $env:WEBSITE_AUTH_V2_CONFIG_JSON) { + $LiveConfig = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -ErrorAction SilentlyContinue + $LiveAppId = $LiveConfig.identityProviders.azureActiveDirectory.registration.clientId + if ($LiveAppId) { + $Existing = [PSCustomObject]@{ AppId = $LiveAppId; Status = 'complete'; CreatedAt = $null } + } + } + if (-not $Existing -or -not $Existing.AppId) { + $StatusCode = [HttpStatusCode]::BadRequest + $Body = @{ Results = 'No SSO app has been created yet. Use the Create action first.' } + break + } + + $MultiTenant = [bool]($Request.Body.multiTenant) + $TargetUrl = $Request.Body.targetUrl + if (-not $TargetUrl) { + $TargetUrl = $Request.Headers.origin ?? $Request.Headers.referer?.TrimEnd('/') + } + if (-not $TargetUrl) { + $TargetUrl = "https://$($env:WEBSITE_HOSTNAME)" + } + + $SignInAudience = if ($MultiTenant) { 'AzureADMultipleOrgs' } else { 'AzureADMyOrg' } + $CallbackUri = $TargetUrl.TrimEnd('/') + '/.auth/login/aad/callback' + + # Look up the existing app and patch it + $AppResponse = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appId='$($Existing.AppId)')?`$select=id,appId,web,signInAudience" -NoAuthCheck $true -AsApp $true + + $PatchBody = @{ + signInAudience = $SignInAudience + web = @{ + redirectUris = @($CallbackUri) + implicitGrantSettings = @{ enableIdTokenIssuance = $true } + } + } | ConvertTo-Json -Depth 10 -Compress + + New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($AppResponse.id)" -body $PatchBody -type PATCH -NoAuthCheck $true -AsApp $true + + # Update migration table + $UpdateRow = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + AppId = $Existing.AppId + MultiTenant = [string]$MultiTenant + RedirectUri = $TargetUrl + Status = $Existing.Status + CreatedAt = $Existing.CreatedAt + LastChecked = (Get-Date).ToUniversalTime().ToString('o') + LastError = '' + } + Add-CIPPAzDataTableEntity @MigrationTable -Entity $UpdateRow -Force | Out-Null + + Write-LogMessage -API $APIName -headers $Headers -message "SSO app updated: multiTenant=$MultiTenant, audience=$SignInAudience" -sev Info + + # Update SSOMultiTenant in KV so initial EasyAuth setup stays in sync + $KV = $env:WEBSITE_DEPLOYMENT_ID + $VaultName = if ($KV) { ($KV -split '-')[0] } else { $null } + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + if ($Secret) { + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOMultiTenant' -Value ([string]$MultiTenant) -Force + Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force | Out-Null + } + } elseif ($VaultName) { + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOMultiTenant' -SecretValue (ConvertTo-SecureString -String ([string]$MultiTenant) -AsPlainText -Force) + } + + # Update EasyAuth ARM config on the App Service (issuer URL + allowed tenants) + try { + Set-CIPPSSOEasyAuth -AppId $Existing.AppId -MultiTenant $MultiTenant -TenantId $env:TenantID + } catch { + Write-Information "[SSO-Update] EasyAuth ARM update skipped (may not be in App Service): $($_.Exception.Message)" + } + + $Body = @{ + Results = @{ + message = "SSO app updated successfully. Sign-in audience is now $SignInAudience." + appId = $Existing.AppId + multiTenant = $MultiTenant + severity = 'success' + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "SSO update failed: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError + $Body = @{ Results = "SSO update failed: $($ErrorMessage.NormalizedError)" } + } + } + + 'RotateSecret' { + # Rotate the client secret for the SSO app + try { + $Existing = Get-CIPPAzDataTableEntity @MigrationTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'MigrationConfig'" -ErrorAction SilentlyContinue + # Fall back to live EasyAuth config if migration table has no entry + if ((-not $Existing -or -not $Existing.AppId) -and $env:CIPPNG -and $env:WEBSITE_AUTH_V2_CONFIG_JSON) { + $LiveConfig = $env:WEBSITE_AUTH_V2_CONFIG_JSON | ConvertFrom-Json -ErrorAction SilentlyContinue + $LiveAppId = $LiveConfig.identityProviders.azureActiveDirectory.registration.clientId + if ($LiveAppId) { + $Existing = [PSCustomObject]@{ AppId = $LiveAppId } + } + } + if (-not $Existing -or -not $Existing.AppId) { + $StatusCode = [HttpStatusCode]::BadRequest + $Body = @{ Results = 'No SSO app has been created yet.' } + break + } + + # Get the app object ID + $AppResponse = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/applications(appId='$($Existing.AppId)')?`$select=id" -NoAuthCheck $true -AsApp $true + + # Create new secret + $PasswordBody = '{"passwordCredential":{"displayName":"CIPP-SSO-Secret"}}' + $PasswordResult = New-GraphPOSTRequest -uri "https://graph.microsoft.com/v1.0/applications/$($AppResponse.id)/addPassword" -body $PasswordBody -type POST -NoAuthCheck $true -AsApp $true + $NewSecret = $PasswordResult.secretText + + if (-not $NewSecret) { + throw 'Failed to create new client secret' + } + + # Store new secret + $KV = $env:WEBSITE_DEPLOYMENT_ID + $VaultName = if ($KV) { ($KV -split '-')[0] } else { $null } + + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + if (-not $Secret) { $Secret = [PSCustomObject]@{} } + $Secret | Add-Member -MemberType NoteProperty -Name 'PartitionKey' -Value 'SSO' -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'RowKey' -Value 'SSO' -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOAppSecret' -Value $NewSecret -Force + Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force | Out-Null + } else { + if (-not $VaultName) { throw 'Cannot determine Key Vault name from WEBSITE_DEPLOYMENT_ID' } + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppSecret' -SecretValue (ConvertTo-SecureString -String $NewSecret -AsPlainText -Force) + } + + # Update last checked + $UpdateRow = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + LastChecked = (Get-Date).ToUniversalTime().ToString('o') + LastError = '' + } + Add-CIPPAzDataTableEntity @MigrationTable -Entity $UpdateRow -Force | Out-Null + + Write-LogMessage -API $APIName -headers $Headers -message "SSO app secret rotated for $($Existing.AppId)" -sev Info + $Body = @{ + Results = @{ + message = 'Client secret rotated successfully. The new secret will be picked up from Key Vault on next restart.' + severity = 'success' + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "SSO secret rotation failed: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError + $Body = @{ Results = "Secret rotation failed: $($ErrorMessage.NormalizedError)" } + } + } + + 'Migrate' { + # Forced SSO migration. Creates the customer's own CIPP-SSO app, + # stores credentials in Key Vault, configures EasyAuth, and removes the migration + # trigger env var. The central migration app (implicit auth, no secret) is replaced + # by the customer's own app with a proper client secret. + if (-not $env:CIPP_SSO_MIGRATION_APPID) { + $Body = @{ Results = @{ message = 'No SSO migration pending.'; severity = 'info' } } + break + } + + $MultiTenant = [bool]($Request.Body.multiTenant) + $TargetUrl = "https://$($env:WEBSITE_HOSTNAME)" + + try { + # Check if we already have SSO credentials from a previous partial run + $KV = $env:WEBSITE_DEPLOYMENT_ID + $VaultName = if ($KV) { ($KV -split '-')[0] } else { $null } + $ExistingAppId = $null + + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $DevSecret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + $ExistingAppId = $DevSecret.SSOAppId + } elseif ($VaultName) { + try { $ExistingAppId = Get-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppId' -AsPlainText -ErrorAction Stop } catch { } + } + + # Step 1: Create or update the customer's own CIPP-SSO app registration + $SSOAppParams = @{ + RedirectUri = $TargetUrl + MultiTenant = $MultiTenant + } + if ($ExistingAppId) { $SSOAppParams.ExistingAppId = $ExistingAppId } + + $SSOApp = New-CIPPSSOApp @SSOAppParams + $AppId = $SSOApp.AppId + $AppSecret = $SSOApp.ClientSecret + Write-LogMessage -API $APIName -headers $Headers -message "SSO migration: CIPP-SSO app $($SSOApp.State): $AppId" -sev Info + + # Step 2: Store credentials + if ($env:AzureWebJobsStorage -eq 'UseDevelopmentStorage=true' -or $env:NonLocalHostAzurite -eq 'true') { + $DevSecretsTable = Get-CIPPTable -tablename 'DevSecrets' + $Secret = Get-CIPPAzDataTableEntity @DevSecretsTable -Filter "PartitionKey eq 'SSO' and RowKey eq 'SSO'" -ErrorAction SilentlyContinue + if (-not $Secret) { $Secret = [PSCustomObject]@{} } + $Secret | Add-Member -MemberType NoteProperty -Name 'PartitionKey' -Value 'SSO' -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'RowKey' -Value 'SSO' -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOAppId' -Value $AppId -Force + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOMultiTenant' -Value ([string]$MultiTenant) -Force + if ($AppSecret) { + $Secret | Add-Member -MemberType NoteProperty -Name 'SSOAppSecret' -Value $AppSecret -Force + } + Add-CIPPAzDataTableEntity @DevSecretsTable -Entity $Secret -Force | Out-Null + Write-Information '[SSO-Migrate] Stored SSO credentials in DevSecrets table' + } else { + if (-not $VaultName) { throw 'Cannot determine Key Vault name from WEBSITE_DEPLOYMENT_ID' } + + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppId' -SecretValue (ConvertTo-SecureString -String $AppId -AsPlainText -Force) + if ($AppSecret) { + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOAppSecret' -SecretValue (ConvertTo-SecureString -String $AppSecret -AsPlainText -Force) + } + Set-CippKeyVaultSecret -VaultName $VaultName -Name 'SSOMultiTenant' -SecretValue (ConvertTo-SecureString -String ([string]$MultiTenant) -AsPlainText -Force) + Write-Information "[SSO-Migrate] Stored SSO credentials in Key Vault ($VaultName)" + } + + # Step 3: Configure EasyAuth on the App Service + Set-CIPPSSOEasyAuth -AppId $AppId -MultiTenant $MultiTenant -TenantId $env:TenantID -UseKvReferences + + # Step 4: Remove the migration trigger env var + Remove-CIPPMigrationAppSetting -SettingName 'CIPP_SSO_MIGRATION_APPID' + + # Step 5: Track in migration table (for audit/status) + $MigrationRow = @{ + PartitionKey = 'SSO' + RowKey = 'MigrationConfig' + AppId = $AppId + MultiTenant = [string]$MultiTenant + RedirectUri = $TargetUrl + Status = 'complete' + CreatedAt = (Get-Date).ToUniversalTime().ToString('o') + LastChecked = (Get-Date).ToUniversalTime().ToString('o') + LastError = '' + MigratedFrom = 'SWA' + } + Add-CIPPAzDataTableEntity @MigrationTable -Entity $MigrationRow -Force | Out-Null + + Write-LogMessage -API $APIName -headers $Headers -message "SSO migration complete: appId=$AppId, multiTenant=$MultiTenant" -sev Info + + # Step 6: Restart to apply EasyAuth + [Craft.Services.AppLifecycleBridge]::RequestRestart('SSO migration complete — EasyAuth configured with customer CIPP-SSO app') + + $Body = @{ + Results = @{ + message = 'SSO migration complete. Your instance will restart with your own CIPP-SSO app registration. You will be redirected to log in once the instance is back online.' + appId = $AppId + multiTenant = $MultiTenant + severity = 'success' + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "SSO migration failed: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError + $Body = @{ Results = "SSO migration failed: $($ErrorMessage.NormalizedError)" } + } + } + + default { + $StatusCode = [HttpStatusCode]::BadRequest + $Body = @{ Results = "Unknown action: $Action. Use 'Status', 'Create', or 'Update'." } + } + } + + return [HttpResponseContext]@{ + StatusCode = $StatusCode ?? [HttpStatusCode]::OK + Body = $Body + } +} From 3adade45c14529810607f8a251d56a9c2c0ac44b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 13 May 2026 14:21:10 +0800 Subject: [PATCH 16/28] Featureflag configs and timer changes --- Config/CIPPTimers.json | 9 + Config/FeatureFlags.json | 6 +- .../Start-ContainerUpdateCheck.ps1 | 193 +++++++++++++ .../CIPPCore/Public/Get-CIPPFeatureFlag.ps1 | 4 + .../Invoke-ExecContainerManagement.ps1 | 264 +++++++++++++++--- 5 files changed, 431 insertions(+), 45 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-ContainerUpdateCheck.ps1 diff --git a/Config/CIPPTimers.json b/Config/CIPPTimers.json index b9899dec6d4b..077f7d53fb0c 100644 --- a/Config/CIPPTimers.json +++ b/Config/CIPPTimers.json @@ -264,5 +264,14 @@ "RunOnProcessor": true, "TZOffset": true, "IsSystem": true + }, + { + "Id": "a3b4c5d6-e7f8-4a9b-8c1d-2e3f4a5b6c7d", + "Command": "Start-ContainerUpdateCheck", + "Description": "Check for container image updates and optionally auto-restart", + "Cron": "0 0 * * * *", + "Priority": 30, + "RunOnProcessor": false, + "IsSystem": true } ] diff --git a/Config/FeatureFlags.json b/Config/FeatureFlags.json index 858f8bc58bcf..0f0d74065933 100644 --- a/Config/FeatureFlags.json +++ b/Config/FeatureFlags.json @@ -19,7 +19,8 @@ "/tenant/standards/bpa-report", "/tenant/standards/bpa-report/builder", "/tenant/standards/bpa-report/view" - ] + ], + "Hidden": false }, { "Id": "SuperAdminNG", @@ -38,6 +39,7 @@ "/cipp/advanced/super-admin/cipp-users", "/cipp/advanced/super-admin/sso", "/cipp/advanced/super-admin/container" - ] + ], + "Hidden": true } ] diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-ContainerUpdateCheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-ContainerUpdateCheck.ps1 new file mode 100644 index 000000000000..381abbbc023c --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-ContainerUpdateCheck.ps1 @@ -0,0 +1,193 @@ +function Start-ContainerUpdateCheck { + <# + .SYNOPSIS + Timer function to check for container image updates + .DESCRIPTION + Reads update settings from ContainerUpdateSettings table, checks if it's time to run based + on the configured interval and check time, queries GHCR for the latest image digest, and + optionally triggers a restart if auto-update is enabled. + #> + [CmdletBinding(SupportsShouldProcess = $true)] + param() + + if ($PSCmdlet.ShouldProcess('Start-ContainerUpdateCheck', 'Check for container image updates')) { + $SettingsTable = Get-CippTable -tablename 'ContainerUpdateSettings' + $Settings = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1 + + if (-not $Settings -or $Settings.CheckInterval -eq '0' -or [string]::IsNullOrWhiteSpace($Settings.CheckInterval)) { + Write-Information 'Container update check: disabled or not configured' + return + } + + # Parse interval to determine if we're due + $IntervalHours = switch ($Settings.CheckInterval) { + '1h' { 1 } + '4h' { 4 } + '12h' { 12 } + '1d' { 24 } + default { 0 } + } + if ($IntervalHours -eq 0) { + Write-Information "Container update check: unknown interval '$($Settings.CheckInterval)'" + return + } + + # Check if preferred time applies — within 45 minutes of desired hour using CIPP timezone + $CheckTime = $Settings.CheckTime + if ($CheckTime -and [string]$CheckTime -ne '') { + $TargetHour = [int]$CheckTime + + # Load the configured CIPP timezone (same source as Get-CIPPTimerFunctions) + $ConfigTable = Get-CIPPTable -tablename Config + $TimeSettings = Get-CIPPAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'TimeSettings' and RowKey eq 'TimeSettings'" | Select-Object -First 1 + $ScheduleTimeZone = [TimeZoneInfo]::Utc + if ($TimeSettings.Timezone) { + try { + $ScheduleTimeZone = [TimeZoneInfo]::FindSystemTimeZoneById($TimeSettings.Timezone) + } catch { + Write-Information "Invalid timezone '$($TimeSettings.Timezone)' — falling back to UTC" + } + } + + # Convert current UTC time to the configured timezone + $NowUtc = [DateTime]::UtcNow + $NowLocal = [TimeZoneInfo]::ConvertTimeFromUtc($NowUtc, $ScheduleTimeZone) + $Today = $NowLocal.Date + $TargetTime = $Today.AddHours($TargetHour) + $MinutesDiff = [math]::Abs(($NowLocal - $TargetTime).TotalMinutes) + # Handle wrapping around midnight (e.g. target=23, current=0) + $MinutesDiffWrap = 1440 - $MinutesDiff + $EffectiveDiff = [math]::Min($MinutesDiff, $MinutesDiffWrap) + if ($EffectiveDiff -gt 45) { + Write-Information "Container update check: not within 45 min of preferred time ($($TargetHour):00 $($ScheduleTimeZone.Id)), current local: $($NowLocal.ToString('HH:mm')), diff: $([math]::Round($EffectiveDiff))min" + return + } + } + + # Check if enough time has elapsed since last check + $LastCheck = $Settings.LastCheck + if ($LastCheck) { + try { + $LastCheckEpoch = [int64]$LastCheck + $LastCheckTime = [DateTimeOffset]::FromUnixTimeSeconds($LastCheckEpoch).UtcDateTime + $ElapsedHours = ((Get-Date).ToUniversalTime() - $LastCheckTime).TotalHours + if ($ElapsedHours -lt ($IntervalHours * 0.9)) { + Write-Information "Container update check: last check was $([math]::Round($ElapsedHours, 1))h ago, interval is ${IntervalHours}h — skipping" + return + } + } catch { + Write-Information "Container update check: could not parse LastCheck '$LastCheck' — proceeding" + } + } + + Write-Information 'Container update check: running' + + try { + # Resolve ARM site details + $Subscription = Get-CIPPAzFunctionAppSubId + $SiteName = $env:WEBSITE_SITE_NAME + $RGName = $env:WEBSITE_RESOURCE_GROUP + if (-not $RGName) { + $Owner = $env:WEBSITE_OWNER_NAME + if ($Owner -match '^(?[^+]+)\+(?[^-]+(?:-[^-]+)*?)(?:-[^-]+webspace(?:-Linux)?)?$') { + $RGName = $Matches.RGName + } + } + + $ImageTag = $env:IMAGE_TAG ?? 'unknown' + $CurrentImage = $null + + if ($Subscription -and $RGName -and $SiteName) { + $apiVersion = '2024-11-01' + $uri = "https://management.azure.com/subscriptions/$Subscription/resourceGroups/$RGName/providers/Microsoft.Web/sites/$SiteName/config/web?api-version=$apiVersion" + $webConfig = New-CIPPAzRestRequest -Uri $uri -Method GET + $linuxFxVersion = $webConfig.properties.linuxFxVersion + if ($linuxFxVersion) { + $CurrentImage = $linuxFxVersion -replace '^DOCKER\|', '' + } + } + + if (-not $CurrentImage) { + Write-LogMessage -API 'ContainerUpdateCheck' -message 'Could not determine current container image from ARM' -sev Warning + return + } + + # Update checking only works with GHCR-hosted images + if ($CurrentImage -notmatch '^ghcr\.io/') { + Write-Information "Container update check: skipped — image '$CurrentImage' is not hosted on GHCR" + return + } + + $CheckTag = if ($CurrentImage -match ':([^:]+)$') { $Matches[1] } else { $ImageTag } + + # Parse image path for GHCR + $imagePath = $CurrentImage -replace '^ghcr\.io/', '' -replace ':.*$', '' + if (-not $imagePath) { + Write-LogMessage -API 'ContainerUpdateCheck' -message 'Could not parse image path from reference' -sev Warning + return + } + + # Get anonymous GHCR token + $tokenUri = "https://ghcr.io/token?scope=repository:${imagePath}:pull" + $tokenResp = Invoke-RestMethod -Uri $tokenUri -Method GET -ErrorAction Stop + $token = $tokenResp.token + + $digestHeaders = @{ + Authorization = "Bearer $token" + Accept = 'application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json' + } + + # Get remote digest for the configured channel tag + $manifestUri = "https://ghcr.io/v2/$imagePath/manifests/$CheckTag" + $resp = Invoke-WebRequest -Uri $manifestUri -Method HEAD -Headers $digestHeaders -ErrorAction Stop + $RemoteDigest = $resp.Headers['Docker-Content-Digest'] + if ($RemoteDigest -is [array]) { $RemoteDigest = $RemoteDigest[0] } + + # Get running digest for the baked-in image tag + $RunningDigest = $null + try { + $runningUri = "https://ghcr.io/v2/$imagePath/manifests/$ImageTag" + $runResp = Invoke-WebRequest -Uri $runningUri -Method HEAD -Headers $digestHeaders -ErrorAction Stop + $RunningDigest = $runResp.Headers['Docker-Content-Digest'] + if ($RunningDigest -is [array]) { $RunningDigest = $RunningDigest[0] } + } catch { + Write-Information "Could not get running digest for tag $ImageTag" + } + + $UpdateAvailable = $false + if ($RemoteDigest -and $RunningDigest -and $RemoteDigest -ne $RunningDigest) { + $UpdateAvailable = $true + } + + # Update the settings row with results (preserve user settings) + $UpdateEntity = @{ + PartitionKey = 'Settings' + RowKey = 'UpdateConfig' + AutoUpdate = [string]($Settings.AutoUpdate ?? 'false') + CheckInterval = [string]($Settings.CheckInterval ?? '0') + CheckTime = [string]($Settings.CheckTime ?? '') + LastCheck = [string][int64](([DateTimeOffset]::UtcNow).ToUnixTimeSeconds()) + UpdateAvailable = [string]$UpdateAvailable + RunningDigest = [string]($RunningDigest ?? '') + RemoteDigest = [string]($RemoteDigest ?? '') + } + Add-CIPPAzDataTableEntity @SettingsTable -Entity $UpdateEntity -Force | Out-Null + + if ($UpdateAvailable -and $Settings.AutoUpdate -eq 'true') { + Write-LogMessage -API 'ContainerUpdateCheck' -message "Auto-update: new container image detected (running: $RunningDigest, remote: $RemoteDigest). Restarting." -sev Info + try { + [Craft.Services.AppLifecycleBridge]::RequestRestart('Auto-update: new container image available') + } catch { + Write-LogMessage -API 'ContainerUpdateCheck' -message 'Auto-restart requested but AppLifecycleBridge is not available' -sev Warning + } + } elseif ($UpdateAvailable) { + Write-LogMessage -API 'ContainerUpdateCheck' -message "Container update available (running: $RunningDigest, remote: $RemoteDigest)" -sev Info + } else { + Write-Information "Container is up to date. Digest: $RunningDigest" + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'ContainerUpdateCheck' -message "Failed to check for container update: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } +} diff --git a/Modules/CIPPCore/Public/Get-CIPPFeatureFlag.ps1 b/Modules/CIPPCore/Public/Get-CIPPFeatureFlag.ps1 index f5d7963d2e59..779c3f13789a 100644 --- a/Modules/CIPPCore/Public/Get-CIPPFeatureFlag.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPFeatureFlag.ps1 @@ -45,6 +45,7 @@ function Get-CIPPFeatureFlag { Timers = $FeatureFlag.Timers Endpoints = $FeatureFlag.Endpoints Pages = $FeatureFlag.Pages + Hidden = [bool]$FeatureFlag.Hidden Enabled = $TableFlag.Enabled } } else { @@ -65,6 +66,7 @@ function Get-CIPPFeatureFlag { Timers = $FeatureFlag.Timers Endpoints = $FeatureFlag.Endpoints Pages = $FeatureFlag.Pages + Hidden = [bool]$FeatureFlag.Hidden Enabled = $FeatureFlag.Enabled } } @@ -83,6 +85,7 @@ function Get-CIPPFeatureFlag { Timers = $FeatureFlag.Timers Endpoints = $FeatureFlag.Endpoints Pages = $FeatureFlag.Pages + Hidden = [bool]$FeatureFlag.Hidden Enabled = $TableFlag.Enabled } } else { @@ -103,6 +106,7 @@ function Get-CIPPFeatureFlag { Timers = $FeatureFlag.Timers Endpoints = $FeatureFlag.Endpoints Pages = $FeatureFlag.Pages + Hidden = [bool]$FeatureFlag.Hidden Enabled = $FeatureFlag.Enabled } } diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 index da9948164016..be842ac61a90 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecContainerManagement.ps1 @@ -13,6 +13,48 @@ function Invoke-ExecContainerManagement { $Action = $Request.Query.Action ?? $Request.Body.Action $ValidChannels = @('latest', 'dev', 'nightly') + $SettingsTable = Get-CippTable -tablename 'ContainerUpdateSettings' + + # Helper: resolve ARM site details + function Get-ContainerSiteInfo { + $info = @{ + Subscription = Get-CIPPAzFunctionAppSubId + SiteName = $env:WEBSITE_SITE_NAME + RGName = $env:WEBSITE_RESOURCE_GROUP + } + if (-not $info.RGName) { + $Owner = $env:WEBSITE_OWNER_NAME + if ($Owner -match '^(?[^+]+)\+(?[^-]+(?:-[^-]+)*?)(?:-[^-]+webspace(?:-Linux)?)?$') { + $info.RGName = $Matches.RGName + } + } + return $info + } + + # Helper: query GHCR for the image digest of a given tag + function Get-GHCRImageDigest { + param([string]$ImageRef, [string]$Tag) + + # Parse image reference: ghcr.io/owner/repo or owner/repo + $imagePath = $ImageRef -replace '^ghcr\.io/', '' -replace ':.*$', '' + if (-not $imagePath) { throw 'Could not parse image path from reference' } + + # Get anonymous token for GHCR (public packages) + $tokenUri = "https://ghcr.io/token?scope=repository:${imagePath}:pull" + $tokenResp = Invoke-RestMethod -Uri $tokenUri -Method GET -ErrorAction Stop + $token = $tokenResp.token + + # Get manifest digest via HEAD request + $manifestUri = "https://ghcr.io/v2/$imagePath/manifests/$Tag" + $digestHeaders = @{ + Authorization = "Bearer $token" + Accept = 'application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json' + } + $resp = Invoke-WebRequest -Uri $manifestUri -Method HEAD -Headers $digestHeaders -ErrorAction Stop + $digest = $resp.Headers['Docker-Content-Digest'] + if ($digest -is [array]) { $digest = $digest[0] } + return [string]$digest + } switch ($Action) { 'Status' { @@ -20,31 +62,20 @@ function Invoke-ExecContainerManagement { $CurrentVersion = $env:APP_VERSION ?? 'unknown' $CommitSha = $env:COMMIT_SHA ?? 'unknown' $ImageTag = $env:IMAGE_TAG ?? 'unknown' - - # The channel is the image tag baked into the container at build time $CurrentChannel = $ImageTag - # Try to read the full container image reference from ARM + # Read the full container image reference from ARM $CurrentImage = 'unknown' - $Subscription = Get-CIPPAzFunctionAppSubId - $RGName = $env:WEBSITE_RESOURCE_GROUP - if (-not $RGName) { - $Owner = $env:WEBSITE_OWNER_NAME - if ($Owner -match '^(?[^+]+)\+(?[^-]+(?:-[^-]+)*?)(?:-[^-]+webspace(?:-Linux)?)?$') { - $RGName = $Matches.RGName - } - } - $SiteName = $env:WEBSITE_SITE_NAME - if ($Subscription -and $RGName -and $SiteName) { + $ConfiguredChannel = $CurrentChannel + $site = Get-ContainerSiteInfo + if ($site.Subscription -and $site.RGName -and $site.SiteName) { try { $apiVersion = '2024-11-01' - $uri = "https://management.azure.com/subscriptions/$Subscription/resourceGroups/$RGName/providers/Microsoft.Web/sites/$SiteName/config/web?api-version=$apiVersion" + $uri = "https://management.azure.com/subscriptions/$($site.Subscription)/resourceGroups/$($site.RGName)/providers/Microsoft.Web/sites/$($site.SiteName)/config/web?api-version=$apiVersion" $webConfig = New-CIPPAzRestRequest -Uri $uri -Method GET $linuxFxVersion = $webConfig.properties.linuxFxVersion if ($linuxFxVersion) { $CurrentImage = $linuxFxVersion -replace '^DOCKER\|', '' - # The ARM config tag may differ from the running container's baked-in tag - # if the channel was changed but the container hasn't restarted yet if ($CurrentImage -match ':([^:]+)$') { $ConfiguredChannel = $Matches[1] } @@ -54,16 +85,38 @@ function Invoke-ExecContainerManagement { } } + # Read update settings and last check result + $Settings = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1 + $UpdateInfo = @{ + AutoUpdate = $false + CheckInterval = '0' + CheckTime = $null + LastCheck = $null + UpdateAvailable = $false + RunningDigest = $null + RemoteDigest = $null + } + if ($Settings) { + $UpdateInfo.AutoUpdate = $Settings.AutoUpdate -eq 'true' + $UpdateInfo.CheckInterval = $Settings.CheckInterval ?? '0' + $UpdateInfo.CheckTime = $Settings.CheckTime ?? $null + $UpdateInfo.LastCheck = if ($Settings.LastCheck) { [int64]$Settings.LastCheck } else { $null } + $UpdateInfo.UpdateAvailable = $Settings.UpdateAvailable -eq 'true' + $UpdateInfo.RunningDigest = $Settings.RunningDigest ?? $null + $UpdateInfo.RemoteDigest = $Settings.RemoteDigest ?? $null + } + $Body = @{ Results = @{ CurrentVersion = $CurrentVersion CommitSha = $CommitSha ImageTag = $ImageTag CurrentChannel = $CurrentChannel - ConfiguredChannel = $ConfiguredChannel ?? $CurrentChannel + ConfiguredChannel = $ConfiguredChannel CurrentImage = $CurrentImage - SiteName = $SiteName + SiteName = $site.SiteName ValidChannels = $ValidChannels + UpdateSettings = $UpdateInfo } } } catch { @@ -75,6 +128,150 @@ function Invoke-ExecContainerManagement { } } } + 'CheckUpdate' { + try { + $site = Get-ContainerSiteInfo + $ImageTag = $env:IMAGE_TAG ?? 'unknown' + + # Get the current image reference from ARM + $CurrentImage = $null + if ($site.Subscription -and $site.RGName -and $site.SiteName) { + $apiVersion = '2024-11-01' + $uri = "https://management.azure.com/subscriptions/$($site.Subscription)/resourceGroups/$($site.RGName)/providers/Microsoft.Web/sites/$($site.SiteName)/config/web?api-version=$apiVersion" + $webConfig = New-CIPPAzRestRequest -Uri $uri -Method GET + $linuxFxVersion = $webConfig.properties.linuxFxVersion + if ($linuxFxVersion) { + $CurrentImage = $linuxFxVersion -replace '^DOCKER\|', '' + } + } + if (-not $CurrentImage) { + throw 'Could not determine current container image from ARM config' + } + + # Update checking only works with GHCR-hosted images + if ($CurrentImage -notmatch '^ghcr\.io/') { + $Body = @{ + Results = @{ + Message = "Update checking is only supported for GHCR-hosted images. Current image: $CurrentImage" + UpdateAvailable = $false + RunningDigest = $null + RemoteDigest = $null + CheckedTag = $null + } + } + break + } + + # Determine the tag to check + $CheckTag = if ($CurrentImage -match ':([^:]+)$') { $Matches[1] } else { $ImageTag } + + # Query GHCR for the remote digest + $RemoteDigest = Get-GHCRImageDigest -ImageRef $CurrentImage -Tag $CheckTag + + # Get the running container's digest — query for the baked-in tag to get what we're running + $RunningDigest = $null + try { + $RunningDigest = Get-GHCRImageDigest -ImageRef $CurrentImage -Tag $ImageTag + } catch { + Write-Information "Could not get running digest for tag $ImageTag — may be first check" + } + + $UpdateAvailable = $false + if ($RemoteDigest -and $RunningDigest -and $RemoteDigest -ne $RunningDigest) { + $UpdateAvailable = $true + } + + # Store result + $Entity = @{ + PartitionKey = 'Settings' + RowKey = 'UpdateConfig' + LastCheck = [string][int64](([DateTimeOffset]::UtcNow).ToUnixTimeSeconds()) + UpdateAvailable = [string]$UpdateAvailable + RunningDigest = [string]($RunningDigest ?? '') + RemoteDigest = [string]($RemoteDigest ?? '') + } + # Merge with existing settings (preserve AutoUpdate, CheckInterval, CheckTime) + $Existing = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1 + if ($Existing) { + $Entity.AutoUpdate = $Existing.AutoUpdate ?? 'false' + $Entity.CheckInterval = $Existing.CheckInterval ?? '0' + $Entity.CheckTime = $Existing.CheckTime ?? '' + } + Add-CIPPAzDataTableEntity @SettingsTable -Entity $Entity -Force | Out-Null + + # Auto-restart if enabled and update is available + $Settings = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1 + if ($UpdateAvailable -and $Settings.AutoUpdate -eq 'true') { + Write-LogMessage -API $APIName -headers $Headers -message "Auto-update: new container image detected (running: $RunningDigest, remote: $RemoteDigest). Restarting." -sev Info + try { [Craft.Services.AppLifecycleBridge]::RequestRestart('Auto-update: new container image available') } catch {} + $Result = "Update available — container restart initiated (auto-update enabled). Running digest: $RunningDigest, Remote digest: $RemoteDigest" + } elseif ($UpdateAvailable) { + $Result = "Update available. Running digest: $RunningDigest, Remote digest: $RemoteDigest. Restart the container to apply." + Write-LogMessage -API $APIName -headers $Headers -message "Container update available (running: $RunningDigest, remote: $RemoteDigest)" -sev Info + } else { + $Result = "Container is up to date. Digest: $RunningDigest" + } + $Body = @{ + Results = @{ + Message = $Result + UpdateAvailable = $UpdateAvailable + RunningDigest = $RunningDigest + RemoteDigest = $RemoteDigest + CheckedTag = $CheckTag + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "Failed to check for update: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::InternalServerError + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + } + 'SaveUpdateSettings' { + try { + $AutoUpdate = [bool]($Request.Body.AutoUpdate) + $CheckInterval = $Request.Body.CheckInterval ?? '0' + $CheckTime = $Request.Body.CheckTime + $ValidIntervals = @('0', '1h', '4h', '12h', '1d') + if ($CheckInterval -notin $ValidIntervals) { + throw "Invalid check interval: $CheckInterval. Valid: $($ValidIntervals -join ', ')" + } + if ($CheckTime -and ($CheckTime -lt 0 -or $CheckTime -gt 23)) { + throw "Invalid check time: $CheckTime. Must be 0-23 (UTC hour)." + } + + # Read existing settings to preserve check results + $Existing = Get-CIPPAzDataTableEntity @SettingsTable -Filter "PartitionKey eq 'Settings' and RowKey eq 'UpdateConfig'" | Select-Object -First 1 + $Entity = @{ + PartitionKey = 'Settings' + RowKey = 'UpdateConfig' + AutoUpdate = [string]$AutoUpdate + CheckInterval = [string]$CheckInterval + CheckTime = [string]($CheckTime ?? '') + LastCheck = [string]($Existing.LastCheck ?? '') + UpdateAvailable = [string]($Existing.UpdateAvailable ?? 'false') + RunningDigest = [string]($Existing.RunningDigest ?? '') + RemoteDigest = [string]($Existing.RemoteDigest ?? '') + } + Add-CIPPAzDataTableEntity @SettingsTable -Entity $Entity -Force | Out-Null + + $IntervalLabel = if ($CheckInterval -eq '0') { 'disabled' } else { "every $CheckInterval" } + $AutoLabel = if ($AutoUpdate) { 'auto-restart enabled' } else { 'manual restart' } + $TimeLabel = if ($CheckTime -and $CheckInterval -ne '0') { " at ${CheckTime}:00 UTC" } else { '' } + $Result = "Update settings saved. Check interval: ${IntervalLabel}${TimeLabel}, $AutoLabel." + Write-LogMessage -API $APIName -headers $Headers -message $Result -sev Info + $Body = @{ Results = $Result } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -headers $Headers -message "Failed to save update settings: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + } 'UpdateChannel' { try { $NewChannel = $Request.Body.Channel @@ -85,30 +282,19 @@ function Invoke-ExecContainerManagement { throw "Invalid channel: $NewChannel. Valid channels: $($ValidChannels -join ', ')" } - $Subscription = Get-CIPPAzFunctionAppSubId - $RGName = $env:WEBSITE_RESOURCE_GROUP - if (-not $RGName) { - $Owner = $env:WEBSITE_OWNER_NAME - if ($Owner -match '^(?[^+]+)\+(?[^-]+(?:-[^-]+)*?)(?:-[^-]+webspace(?:-Linux)?)?$') { - $RGName = $Matches.RGName - } - } - $SiteName = $env:WEBSITE_SITE_NAME - if (-not ($Subscription -and $RGName -and $SiteName)) { + $site = Get-ContainerSiteInfo + if (-not ($site.Subscription -and $site.RGName -and $site.SiteName)) { throw 'Could not determine Azure App Service details from environment' } $apiVersion = '2024-11-01' - - # Read current web config - $getUri = "https://management.azure.com/subscriptions/$Subscription/resourceGroups/$RGName/providers/Microsoft.Web/sites/$SiteName/config/web?api-version=$apiVersion" + $getUri = "https://management.azure.com/subscriptions/$($site.Subscription)/resourceGroups/$($site.RGName)/providers/Microsoft.Web/sites/$($site.SiteName)/config/web?api-version=$apiVersion" $webConfig = New-CIPPAzRestRequest -Uri $getUri -Method GET $currentLinuxFx = $webConfig.properties.linuxFxVersion if (-not $currentLinuxFx) { throw 'Could not read current linuxFxVersion — is this a Linux container app?' } - # Replace the tag in the image reference $currentImage = $currentLinuxFx -replace '^DOCKER\|', '' if ($currentImage -match '^(.+):([^:]+)$') { $imageBase = $Matches[1] @@ -117,14 +303,8 @@ function Invoke-ExecContainerManagement { $newLinuxFx = "DOCKER|${currentImage}:${NewChannel}" } - # Update the web config with new image tag - $putUri = $getUri - $putBody = @{ - properties = @{ - linuxFxVersion = $newLinuxFx - } - } - New-CIPPAzRestRequest -Uri $putUri -Method PATCH -Body $putBody -ContentType 'application/json' | Out-Null + $putBody = @{ properties = @{ linuxFxVersion = $newLinuxFx } } + New-CIPPAzRestRequest -Uri $getUri -Method PATCH -Body $putBody -ContentType 'application/json' | Out-Null $Result = "Release channel updated to '$NewChannel'. Image: $newLinuxFx. The container will pull the new image on next restart." Write-LogMessage -API $APIName -headers $Headers -message "Release channel changed to $NewChannel ($newLinuxFx)" -sev Info @@ -142,8 +322,6 @@ function Invoke-ExecContainerManagement { try { Write-LogMessage -API $APIName -headers $Headers -message 'Container restart requested by super admin' -sev Info $Body = @{ Results = 'Container restart initiated. The application will be back online shortly.' } - - # Schedule restart after response is sent try { [Craft.Services.AppLifecycleBridge]::RequestRestart('Restart requested by super admin via container management page') } catch { @@ -161,7 +339,7 @@ function Invoke-ExecContainerManagement { default { return [HttpResponseContext]@{ StatusCode = [HttpStatusCode]::BadRequest - Body = @{ Results = "Unknown action: $Action. Valid actions: Status, UpdateChannel, Restart" } + Body = @{ Results = "Unknown action: $Action. Valid actions: Status, CheckUpdate, SaveUpdateSettings, UpdateChannel, Restart" } } } } From 93c80812f29762f375ed0ea28b03e59f8d956017 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 13 May 2026 15:52:56 +0800 Subject: [PATCH 17/28] skip replacement if not value set for variable --- .../CIPPCore/Public/Get-CIPPTextReplacement.ps1 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1 b/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1 index 3b2db7623b7e..f96bef07090c 100644 --- a/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1 @@ -65,11 +65,12 @@ function Get-CIPPTextReplacement { $Vars = @{} if ($GlobalMap) { foreach ($Var in $GlobalMap) { + if (-not $Var.PSObject.Properties['Value']) { continue } + $Val = $Var.Value if ($EscapeForJson.IsPresent) { - # Escape quotes for JSON if not already escaped - $Var.Value = $Var.Value -replace '(? Date: Wed, 13 May 2026 16:47:18 +0800 Subject: [PATCH 18/28] strip return characters --- Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 index deb66894da52..db1d5e455da1 100644 --- a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 @@ -280,9 +280,11 @@ function New-CIPPAlertTemplate { } if ($Format -eq 'html') { + $AssembledHtml = $HTMLTemplate -f $Title, $IntroText, $ButtonUrl, $ButtonText, $AfterButtonText, $AuditLogLink + $AssembledHtml = $AssembledHtml -replace '\r\n', '' -replace '\n', '' return [pscustomobject]@{ title = $Title - htmlcontent = $HTMLTemplate -f $Title, $IntroText, $ButtonUrl, $ButtonText, $AfterButtonText, $AuditLogLink + htmlcontent = $AssembledHtml } } elseif ($Format -eq 'json') { if ($InputObject -eq 'auditlog') { From d480cf74f7bea7180136c74d1e351b4b93b7d06a Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 13 May 2026 17:06:47 +0800 Subject: [PATCH 19/28] Add Apps and SP to universal search --- Modules/CIPPCore/Public/Search-CIPPDbData.ps1 | 2 +- .../HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Search-CIPPDbData.ps1 b/Modules/CIPPCore/Public/Search-CIPPDbData.ps1 index cf09305a8dd6..ab03c35f5771 100644 --- a/Modules/CIPPCore/Public/Search-CIPPDbData.ps1 +++ b/Modules/CIPPCore/Public/Search-CIPPDbData.ps1 @@ -69,7 +69,7 @@ function Search-CIPPDbData { 'Groups', 'Roles', 'LicenseOverview', 'IntuneDeviceCompliancePolicies', 'SecureScore', 'SecureScoreControlProfiles', 'Mailboxes', 'CASMailbox', 'MailboxPermissions', 'OneDriveUsage', 'MailboxUsage', 'Devices', 'AllRoles', 'Licenses', 'DeviceCompliancePolicies', - 'BitlockerKeys' + 'BitlockerKeys', 'Apps', 'ServicePrincipals' )] [string[]]$Types, diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 index 6bcb3befbb49..8c3b9d954fb1 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Invoke-ExecUniversalSearchV2.ps1 @@ -28,6 +28,9 @@ function Invoke-ExecUniversalSearchV2 { 'Groups' { $Results = Search-CIPPDbData -SearchTerms $SearchTerms -Types 'Groups' -Limit $Limit -Properties 'id', 'displayName', 'mail', 'mailEnabled', 'securityEnabled', 'groupTypes', 'description' -TenantFilter $TenantFilter } + 'Applications' { + $Results = Search-CIPPDbData -SearchTerms $SearchTerms -Types 'Apps', 'ServicePrincipals' -Limit $Limit -Properties 'id', 'appId', 'displayName', 'publisherName', 'appOwnerOrganizationId' -TenantFilter $TenantFilter + } default { $Results = Search-CIPPDbData -SearchTerms $SearchTerms -Types 'Users' -Limit $Limit -Properties 'id', 'userPrincipalName', 'displayName' -TenantFilter $TenantFilter } From 9011dd63e2a4ac6aef393aab54893b791e06caa0 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 13 May 2026 18:53:59 +0800 Subject: [PATCH 20/28] Nice CA policy editor and template creator/editor --- .../Invoke-ExecCreateCATemplate.ps1 | 54 ++++++++++++++++ .../Invoke-ExecEditCAPolicyFull.ps1 | 64 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCreateCATemplate.ps1 create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecEditCAPolicyFull.ps1 diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCreateCATemplate.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCreateCATemplate.ps1 new file mode 100644 index 000000000000..28d932d2d8aa --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCreateCATemplate.ps1 @@ -0,0 +1,54 @@ +function Invoke-ExecCreateCATemplate { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + Tenant.ConditionalAccess.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + + try { + $Body = $Request.Body + $DisplayName = $Body.displayName ?? $Body.name + if ([string]::IsNullOrWhiteSpace($DisplayName)) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'Error: displayName is required' } + } + } + + $GUID = (New-Guid).GUID + + # Strip any read-only or internal properties before storing + $Template = $Body | Select-Object -Property * -ExcludeProperty GUID, id, createdDateTime, modifiedDateTime, templateId + + $JSON = ConvertTo-Json -InputObject $Template -Depth 100 -Compress + + $Table = Get-CippTable -tablename 'templates' + $Table.Force = $true + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$JSON" + RowKey = "$GUID" + PartitionKey = 'CATemplate' + GUID = "$GUID" + } + + $Result = "Successfully created CA template '$DisplayName' with GUID $GUID" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to create CA template: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError + } + + return [HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @{ Results = $Result } + } +} diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecEditCAPolicyFull.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecEditCAPolicyFull.ps1 new file mode 100644 index 000000000000..40c347946fe2 --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecEditCAPolicyFull.ps1 @@ -0,0 +1,64 @@ +function Invoke-ExecEditCAPolicyFull { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.ConditionalAccess.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + + if ([string]::IsNullOrWhiteSpace($TenantFilter)) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'Error: tenantFilter is required' } + } + } + + $PolicyId = $Request.Query.PolicyId ?? $Request.Body.PolicyId + if ([string]::IsNullOrWhiteSpace($PolicyId)) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'Error: PolicyId is required' } + } + } + + try { + $PolicyBody = $Request.Body.PolicyBody + if ($null -eq $PolicyBody) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'Error: PolicyBody is required' } + } + } + + # Strip read-only properties that cannot be PATCHed + $CleanBody = $PolicyBody | Select-Object -Property * -ExcludeProperty id, createdDateTime, modifiedDateTime, templateId + $RawJSON = ConvertTo-Json -InputObject $CleanBody -Depth 20 -Compress + + $null = New-GraphPOSTRequest ` + -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$PolicyId" ` + -tenantid $TenantFilter ` + -type PATCH ` + -body $RawJSON ` + -asApp $true + + $DisplayName = $PolicyBody.displayName ?? $PolicyId + $Result = "Successfully updated CA policy '$DisplayName' for $TenantFilter" + Write-LogMessage -API $APIName -tenant $TenantFilter -headers $Headers -message $Result -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to update CA policy $PolicyId for ${TenantFilter}: $($ErrorMessage.NormalizedError)" + Write-LogMessage -API $APIName -tenant $TenantFilter -headers $Headers -message $Result -sev Error -LogData $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError + } + + return [HttpResponseContext]@{ + StatusCode = $StatusCode ?? [HttpStatusCode]::OK + Body = @{ Results = $Result } + } +} From 897dfaa4f07443e0f05f8f2aefa5b0a92fc91a9c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 13 May 2026 21:18:48 +0200 Subject: [PATCH 21/28] fixed #5997 --- .../Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index a8d9e29000bf..438a3d34ebd4 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -161,6 +161,7 @@ function Invoke-CIPPStandardSpamFilterPolicy { $RuleStateIsCorrect = ($RuleState.Name -eq $PolicyName) -and ($RuleState.HostedContentFilterPolicy -eq $PolicyName) -and + ($RuleState.State -eq 'Enabled') -and ($RuleState.Priority -eq 0) -and (!(Compare-Object -ReferenceObject $RuleState.RecipientDomainIs -DifferenceObject $AcceptedDomains.Name)) @@ -255,6 +256,15 @@ function Invoke-CIPPStandardSpamFilterPolicy { } catch { Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Failed to update Spam Filter rule $PolicyName." -sev Error -LogData $_ } + + if ($RuleState.State -eq 'Disabled') { + try { + $null = New-ExoRequest -TenantId $Tenant -cmdlet 'Enable-HostedContentFilterRule' -cmdParams @{ Identity = $PolicyName } -UseSystemMailbox $true + Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Enabled Spam Filter rule $PolicyName." -sev Info + } catch { + Write-LogMessage -API 'Standards' -Tenant $Tenant -message "Failed to enable Spam Filter rule $PolicyName." -sev Error -LogData $_ + } + } } else { try { $cmdParams.Add('Name', "$PolicyName") From b1363008db683132524711ec2932afe9a12f0342 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 13 May 2026 21:19:03 +0200 Subject: [PATCH 22/28] #5997 --- .../Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index 438a3d34ebd4..f01a369d3406 100644 --- a/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -86,7 +86,7 @@ function Invoke-CIPPStandardSpamFilterPolicy { try { $CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-HostedContentFilterPolicy' | - Where-Object -Property Name -EQ $PolicyName + Where-Object -Property Name -EQ $PolicyName } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Could not get the SpamFilterPolicy state for $Tenant. Error: $ErrorMessage" -Sev Error @@ -157,7 +157,7 @@ function Invoke-CIPPStandardSpamFilterPolicy { $AcceptedDomains = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-AcceptedDomain' $RuleState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-HostedContentFilterRule' | - Where-Object -Property Name -EQ $PolicyName + Where-Object -Property Name -EQ $PolicyName $RuleStateIsCorrect = ($RuleState.Name -eq $PolicyName) -and ($RuleState.HostedContentFilterPolicy -eq $PolicyName) -and From e060fa22b10156d59bed43c7b22f965a768379e5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 13 May 2026 21:47:17 +0200 Subject: [PATCH 23/28] implements #5981 --- Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 85 ++++++++++++++++++- .../CIPPCore/Public/New-CIPPCATemplate.ps1 | 24 ++++++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 01c10ce4a745..bed989dc48dc 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -167,6 +167,19 @@ function New-CIPPCAPolicy { } } + # Get authentication context class references once if needed + $AllAuthContexts = $null + if ($JSONobj.AuthContextInfo) { + try { + Write-Information 'Fetching authentication context class references...' + $AllAuthContexts = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/authenticationContextClassReferences' -tenantid $TenantFilter -asApp $true + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Error fetching authentication context class references: $($ErrorMessage | ConvertTo-Json -Depth 10 -Compress)" + throw "Failed to fetch authentication context class references: $($ErrorMessage.NormalizedError)" + } + } + # Get service principals once if needed $AllServicePrincipals = $null if (($JSONobj.conditions.applications.includeApplications -and $JSONobj.conditions.applications.includeApplications -notcontains 'All') -or ($JSONobj.conditions.applications.excludeApplications -and $JSONobj.conditions.applications.excludeApplications -notcontains 'All')) { @@ -218,6 +231,75 @@ function New-CIPPCAPolicy { } } + # Handle authentication context class references - create if missing, replace displayNames with IDs + if ($JSONobj.AuthContextInfo) { + $AuthContextLookupTable = foreach ($authContext in $JSONobj.AuthContextInfo) { + if (-not $authContext.displayName) { continue } + $ExistingContext = $AllAuthContexts | Where-Object -Property displayName -EQ $authContext.displayName + if ($ExistingContext) { + Write-LogMessage -Tenant $TenantFilter -Headers $Headers -API $APIName -message "Matched authentication context: $($authContext.displayName)" -Sev 'Info' + [pscustomobject]@{ + id = $ExistingContext.id + displayName = $ExistingContext.displayName + templateId = $authContext.id + } + } else { + # Find the next available ID (c1-c99) + $UsedIds = @($AllAuthContexts.id) + $NewId = $null + for ($i = 1; $i -le 99; $i++) { + $candidateId = "c$i" + if ($candidateId -notin $UsedIds) { + $NewId = $candidateId + break + } + } + if (-not $NewId) { + throw "No available authentication context IDs (c1-c99) in tenant $TenantFilter" + } + $Body = @{ + id = $NewId + displayName = $authContext.displayName + description = if ($authContext.description) { $authContext.description } else { '' } + isAvailable = $true + } | ConvertTo-Json -Compress + try { + $GraphRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/authenticationContextClassReferences' -body $Body -Type POST -tenantid $TenantFilter -asApp $true + Write-LogMessage -Tenant $TenantFilter -Headers $Headers -API $APIName -message "Created new Authentication Context: $($authContext.displayName) with ID $NewId" -Sev 'Info' + # Add to the list so subsequent contexts can see it + $AllAuthContexts = @($AllAuthContexts) + @([pscustomobject]@{ id = $NewId; displayName = $authContext.displayName }) + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-Information "Error creating authentication context: $($ErrorMessage | ConvertTo-Json -Depth 10 -Compress)" + throw "Failed to create authentication context '$($authContext.displayName)': $($ErrorMessage.NormalizedError)" + } + [pscustomobject]@{ + id = $NewId + displayName = $authContext.displayName + templateId = $authContext.id + } + } + } + + Write-Information 'Auth Context Lookup Table:' + Write-Information ($AuthContextLookupTable | ConvertTo-Json -Depth 10) + + # Replace display names with actual IDs in the policy + if ($AuthContextLookupTable -and $JSONobj.conditions.applications.includeAuthenticationContextClassReferences) { + $ResolvedContextIds = [System.Collections.Generic.List[string]]::new() + foreach ($ref in $JSONobj.conditions.applications.includeAuthenticationContextClassReferences) { + $lookup = $AuthContextLookupTable | Where-Object { $_.displayName -eq $ref -or $_.templateId -eq $ref } | Select-Object -First 1 + if ($lookup) { + $ResolvedContextIds.Add($lookup.id) + } else { + # Keep the original value if no match found (might already be an ID) + $ResolvedContextIds.Add($ref) + } + } + $JSONobj.conditions.applications.includeAuthenticationContextClassReferences = @($ResolvedContextIds) + } + } + #for each of the locations, check if they exist, if not create them. These are in $JSONobj.LocationInfo $NewLocationsCreated = $false $LocationLookupTable = foreach ($locations in $JSONobj.LocationInfo) { @@ -386,6 +468,7 @@ function New-CIPPCAPolicy { } } $JSONobj.PSObject.Properties.Remove('LocationInfo') + $JSONobj.PSObject.Properties.Remove('AuthContextInfo') foreach ($condition in $JSONobj.conditions.users.PSObject.Properties.Name) { $value = $JSONobj.conditions.users.$condition if ($null -eq $value) { @@ -451,7 +534,7 @@ function New-CIPPCAPolicy { # Preserve any exclusion groups named "Vacation Exclusion - " from existing policy try { $ExistingVacationGroup = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=startsWith(displayName,'Vacation Exclusion')&`$select=id,displayName&`$top=999&`$count=true" -ComplexFilter -tenantid $TenantFilter -asApp $true | - Where-Object { $CheckExisting.conditions.users.excludeGroups -contains $_.id } + Where-Object { $CheckExisting.conditions.users.excludeGroups -contains $_.id } if ($ExistingVacationGroup) { if (-not ($JSONobj.conditions.users.PSObject.Properties.Name -contains 'excludeGroups')) { $JSONobj.conditions.users | Add-Member -NotePropertyName 'excludeGroups' -NotePropertyValue @() -Force diff --git a/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 index 6103d5ad945c..677377cbaee0 100644 --- a/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCATemplate.ps1 @@ -38,6 +38,12 @@ function New-CIPPCATemplate { } } + # Fetch authentication context class references if the policy uses them + $authContexts = $null + if ($JSON.conditions.applications.includeAuthenticationContextClassReferences) { + $authContexts = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/authenticationContextClassReferences' -tenantid $TenantFilter + } + $AllLocations = [system.collections.generic.list[object]]::new() $includelocations = [system.collections.generic.list[object]]::new() @@ -113,6 +119,24 @@ function New-CIPPCATemplate { # Remove duplicates based on displayName to avoid Select-Object -Unique issues with complex objects $UniqueLocations = $AllLocations | Group-Object -Property displayName | ForEach-Object { $_.Group[0] } $JSON | Add-Member -NotePropertyName 'LocationInfo' -NotePropertyValue @($UniqueLocations) -Force + + # Convert authentication context class reference IDs to display names and store full objects + if ($authContexts -and $JSON.conditions.applications.includeAuthenticationContextClassReferences) { + $AllAuthContexts = [System.Collections.Generic.List[object]]::new() + $authContextDisplayNames = [System.Collections.Generic.List[object]]::new() + foreach ($acr in $JSON.conditions.applications.includeAuthenticationContextClassReferences) { + $acrInfo = $authContexts | Where-Object -Property id -EQ $acr | Select-Object id, displayName, description, isAvailable + if ($acrInfo) { + $authContextDisplayNames.Add($acrInfo.displayName) + $AllAuthContexts.Add($acrInfo) + } else { + $authContextDisplayNames.Add($acr) + } + } + $JSON.conditions.applications.includeAuthenticationContextClassReferences = @($authContextDisplayNames) + $JSON | Add-Member -NotePropertyName 'AuthContextInfo' -NotePropertyValue @($AllAuthContexts) -Force + } + $JSON = (ConvertTo-Json -Compress -Depth 100 -InputObject $JSON) return $JSON } From f5f7ae7064404c314d6cd5694ee1260d1e543c0f Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 14 May 2026 15:09:35 +1000 Subject: [PATCH 24/28] fixes duplicate test calls in some cases --- .../CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 | 14 ++++++++++++-- .../CIPPCore/Public/Invoke-CIPPTestCollection.ps1 | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 60fcb1878353..6c2e1ee6f951 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -40,6 +40,7 @@ function Add-CIPPAzDataTableEntity { $MaxRowSize = 500000 - 100 $MaxSize = 30kb $BatchQueue = [System.Collections.Generic.List[object]]::new() + $BatchKeys = [System.Collections.Generic.Dictionary[string,int]]::new() foreach ($SingleEnt in @($Entity)) { try { @@ -90,8 +91,14 @@ function Add-CIPPAzDataTableEntity { $entityBytes = [System.Text.Encoding]::UTF8.GetByteCount($($SingleEnt | ConvertTo-Json -Compress)) if ($entityBytes -lt $MaxSize) { - # Small entity - add to batch queue - $BatchQueue.Add($SingleEnt) + # Small entity - add to batch queue, dedup by PartitionKey+RowKey (last-in wins) + $batchKey = "$($SingleEnt.PartitionKey)|$($SingleEnt.RowKey)" + if ($BatchKeys.ContainsKey($batchKey)) { + $BatchQueue[$BatchKeys[$batchKey]] = $SingleEnt + } else { + $BatchKeys[$batchKey] = $BatchQueue.Count + $BatchQueue.Add($SingleEnt) + } if ($BatchQueue.Count -ge 100) { try { Add-AzDataTableEntity @Parameters -Entity $BatchQueue.ToArray() -ErrorAction Stop @@ -103,6 +110,7 @@ function Add-CIPPAzDataTableEntity { } } $BatchQueue.Clear() + $BatchKeys.Clear() } continue } @@ -118,6 +126,7 @@ function Add-CIPPAzDataTableEntity { } } $BatchQueue.Clear() + $BatchKeys.Clear() } Add-AzDataTableEntity @Parameters -Entity $SingleEnt -ErrorAction Stop @@ -284,5 +293,6 @@ function Add-CIPPAzDataTableEntity { } } $BatchQueue.Clear() + $BatchKeys.Clear() } } diff --git a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 index 48514b19b36e..3c9ca9619568 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPTestCollection.ps1 @@ -146,7 +146,7 @@ function Invoke-CIPPTestCollection { # Standard suites: discover functions by name pattern via Get-Command $Pattern = $SuitePatterns[$SuiteName] - $TestFunctions = @(Get-Command -Name $Pattern -ErrorAction SilentlyContinue) + $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 b4a5215251751201733eb04fda5a464e462b88d8 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 14 May 2026 15:37:16 +1000 Subject: [PATCH 25/28] Fix tenant group scope cache --- Config/FeatureFlags.json | 6 +- .../Public/TenantGroups/Get-TenantGroups.ps1 | 32 ++-- .../Settings/Invoke-ListContainerLogs.ps1 | 147 ++++++++++++++++++ 3 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 diff --git a/Config/FeatureFlags.json b/Config/FeatureFlags.json index 0f0d74065933..618c8c242094 100644 --- a/Config/FeatureFlags.json +++ b/Config/FeatureFlags.json @@ -33,12 +33,14 @@ "ExecCIPPUsers", "ListCIPPUsers", "ExecSSOSetup", - "ExecContainerManagement" + "ExecContainerManagement", + "ListContainerLogs" ], "Pages": [ "/cipp/advanced/super-admin/cipp-users", "/cipp/advanced/super-admin/sso", - "/cipp/advanced/super-admin/container" + "/cipp/advanced/super-admin/container", + "/cipp/advanced/container-logs" ], "Hidden": true } diff --git a/Modules/CIPPCore/Public/TenantGroups/Get-TenantGroups.ps1 b/Modules/CIPPCore/Public/TenantGroups/Get-TenantGroups.ps1 index 424891f4d555..96f70c91f1db 100644 --- a/Modules/CIPPCore/Public/TenantGroups/Get-TenantGroups.ps1 +++ b/Modules/CIPPCore/Public/TenantGroups/Get-TenantGroups.ps1 @@ -1,19 +1,3 @@ -if (-not $script:TenantGroupsCache) { - $script:TenantGroupsCache = @{ - Groups = $null - Members = $null - LastRefresh = $null - MembersByGroup = $null # Dictionary: GroupId -> members array - } -} - -# Result cache: keyed by "GroupId|TenantFilter|Dynamic" -if (-not $script:TenantGroupsResultCache) { - $script:TenantGroupsResultCache = @{} -} - -$script:TenantGroupsCacheTTL = (New-TimeSpan -Minutes 5) - function Get-TenantGroups { <# .SYNOPSIS @@ -35,6 +19,22 @@ function Get-TenantGroups { [switch]$Dynamic, [switch]$SkipCache ) + + if (-not $script:TenantGroupsCache) { + $script:TenantGroupsCache = @{ + Groups = $null + Members = $null + LastRefresh = $null + MembersByGroup = $null + } + } + if (-not $script:TenantGroupsResultCache) { + $script:TenantGroupsResultCache = @{} + } + if (-not $script:TenantGroupsCacheTTL) { + $script:TenantGroupsCacheTTL = (New-TimeSpan -Minutes 5) + } + $CacheKey = "$GroupId|$TenantFilter|$($Dynamic.IsPresent)" if ($SkipCache) { diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 new file mode 100644 index 000000000000..86214b70074b --- /dev/null +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 @@ -0,0 +1,147 @@ +function Invoke-ListContainerLogs { + <# + .FUNCTIONALITY + Entrypoint,AnyTenant + .ROLE + CIPP.SuperAdmin.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Action = $Request.Query.Action ?? 'ReadLog' + + try { + switch ($Action) { + 'ListFiles' { + $Results = [Craft.Services.LogBridge]::GetLogFiles() + $Body = @{ Results = @($Results) } + } + 'ReadLog' { + $Tail = [int]($Request.Query.Tail ?? '500') + $Level = $Request.Query.Level + $Search = $Request.Query.Search + $File = $Request.Query.File + + # Date range parsing + $From = $null + $To = $null + if ($Request.Query.From) { + $From = [DateTime]::Parse($Request.Query.From).ToUniversalTime() + } + if ($Request.Query.To) { + $To = [DateTime]::Parse($Request.Query.To).ToUniversalTime() + } + + # Resolve nullable params + $LevelParam = if ([string]::IsNullOrEmpty($Level)) { $null } else { $Level } + $SearchParam = if ([string]::IsNullOrEmpty($Search)) { $null } else { $Search } + $FileParam = if ([string]::IsNullOrEmpty($File)) { $null } else { $File } + + $Lines = [Craft.Services.LogBridge]::ReadLog($Tail, $LevelParam, $SearchParam, $FileParam, $From, $To) + + # Parse log lines into structured objects for the table + $Results = foreach ($Line in $Lines) { + if ([string]::IsNullOrWhiteSpace($Line)) { continue } + # Continuation lines (exception details) — skip, they were attached to previous + if ($Line.Length -gt 0 -and [char]::IsWhiteSpace($Line[0])) { continue } + + # Parse: "2026-05-13 10:30:00.000 [INF] message text" + if ($Line -match '^\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(.*)$') { + [PSCustomObject]@{ + Timestamp = "$($Matches[1].Replace(' ', 'T'))Z" + Level = $Matches[2] + Message = $Matches[3] + Raw = $Line + } + } elseif ($Line -match '^\s*(\d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(.*)$') { + # Legacy time-only format + [PSCustomObject]@{ + Timestamp = "$($Matches[1])Z" + Level = $Matches[2] + Message = $Matches[3] + Raw = $Line + } + } else { + [PSCustomObject]@{ + Timestamp = '' + Level = '' + Message = $Line + Raw = $Line + } + } + } + $Body = @{ Results = @($Results) } + } + 'SearchAll' { + $Search = $Request.Query.Search + $Level = $Request.Query.Level + $Tail = [int]($Request.Query.Tail ?? '500') + + $From = $null + $To = $null + if ($Request.Query.From) { + $From = [DateTime]::Parse($Request.Query.From).ToUniversalTime() + } + if ($Request.Query.To) { + $To = [DateTime]::Parse($Request.Query.To).ToUniversalTime() + } + + $SearchParam = if ([string]::IsNullOrEmpty($Search)) { $null } else { $Search } + $LevelParam = if ([string]::IsNullOrEmpty($Level)) { $null } else { $Level } + + $Lines = [Craft.Services.LogBridge]::SearchAllFiles($SearchParam, $LevelParam, $From, $To, $Tail) + + $Results = foreach ($Line in $Lines) { + if ([string]::IsNullOrWhiteSpace($Line)) { continue } + if ($Line.Length -gt 0 -and [char]::IsWhiteSpace($Line[0])) { continue } + + if ($Line -match '^\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(.*)$') { + [PSCustomObject]@{ + Timestamp = "$($Matches[1].Replace(' ', 'T'))Z" + Level = $Matches[2] + Message = $Matches[3] + Raw = $Line + } + } else { + [PSCustomObject]@{ + Timestamp = '' + Level = '' + Message = $Line + Raw = $Line + } + } + } + $Body = @{ Results = @($Results) } + } + 'GetInfo' { + $Body = @{ + Results = @{ + CurrentFile = [Craft.Services.LogBridge]::GetCurrentLogPath() + LogDirectory = [Craft.Services.LogBridge]::GetLogDirectory() + Files = @([Craft.Services.LogBridge]::GetLogFiles()) + } + } + } + default { + $Body = @{ Results = "Unknown action: $Action" } + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = $Body + } + } + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API $APIName -message "Container logs error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::InternalServerError + Body = @{ Results = "Failed: $($ErrorMessage.NormalizedError)" } + } + } + + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + } +} From dbd7fcb3486342ae02f5cc914343052a7bdec645 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 14 May 2026 18:09:21 +1000 Subject: [PATCH 26/28] logging improvements --- .../Settings/Invoke-ListContainerLogs.ps1 | 287 ++++++++++++++---- 1 file changed, 220 insertions(+), 67 deletions(-) diff --git a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 index 86214b70074b..a98c001c05b8 100644 --- a/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 +++ b/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListContainerLogs.ps1 @@ -11,6 +11,182 @@ function Invoke-ListContainerLogs { $APIName = $Request.Params.CIPPEndpoint $Action = $Request.Query.Action ?? 'ReadLog' + # ── Helper: parse raw log lines into structured objects ── + function ConvertTo-LogEntry { + param([string[]]$Lines) + foreach ($Line in $Lines) { + if ([string]::IsNullOrWhiteSpace($Line)) { continue } + if ($Line.Length -gt 0 -and [char]::IsWhiteSpace($Line[0])) { continue } + # ISO 8601: "2026-05-13T10:30:00.000Z [INF] message" + if ($Line -match '^\s*(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\s+\[(\w+)\]\s+(.*)$') { + [PSCustomObject]@{ + Timestamp = $Matches[1] + Level = $Matches[2] + Message = $Matches[3] + Raw = $Line + } + # Legacy: "2026-05-13 10:30:00.000 [INF] message" + } elseif ($Line -match '^\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(.*)$') { + [PSCustomObject]@{ + Timestamp = "$($Matches[1].Replace(' ', 'T'))Z" + Level = $Matches[2] + Message = $Matches[3] + Raw = $Line + } + } else { + [PSCustomObject]@{ + Timestamp = '' + Level = '' + Message = $Line + Raw = $Line + } + } + } + } + + # ── Helper: parse a KQL-subset query into LogBridge parameters ── + # Supported syntax: + # where Level == "ERR" + # where Level in ("ERR", "CRT", "WRN") + # where Message contains "timeout" + # where Message !contains "heartbeat" + # where Message matches regex "error|fail" + # where Timestamp > ago(1h) + # where Timestamp > ago(30m) + # where Timestamp > ago(2d) + # where Timestamp between (ago(2h) .. ago(1h)) + # where Timestamp > datetime(2026-05-14 10:00) + # take 500 + # sort by Timestamp desc + # sort by Timestamp asc + function ConvertFrom-LogQuery { + param([string]$Query) + + $params = @{ + Tail = 500 + Level = $null + Search = $null + Exclude = $null + RegexPattern = $null + From = $null + To = $null + File = $null + SearchAll = $false + SortNewest = $true + } + + # Split on pipe, trim each clause + $clauses = $Query -split '\|' | ForEach-Object { $_.Trim() } | Where-Object { $_ } + + foreach ($clause in $clauses) { + # take N + if ($clause -match '^\s*take\s+(\d+)\s*$') { + $params.Tail = [int]$Matches[1] + continue + } + + # sort by Timestamp asc/desc + if ($clause -match '^\s*sort\s+by\s+\w+\s+(asc|desc)\s*$') { + $params.SortNewest = ($Matches[1] -eq 'desc') + continue + } + + # search all files + if ($clause -match '^\s*search\s+all(\s+files)?\s*$') { + $params.SearchAll = $true + continue + } + + # where clauses + if ($clause -match '^\s*where\s+(.+)$') { + $condition = $Matches[1].Trim() + + # Level == "X" + if ($condition -match '^Level\s*==\s*"(\w+)"\s*$') { + $params.Level = $Matches[1] + } + # Level in ("X", "Y", ...) + elseif ($condition -match '^Level\s+in\s*\(\s*(.+)\s*\)\s*$') { + $items = $Matches[1] -split ',' | ForEach-Object { $_.Trim().Trim('"', "'", ' ') } | Where-Object { $_ } + $params.Level = $items -join ',' + } + # Level != "X" + elseif ($condition -match '^Level\s*!=\s*"(\w+)"\s*$') { + # Exclude this level by including all others + $allLevels = @('TRC', 'DBG', 'INF', 'WRN', 'ERR', 'CRT') + $excluded = $Matches[1].ToUpper() + $params.Level = ($allLevels | Where-Object { $_ -ne $excluded }) -join ',' + } + # Message contains "text" + elseif ($condition -match '^Message\s+contains\s+"(.+)"\s*$') { + $params.Search = $Matches[1] + } + # Message !contains "text" + elseif ($condition -match '^Message\s+!contains\s+"(.+)"\s*$') { + $params.Exclude = $Matches[1] + } + # Message matches regex "pattern" + elseif ($condition -match '^Message\s+matches\s+regex\s+"(.+)"\s*$') { + $params.RegexPattern = $Matches[1] + } + # Timestamp > ago(Xh/m/d/s) + elseif ($condition -match '^Timestamp\s*>\s*ago\((\d+)([smhdw])\)\s*$') { + $amount = [int]$Matches[1] + $unit = $Matches[2] + $params.From = switch ($unit) { + 's' { [DateTime]::UtcNow.AddSeconds(-$amount) } + 'm' { [DateTime]::UtcNow.AddMinutes(-$amount) } + 'h' { [DateTime]::UtcNow.AddHours(-$amount) } + 'd' { [DateTime]::UtcNow.AddDays(-$amount) } + 'w' { [DateTime]::UtcNow.AddDays(-$amount * 7) } + } + } + # Timestamp between (ago(Xh) .. ago(Yh)) + elseif ($condition -match '^Timestamp\s+between\s*\(\s*ago\((\d+)([smhdw])\)\s*\.\.\s*ago\((\d+)([smhdw])\)\s*\)\s*$') { + $fromAmount = [int]$Matches[1]; $fromUnit = $Matches[2] + $toAmount = [int]$Matches[3]; $toUnit = $Matches[4] + $params.From = switch ($fromUnit) { + 's' { [DateTime]::UtcNow.AddSeconds(-$fromAmount) } + 'm' { [DateTime]::UtcNow.AddMinutes(-$fromAmount) } + 'h' { [DateTime]::UtcNow.AddHours(-$fromAmount) } + 'd' { [DateTime]::UtcNow.AddDays(-$fromAmount) } + 'w' { [DateTime]::UtcNow.AddDays(-$fromAmount * 7) } + } + $params.To = switch ($toUnit) { + 's' { [DateTime]::UtcNow.AddSeconds(-$toAmount) } + 'm' { [DateTime]::UtcNow.AddMinutes(-$toAmount) } + 'h' { [DateTime]::UtcNow.AddHours(-$toAmount) } + 'd' { [DateTime]::UtcNow.AddDays(-$toAmount) } + 'w' { [DateTime]::UtcNow.AddDays(-$toAmount * 7) } + } + } + # Timestamp between (ago(Xh) .. now()) + elseif ($condition -match '^Timestamp\s+between\s*\(\s*ago\((\d+)([smhdw])\)\s*\.\.\s*now\(\)\s*\)\s*$') { + $amount = [int]$Matches[1]; $unit = $Matches[2] + $params.From = switch ($unit) { + 's' { [DateTime]::UtcNow.AddSeconds(-$amount) } + 'm' { [DateTime]::UtcNow.AddMinutes(-$amount) } + 'h' { [DateTime]::UtcNow.AddHours(-$amount) } + 'd' { [DateTime]::UtcNow.AddDays(-$amount) } + 'w' { [DateTime]::UtcNow.AddDays(-$amount * 7) } + } + } + # Timestamp > datetime(2026-05-14) or datetime(2026-05-14 10:00) + elseif ($condition -match '^Timestamp\s*>\s*datetime\((.+)\)\s*$') { + $params.From = [DateTime]::Parse($Matches[1]).ToUniversalTime() + } + # Timestamp < datetime(...) + elseif ($condition -match '^Timestamp\s*<\s*datetime\((.+)\)\s*$') { + $params.To = [DateTime]::Parse($Matches[1]).ToUniversalTime() + } + # Unrecognized where clause — ignore + } + # Unrecognized clause — ignore + } + + return $params + } + try { switch ($Action) { 'ListFiles' { @@ -22,96 +198,73 @@ function Invoke-ListContainerLogs { $Level = $Request.Query.Level $Search = $Request.Query.Search $File = $Request.Query.File + $Exclude = $Request.Query.Exclude + $Regex = $Request.Query.Regex + $SortDesc = $Request.Query.SortDesc - # Date range parsing $From = $null $To = $null - if ($Request.Query.From) { - $From = [DateTime]::Parse($Request.Query.From).ToUniversalTime() - } - if ($Request.Query.To) { - $To = [DateTime]::Parse($Request.Query.To).ToUniversalTime() - } + if ($Request.Query.From) { $From = [DateTime]::Parse($Request.Query.From).ToUniversalTime() } + if ($Request.Query.To) { $To = [DateTime]::Parse($Request.Query.To).ToUniversalTime() } - # Resolve nullable params $LevelParam = if ([string]::IsNullOrEmpty($Level)) { $null } else { $Level } $SearchParam = if ([string]::IsNullOrEmpty($Search)) { $null } else { $Search } $FileParam = if ([string]::IsNullOrEmpty($File)) { $null } else { $File } + $ExcludeParam = if ([string]::IsNullOrEmpty($Exclude)) { $null } else { $Exclude } + $RegexParam = if ([string]::IsNullOrEmpty($Regex)) { $null } else { $Regex } + $SortNewest = $SortDesc -eq 'true' - $Lines = [Craft.Services.LogBridge]::ReadLog($Tail, $LevelParam, $SearchParam, $FileParam, $From, $To) - - # Parse log lines into structured objects for the table - $Results = foreach ($Line in $Lines) { - if ([string]::IsNullOrWhiteSpace($Line)) { continue } - # Continuation lines (exception details) — skip, they were attached to previous - if ($Line.Length -gt 0 -and [char]::IsWhiteSpace($Line[0])) { continue } - - # Parse: "2026-05-13 10:30:00.000 [INF] message text" - if ($Line -match '^\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(.*)$') { - [PSCustomObject]@{ - Timestamp = "$($Matches[1].Replace(' ', 'T'))Z" - Level = $Matches[2] - Message = $Matches[3] - Raw = $Line - } - } elseif ($Line -match '^\s*(\d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(.*)$') { - # Legacy time-only format - [PSCustomObject]@{ - Timestamp = "$($Matches[1])Z" - Level = $Matches[2] - Message = $Matches[3] - Raw = $Line - } - } else { - [PSCustomObject]@{ - Timestamp = '' - Level = '' - Message = $Line - Raw = $Line - } - } - } + $Lines = [Craft.Services.LogBridge]::ReadLog($Tail, $LevelParam, $SearchParam, $FileParam, $From, $To, $ExcludeParam, $RegexParam, $SortNewest) + $Results = ConvertTo-LogEntry -Lines $Lines $Body = @{ Results = @($Results) } } 'SearchAll' { $Search = $Request.Query.Search $Level = $Request.Query.Level $Tail = [int]($Request.Query.Tail ?? '500') + $Exclude = $Request.Query.Exclude + $Regex = $Request.Query.Regex + $SortDesc = $Request.Query.SortDesc $From = $null $To = $null - if ($Request.Query.From) { - $From = [DateTime]::Parse($Request.Query.From).ToUniversalTime() - } - if ($Request.Query.To) { - $To = [DateTime]::Parse($Request.Query.To).ToUniversalTime() - } + if ($Request.Query.From) { $From = [DateTime]::Parse($Request.Query.From).ToUniversalTime() } + if ($Request.Query.To) { $To = [DateTime]::Parse($Request.Query.To).ToUniversalTime() } $SearchParam = if ([string]::IsNullOrEmpty($Search)) { $null } else { $Search } $LevelParam = if ([string]::IsNullOrEmpty($Level)) { $null } else { $Level } + $ExcludeParam = if ([string]::IsNullOrEmpty($Exclude)) { $null } else { $Exclude } + $RegexParam = if ([string]::IsNullOrEmpty($Regex)) { $null } else { $Regex } + $SortNewest = $SortDesc -eq 'true' - $Lines = [Craft.Services.LogBridge]::SearchAllFiles($SearchParam, $LevelParam, $From, $To, $Tail) - - $Results = foreach ($Line in $Lines) { - if ([string]::IsNullOrWhiteSpace($Line)) { continue } - if ($Line.Length -gt 0 -and [char]::IsWhiteSpace($Line[0])) { continue } - - if ($Line -match '^\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(.*)$') { - [PSCustomObject]@{ - Timestamp = "$($Matches[1].Replace(' ', 'T'))Z" - Level = $Matches[2] - Message = $Matches[3] - Raw = $Line - } - } else { - [PSCustomObject]@{ - Timestamp = '' - Level = '' - Message = $Line - Raw = $Line - } + $Lines = [Craft.Services.LogBridge]::SearchAllFiles($SearchParam, $LevelParam, $From, $To, $Tail, $ExcludeParam, $RegexParam, $SortNewest) + $Results = ConvertTo-LogEntry -Lines $Lines + $Body = @{ Results = @($Results) } + } + 'Query' { + $Query = $Request.Query.Query ?? $Request.Body.Query + if ([string]::IsNullOrWhiteSpace($Query)) { + return [HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = @{ Results = 'Query parameter is required' } } } + + $p = ConvertFrom-LogQuery -Query $Query + + $LevelParam = if ([string]::IsNullOrEmpty($p.Level)) { $null } else { $p.Level } + $SearchParam = if ([string]::IsNullOrEmpty($p.Search)) { $null } else { $p.Search } + $ExcludeParam = if ([string]::IsNullOrEmpty($p.Exclude)) { $null } else { $p.Exclude } + $RegexParam = if ([string]::IsNullOrEmpty($p.RegexPattern)) { $null } else { $p.RegexPattern } + $FileParam = if ([string]::IsNullOrEmpty($p.File)) { $null } else { $p.File } + + if ($p.SearchAll) { + $Lines = [Craft.Services.LogBridge]::SearchAllFiles($SearchParam, $LevelParam, $p.From, $p.To, $p.Tail, $ExcludeParam, $RegexParam, $p.SortNewest) + } else { + $Lines = [Craft.Services.LogBridge]::ReadLog($p.Tail, $LevelParam, $SearchParam, $FileParam, $p.From, $p.To, $ExcludeParam, $RegexParam, $p.SortNewest) + } + + $Results = ConvertTo-LogEntry -Lines $Lines $Body = @{ Results = @($Results) } } 'GetInfo' { From bc7de0c1a41134baa87c8d45ee2f13fb8d96b979 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 14 May 2026 18:09:59 +1000 Subject: [PATCH 27/28] when running a standard manually still process all standards for precedence overrides --- .../Public/Standards/Get-CIPPStandards.ps1 | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Get-CIPPStandards.ps1 b/Modules/CIPPCore/Public/Standards/Get-CIPPStandards.ps1 index 71c231d4360c..f94b227967a0 100644 --- a/Modules/CIPPCore/Public/Standards/Get-CIPPStandards.ps1 +++ b/Modules/CIPPCore/Public/Standards/Get-CIPPStandards.ps1 @@ -22,6 +22,9 @@ function Get-CIPPStandards { # 1. Get all JSON-based templates from the "templates" table $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'StandardsTemplateV2'" + # Always load ALL templates so the three-tier merge (AllTenants → Group → Tenant-Specific) + # can compute correct precedence. The $TemplateId filter is applied after merge so that + # manual runs of a single template don't bypass tenant-specific overrides. $Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter | Sort-Object TimeStamp).JSON | ForEach-Object { try { @@ -31,7 +34,7 @@ function Get-CIPPStandards { } catch {} } | Where-Object { - $_.GUID -like $TemplateId -and $_.runManually -eq $runManually + $_.runManually -eq $runManually } # 1.5. Expand templates that contain TemplateList-Tags into multiple standards @@ -128,7 +131,7 @@ function Get-CIPPStandards { # 3. If -ListAllTenants, build standards for "AllTenants" only if ($ListAllTenants.IsPresent) { $AllTenantsTemplates = $Templates | Where-Object { - $_.tenantFilter.value -contains 'AllTenants' + $_.tenantFilter.value -contains 'AllTenants' -and $_.GUID -like $TemplateId } foreach ($Template in $AllTenantsTemplates) { @@ -505,6 +508,15 @@ function Get-CIPPStandards { $StandardName = $Key -replace '\|.*$', '' # Preserve TemplateId before removing $PreservedTemplateId = $Standard.TemplateId + + # When a specific TemplateId was requested, only emit standards that + # this template actually won after the three-tier merge. This prevents + # a group template manual run from executing standards that a + # tenant-specific template has overridden. + if ($TemplateId -ne '*' -and $PreservedTemplateId -notlike $TemplateId) { + continue + } + $Standard.PSObject.Properties.Remove('TemplateId') | Out-Null $Normalized = ConvertTo-CippStandardObject $Standard 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 28/28] 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 = @{