diff --git a/Modules/CIPPCore/Private/Test-CIPPConditionFilter.ps1 b/Modules/CIPPCore/Private/Test-CIPPConditionFilter.ps1
new file mode 100644
index 000000000000..6369094e852d
--- /dev/null
+++ b/Modules/CIPPCore/Private/Test-CIPPConditionFilter.ps1
@@ -0,0 +1,78 @@
+function Test-CIPPConditionFilter {
+ <#
+ .SYNOPSIS
+ Returns a sanitized PowerShell condition string for an audit log / delta query condition.
+ .DESCRIPTION
+ Validates operator and property name against allowlists, sanitizes input values,
+ then returns a safe condition string suitable for [ScriptBlock]::Create().
+
+ This replaces the old Invoke-Expression pattern which was vulnerable to code injection
+ through unsanitized user-controlled fields.
+ .PARAMETER Condition
+ A single condition object with Property.label, Operator.value, and Input.value.
+ .OUTPUTS
+ [string] A sanitized PowerShell condition string, or $null if validation fails.
+ .FUNCTIONALITY
+ Internal
+ #>
+ [CmdletBinding()]
+ [OutputType([string])]
+ param(
+ [Parameter(Mandatory = $true)]
+ $Condition
+ )
+
+ # Operator allowlist - only these PowerShell comparison operators are permitted
+ $AllowedOperators = @(
+ 'eq', 'ne', 'like', 'notlike', 'match', 'notmatch',
+ 'gt', 'lt', 'ge', 'le', 'in', 'notin',
+ 'contains', 'notcontains'
+ )
+
+ # Property name validation - only alphanumeric, underscores, and dots allowed
+ $SafePropertyRegex = [regex]'^[a-zA-Z0-9_.]+$'
+
+ # Value sanitization - block characters that enable code injection
+ $UnsafeValueRegex = [regex]'[;|`\$\{\}]'
+
+ $propertyName = $Condition.Property.label
+ $operatorValue = $Condition.Operator.value.ToLower()
+ $inputValue = $Condition.Input.value
+
+ # Validate operator against allowlist
+ if ($operatorValue -notin $AllowedOperators) {
+ Write-Warning "Blocked invalid operator '$($Condition.Operator.value)' in condition for property '$propertyName'"
+ return $null
+ }
+
+ # Validate property name to prevent injection via property paths
+ if (-not $SafePropertyRegex.IsMatch($propertyName)) {
+ Write-Warning "Blocked invalid property name '$propertyName' in condition"
+ return $null
+ }
+
+ # Build sanitized condition string
+ if ($inputValue -is [array]) {
+ # Sanitize each array element
+ $sanitizedItems = foreach ($item in $inputValue) {
+ $itemStr = [string]$item
+ if ($UnsafeValueRegex.IsMatch($itemStr)) {
+ Write-Warning "Blocked unsafe value in array for property '$propertyName': '$itemStr'"
+ return $null
+ }
+ $itemStr -replace "'", "''"
+ }
+ if ($null -eq $sanitizedItems) { return $null }
+ $arrayAsString = $sanitizedItems | ForEach-Object { "'$_'" }
+ $value = "@($($arrayAsString -join ', '))"
+ } else {
+ $valueStr = [string]$inputValue
+ if ($UnsafeValueRegex.IsMatch($valueStr)) {
+ Write-Warning "Blocked unsafe value for property '$propertyName': '$valueStr'"
+ return $null
+ }
+ $value = "'$($valueStr -replace "'", "''")'"
+ }
+
+ return "`$(`$_.$propertyName) -$operatorValue $value"
+}
diff --git a/Modules/CIPPCore/Private/Test-CIPPDynamicGroupFilter.ps1 b/Modules/CIPPCore/Private/Test-CIPPDynamicGroupFilter.ps1
new file mode 100644
index 000000000000..f8138c972094
--- /dev/null
+++ b/Modules/CIPPCore/Private/Test-CIPPDynamicGroupFilter.ps1
@@ -0,0 +1,210 @@
+function Test-CIPPDynamicGroupFilter {
+ <#
+ .SYNOPSIS
+ Returns a sanitized PowerShell condition string for a dynamic tenant group rule.
+ .DESCRIPTION
+ Validates all user-controlled inputs (property, operator, values) against allowlists
+ and sanitizes values before building the condition string. Returns a safe condition
+ string suitable for use in [ScriptBlock]::Create().
+
+ This replaces the old pattern of directly interpolating unsanitized user input into
+ scriptblock strings, which was vulnerable to code injection.
+ .PARAMETER Rule
+ A single rule object with .property, .operator, and .value fields.
+ .PARAMETER TenantGroupMembersCache
+ Hashtable of group memberships keyed by group ID.
+ .OUTPUTS
+ [string] A sanitized PowerShell condition string, or $null if validation fails.
+ .FUNCTIONALITY
+ Internal
+ #>
+ [CmdletBinding()]
+ [OutputType([string])]
+ param(
+ [Parameter(Mandatory = $true)]
+ $Rule,
+ [Parameter(Mandatory = $false)]
+ [hashtable]$TenantGroupMembersCache = @{}
+ )
+
+ $AllowedOperators = @('eq', 'ne', 'like', 'notlike', 'in', 'notin', 'contains', 'notcontains')
+ $AllowedProperties = @('delegatedAccessStatus', 'availableLicense', 'availableServicePlan', 'tenantGroupMember', 'customVariable')
+
+ # Regex for sanitizing string values - block characters that enable code injection
+ $SafeValueRegex = [regex]'^[^;|`\$\{\}\(\)]*$'
+ # Regex for GUID validation
+ $GuidRegex = [regex]'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
+ # Regex for safe identifiers (variable names, plan names, etc.)
+ $SafeIdentifierRegex = [regex]'^[a-zA-Z0-9_.\-\s\(\)]+$'
+
+ $Property = $Rule.property
+ $Operator = [string]($Rule.operator)
+ $OperatorLower = $Operator.ToLower()
+ $Value = $Rule.value
+
+ # Validate operator
+ if ($OperatorLower -notin $AllowedOperators) {
+ Write-Warning "Blocked invalid operator '$Operator' in dynamic group rule for property '$Property'"
+ return $null
+ }
+
+ # Validate property
+ if ($Property -notin $AllowedProperties) {
+ Write-Warning "Blocked invalid property '$Property' in dynamic group rule"
+ return $null
+ }
+
+ # Helper: sanitize a single string value for safe embedding in a quoted string
+ function Protect-StringValue {
+ param([string]$InputValue)
+ # Escape single quotes by doubling them (PowerShell string escaping)
+ $escaped = $InputValue -replace "'", "''"
+ # Block any remaining injection characters
+ if (-not $SafeValueRegex.IsMatch($escaped)) {
+ Write-Warning "Blocked unsafe value: '$InputValue'"
+ return $null
+ }
+ return $escaped
+ }
+
+ # Helper: sanitize and format an array of string values for embedding in @('a','b')
+ function Protect-StringArray {
+ param([array]$InputValues)
+ $sanitized = foreach ($v in $InputValues) {
+ $clean = Protect-StringValue -InputValue ([string]$v)
+ if ($null -eq $clean) { return $null }
+ "'$clean'"
+ }
+ return "@($($sanitized -join ', '))"
+ }
+
+ switch ($Property) {
+ 'delegatedAccessStatus' {
+ $safeValue = Protect-StringValue -InputValue ([string]$Value.value)
+ if ($null -eq $safeValue) { return $null }
+ return "`$_.delegatedPrivilegeStatus -$OperatorLower '$safeValue'"
+ }
+ 'availableLicense' {
+ if ($OperatorLower -in @('in', 'notin')) {
+ $arrayValues = @(if ($Value -is [array]) { $Value.guid } else { @($Value.guid) })
+ # Validate each GUID
+ foreach ($g in $arrayValues) {
+ if (![string]::IsNullOrEmpty($g) -and -not $GuidRegex.IsMatch($g)) {
+ Write-Warning "Blocked invalid GUID in availableLicense rule: '$g'"
+ return $null
+ }
+ }
+ $arrayAsString = ($arrayValues | Where-Object { ![string]::IsNullOrEmpty($_) }) | ForEach-Object { "'$_'" }
+ if ($OperatorLower -eq 'in') {
+ return "(`$_.skuId | Where-Object { `$_ -in @($($arrayAsString -join ', ')) }).Count -gt 0"
+ } else {
+ return "(`$_.skuId | Where-Object { `$_ -in @($($arrayAsString -join ', ')) }).Count -eq 0"
+ }
+ } else {
+ $guid = [string]$Value.guid
+ if (![string]::IsNullOrEmpty($guid) -and -not $GuidRegex.IsMatch($guid)) {
+ Write-Warning "Blocked invalid GUID in availableLicense rule: '$guid'"
+ return $null
+ }
+ return "`$_.skuId -$OperatorLower '$guid'"
+ }
+ }
+ 'availableServicePlan' {
+ if ($OperatorLower -in @('in', 'notin')) {
+ $arrayValues = @(if ($Value -is [array]) { $Value.value } else { @($Value.value) })
+ foreach ($v in $arrayValues) {
+ if (![string]::IsNullOrEmpty($v) -and -not $SafeIdentifierRegex.IsMatch($v)) {
+ Write-Warning "Blocked invalid service plan name: '$v'"
+ return $null
+ }
+ }
+ $arrayAsString = ($arrayValues | Where-Object { ![string]::IsNullOrEmpty($_) }) | ForEach-Object { "'$_'" }
+ if ($OperatorLower -eq 'in') {
+ return "(`$_.servicePlans | Where-Object { `$_ -in @($($arrayAsString -join ', ')) }).Count -gt 0"
+ } else {
+ return "(`$_.servicePlans | Where-Object { `$_ -in @($($arrayAsString -join ', ')) }).Count -eq 0"
+ }
+ } else {
+ $safeValue = Protect-StringValue -InputValue ([string]$Value.value)
+ if ($null -eq $safeValue) { return $null }
+ return "`$_.servicePlans -$OperatorLower '$safeValue'"
+ }
+ }
+ 'tenantGroupMember' {
+ if ($OperatorLower -in @('in', 'notin')) {
+ $ReferencedGroupIds = @($Value.value)
+ # Validate group IDs are GUIDs
+ foreach ($gid in $ReferencedGroupIds) {
+ if (![string]::IsNullOrEmpty($gid) -and -not $GuidRegex.IsMatch($gid)) {
+ Write-Warning "Blocked invalid group ID in tenantGroupMember rule: '$gid'"
+ return $null
+ }
+ }
+
+ $AllMembers = [System.Collections.Generic.HashSet[string]]::new()
+ foreach ($GroupId in $ReferencedGroupIds) {
+ if ($TenantGroupMembersCache.ContainsKey($GroupId)) {
+ foreach ($MemberId in $TenantGroupMembersCache[$GroupId]) {
+ [void]$AllMembers.Add($MemberId)
+ }
+ }
+ }
+
+ $MemberArray = $AllMembers | ForEach-Object { "'$_'" }
+ $MemberArrayString = $MemberArray -join ', '
+
+ if ($OperatorLower -eq 'in') {
+ return "`$_.customerId -in @($MemberArrayString)"
+ } else {
+ return "`$_.customerId -notin @($MemberArrayString)"
+ }
+ } else {
+ $ReferencedGroupId = [string]$Value.value
+ if (![string]::IsNullOrEmpty($ReferencedGroupId) -and -not $GuidRegex.IsMatch($ReferencedGroupId)) {
+ Write-Warning "Blocked invalid group ID: '$ReferencedGroupId'"
+ return $null
+ }
+ return "`$_.customerId -$OperatorLower `$script:TenantGroupMembersCache['$ReferencedGroupId']"
+ }
+ }
+ 'customVariable' {
+ $VariableName = if ($Value.variableName -is [string]) {
+ $Value.variableName
+ } elseif ($Value.variableName.value) {
+ $Value.variableName.value
+ } else {
+ [string]$Value.variableName
+ }
+ # Validate variable name - alphanumeric, underscores, hyphens, dots only
+ if (-not $SafeIdentifierRegex.IsMatch($VariableName)) {
+ Write-Warning "Blocked invalid custom variable name: '$VariableName'"
+ return $null
+ }
+ $ExpectedValue = Protect-StringValue -InputValue ([string]$Value.value)
+ if ($null -eq $ExpectedValue) { return $null }
+
+ switch ($OperatorLower) {
+ 'eq' {
+ return "(`$_.customVariables.ContainsKey('$VariableName') -and `$_.customVariables['$VariableName'].Value -eq '$ExpectedValue')"
+ }
+ 'ne' {
+ return "(-not `$_.customVariables.ContainsKey('$VariableName') -or `$_.customVariables['$VariableName'].Value -ne '$ExpectedValue')"
+ }
+ 'like' {
+ return "(`$_.customVariables.ContainsKey('$VariableName') -and `$_.customVariables['$VariableName'].Value -like '*$ExpectedValue*')"
+ }
+ 'notlike' {
+ return "(-not `$_.customVariables.ContainsKey('$VariableName') -or `$_.customVariables['$VariableName'].Value -notlike '*$ExpectedValue*')"
+ }
+ default {
+ Write-Warning "Unsupported operator '$OperatorLower' for customVariable"
+ return $null
+ }
+ }
+ }
+ default {
+ Write-Warning "Unknown property type: $Property"
+ return $null
+ }
+ }
+}
diff --git a/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1 b/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1
index 5ef92c5a997a..640cb120265a 100644
--- a/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1
+++ b/Modules/CIPPCore/Public/Add-CIPPW32ScriptApplication.ps1
@@ -149,7 +149,8 @@ function Add-CIPPW32ScriptApplication {
$UninstallScriptId = $null
if ($Properties.installScript) {
- $InstallScriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Properties.installScript))
+ $ReplacedInstallScript = Get-CIPPTextReplacement -Text $Properties.installScript -TenantFilter $TenantFilter
+ $InstallScriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($ReplacedInstallScript))
$InstallScriptBody = @{
'@odata.type' = '#microsoft.graph.win32LobAppInstallPowerShellScript'
displayName = 'install.ps1'
@@ -172,7 +173,8 @@ function Add-CIPPW32ScriptApplication {
}
if ($Properties.uninstallScript) {
- $UninstallScriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Properties.uninstallScript))
+ $ReplacedUninstallScript = Get-CIPPTextReplacement -Text $Properties.uninstallScript -TenantFilter $TenantFilter
+ $UninstallScriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($ReplacedUninstallScript))
$UninstallScriptBody = @{
'@odata.type' = '#microsoft.graph.win32LobAppUninstallPowerShellScript'
displayName = 'uninstall.ps1'
diff --git a/Modules/CIPPCore/Public/DeltaQueries/Test-DeltaQueryConditions.ps1 b/Modules/CIPPCore/Public/DeltaQueries/Test-DeltaQueryConditions.ps1
index a510320b160a..140509db5853 100644
--- a/Modules/CIPPCore/Public/DeltaQueries/Test-DeltaQueryConditions.ps1
+++ b/Modules/CIPPCore/Public/DeltaQueries/Test-DeltaQueryConditions.ps1
@@ -106,35 +106,33 @@ function Test-DeltaQueryConditions {
$conditions = $Trigger.Conditions | ConvertFrom-Json | Where-Object { $_.Input.value -ne '' -and $_.Input.value -ne $null }
if ($conditions) {
- # Initialize collections for condition strings
- $conditionStrings = [System.Collections.Generic.List[string]]::new()
+ # Build human-readable clause for logging
$CIPPClause = [System.Collections.Generic.List[string]]::new()
+ foreach ($condition in $conditions) {
+ $CIPPClause.Add("$($condition.Property.label) is $($condition.Operator.label) $($condition.Input.value)")
+ }
+ Write-Information "Testing delta query conditions: $($CIPPClause -join ' and ')"
+ # Build sanitized condition strings instead of direct evaluation
+ $conditionStrings = [System.Collections.Generic.List[string]]::new()
+ $validConditions = $true
foreach ($condition in $conditions) {
- # Handle array vs single values
- $value = if ($condition.Input.value -is [array]) {
- $arrayAsString = $condition.Input.value | ForEach-Object {
- "'$_'"
- }
- "@($($arrayAsString -join ', '))"
- } else {
- "'$($condition.Input.value)'"
+ $sanitized = Test-CIPPConditionFilter -Condition $condition
+ if ($null -eq $sanitized) {
+ Write-Warning "Skipping due to invalid condition for property '$($condition.Property.label)'"
+ $validConditions = $false
+ break
}
-
- # Build PowerShell condition string
- $conditionStrings.Add("`$(`$_.$($condition.Property.label)) -$($condition.Operator.value) $value")
- $CIPPClause.Add("$($condition.Property.label) is $($condition.Operator.label) $value")
+ $conditionStrings.Add($sanitized)
}
- # Join all conditions with AND
- $finalCondition = $conditionStrings -join ' -AND '
-
- Write-Information "Testing delta query conditions: $finalCondition"
- Write-Information "Human readable: $($CIPPClause -join ' and ')"
-
- # Apply conditions to filter the data using a script block instead of Invoke-Expression
- $scriptBlock = [scriptblock]::Create("param(`$_) $finalCondition")
- $MatchedData = $Data | Where-Object $scriptBlock
+ if ($validConditions -and $conditionStrings.Count -gt 0) {
+ $WhereString = $conditionStrings -join ' -and '
+ $WhereBlock = [ScriptBlock]::Create($WhereString)
+ $MatchedData = $Data | Where-Object $WhereBlock
+ } else {
+ $MatchedData = @()
+ }
} else {
Write-Information 'No valid conditions found in trigger configuration.'
$MatchedData = $Data
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1
index 73b1a1bce23b..29be42487b60 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecTenantGroup.ps1
@@ -23,6 +23,26 @@ function Invoke-ExecTenantGroup {
$dynamicRules = $Request.Body.dynamicRules
$ruleLogic = $Request.Body.ruleLogic ?? 'and'
+ # Validate dynamic rules to prevent code injection
+ if ($groupType -eq 'dynamic' -and $dynamicRules) {
+ $AllowedDynamicOperators = @('eq', 'ne', 'like', 'notlike', 'in', 'notin', 'contains', 'notcontains')
+ $AllowedDynamicProperties = @('delegatedAccessStatus', 'availableLicense', 'availableServicePlan', 'tenantGroupMember', 'customVariable')
+ foreach ($rule in $dynamicRules) {
+ if ($rule.operator -and $rule.operator.ToLower() -notin $AllowedDynamicOperators) {
+ return ([HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::BadRequest
+ Body = @{ Results = "Invalid operator in dynamic rule: $($rule.operator)" }
+ })
+ }
+ if ($rule.property -and $rule.property -notin $AllowedDynamicProperties) {
+ return ([HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::BadRequest
+ Body = @{ Results = "Invalid property in dynamic rule: $($rule.property)" }
+ })
+ }
+ }
+ }
+
$AllowedGroups = Test-CippAccess -Request $Request -GroupList
if ($AllowedGroups -notcontains 'AllGroups') {
return ([HttpResponseContext]@{
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1
index 47d72e537885..2c7cc7b4ad9d 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1
@@ -1,4 +1,4 @@
-Function Invoke-ExecResetMFA {
+function Invoke-ExecResetMFA {
<#
.FUNCTIONALITY
Entrypoint
@@ -7,8 +7,8 @@ Function Invoke-ExecResetMFA {
#>
[CmdletBinding()]
param($Request, $TriggerMetadata)
- $Headers = $Request.Headers
+ $Headers = $Request.Headers
# Interact with query parameters or the body of the request.
$TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Invoke-ListMDEOnboarding.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Invoke-ListMDEOnboarding.ps1
new file mode 100644
index 000000000000..9c87485beb35
--- /dev/null
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Security/Invoke-ListMDEOnboarding.ps1
@@ -0,0 +1,54 @@
+function Invoke-ListMDEOnboarding {
+ <#
+ .FUNCTIONALITY
+ Entrypoint
+ .ROLE
+ Security.Defender.Read
+ #>
+ [CmdletBinding()]
+ param($Request, $TriggerMetadata)
+ $TenantFilter = $Request.Query.tenantFilter
+ $UseReportDB = $Request.Query.UseReportDB
+
+ try {
+ if ($UseReportDB -eq 'true') {
+ try {
+ $GraphRequest = Get-CIPPMDEOnboardingReport -TenantFilter $TenantFilter -ErrorAction Stop
+ $StatusCode = [HttpStatusCode]::OK
+ } catch {
+ Write-Host "Error retrieving MDE onboarding status from report database: $($_.Exception.Message)"
+ $StatusCode = [HttpStatusCode]::InternalServerError
+ $GraphRequest = $_.Exception.Message
+ }
+
+ return ([HttpResponseContext]@{
+ StatusCode = $StatusCode
+ Body = @($GraphRequest)
+ })
+ }
+
+ $ConnectorId = 'fc780465-2017-40d4-a0c5-307022471b92'
+ $ConnectorUri = "https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/$ConnectorId"
+ try {
+ $ConnectorState = New-GraphGetRequest -uri $ConnectorUri -tenantid $TenantFilter
+ $PartnerState = $ConnectorState.partnerState
+ } catch {
+ $PartnerState = 'unavailable'
+ }
+
+ $GraphRequest = [PSCustomObject]@{
+ Tenant = $TenantFilter
+ partnerState = $PartnerState
+ }
+ $StatusCode = [HttpStatusCode]::OK
+ } catch {
+ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
+ $StatusCode = [HttpStatusCode]::Forbidden
+ $GraphRequest = $ErrorMessage
+ }
+
+ return ([HttpResponseContext]@{
+ StatusCode = $StatusCode
+ Body = @($GraphRequest)
+ })
+}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1
index df6818654545..31b38f41bc1e 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-AddAlert.ps1
@@ -7,31 +7,61 @@ function Invoke-AddAlert {
#>
[CmdletBinding()]
param($Request, $TriggerMetadata)
- # Interact with query parameters or the body of the request.
- $Tenants = $Request.Body.tenantFilter
- $Conditions = $Request.Body.conditions | ConvertTo-Json -Compress -Depth 10 | Out-String
- $TenantsJson = $Tenants | ConvertTo-Json -Compress -Depth 10 | Out-String
- $excludedTenantsJson = $Request.Body.excludedTenants | ConvertTo-Json -Compress -Depth 10 | Out-String
- $Actions = $Request.Body.actions | ConvertTo-Json -Compress -Depth 10 | Out-String
- $RowKey = $Request.Body.RowKey ? $Request.Body.RowKey : (New-Guid).ToString()
- $CompleteObject = @{
- Tenants = [string]$TenantsJson
- excludedTenants = [string]$excludedTenantsJson
- Conditions = [string]$Conditions
- Actions = [string]$Actions
- type = $Request.Body.logbook.value
- RowKey = $RowKey
- PartitionKey = 'Webhookv2'
- AlertComment = [string]$Request.Body.AlertComment
- }
- $WebhookTable = Get-CippTable -TableName 'WebhookRules'
- Add-CIPPAzDataTableEntity @WebhookTable -Entity $CompleteObject -Force
- $Results = "Added Audit Log Alert for $($Tenants.count) tenants. It may take up to four hours before Microsoft starts delivering these alerts."
- Write-LogMessage -API 'AddAlert' -message $Results -sev Info -LogData $CompleteObject -headers $Request.Headers
- return ([HttpResponseContext]@{
- StatusCode = [HttpStatusCode]::OK
- Body = @{ 'Results' = @($Results) }
- })
+ try {
+
+ $Conditions = $Request.Body.conditions
+ Write-Information "Received request to add alert with conditions: $($Conditions | ConvertTo-Json -Compress -Depth 10)"
+
+ # Validate conditions to prevent code injection via operator/property fields
+ $AllowedOperators = @('eq', 'ne', 'like', 'notlike', 'match', 'notmatch', 'gt', 'lt', 'ge', 'le', 'in', 'notin', 'contains', 'notcontains')
+ $SafePropertyRegex = [regex]'^[a-zA-Z0-9_.]+$'
+ foreach ($condition in $Conditions) {
+ if ($condition.Operator.value -and $condition.Operator.value.ToLower() -notin $AllowedOperators) {
+ return ([HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::BadRequest
+ Body = @{ error = "Invalid operator: $($condition.Operator.value)" }
+ })
+ }
+ if ($condition.Property.label -and -not $SafePropertyRegex.IsMatch($condition.Property.label)) {
+ return ([HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::BadRequest
+ Body = @{ error = "Invalid property name: $($condition.Property.label)" }
+ })
+ }
+ }
+ $Tenants = $Request.Body.tenantFilter
+ $Conditions = $Request.Body.conditions | ConvertTo-Json -Compress -Depth 10 | Out-String
+ $TenantsJson = $Tenants | ConvertTo-Json -Compress -Depth 10 | Out-String
+ $excludedTenantsJson = $Request.Body.excludedTenants | ConvertTo-Json -Compress -Depth 10 | Out-String
+ $Actions = $Request.Body.actions | ConvertTo-Json -Compress -Depth 10 | Out-String
+ $RowKey = $Request.Body.RowKey ? $Request.Body.RowKey : (New-Guid).ToString()
+ $CompleteObject = @{
+ Tenants = [string]$TenantsJson
+ excludedTenants = [string]$excludedTenantsJson
+ Conditions = [string]$Conditions
+ Actions = [string]$Actions
+ type = $Request.Body.logbook.value
+ RowKey = $RowKey
+ PartitionKey = 'Webhookv2'
+ AlertComment = [string]$Request.Body.AlertComment
+ CustomSubject = [string]$Request.Body.CustomSubject
+ }
+ $WebhookTable = Get-CippTable -TableName 'WebhookRules'
+ Add-CIPPAzDataTableEntity @WebhookTable -Entity $CompleteObject -Force
+ $Results = "Added Audit Log Alert for $($Tenants.count) tenants. It may take up to four hours before Microsoft starts delivering these alerts."
+ Write-LogMessage -API 'AddAlert' -message $Results -sev Info -LogData $CompleteObject -headers $Request.Headers
+
+ return ([HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::OK
+ Body = @{ 'Results' = @($Results) }
+ })
+ } catch {
+ Write-LogMessage -API 'AddAlert' -message "Error adding alert: $_" -sev Error -headers $Request.Headers
+ return ([HttpResponseContext]@{
+ StatusCode = [HttpStatusCode]::InternalServerError
+ Body = @{ error = "Failed to add alert: $_" }
+ })
+ }
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1
index 283913c6702c..6125cc632780 100644
--- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-RemoveStandardTemplate.ps1
@@ -16,14 +16,15 @@ function Invoke-RemoveStandardTemplate {
$ID = $Request.Body.ID ?? $Request.Query.ID
try {
$Table = Get-CippTable -tablename 'templates'
- $Filter = "PartitionKey eq 'StandardsTemplateV2' and (GUID eq '$ID' or RowKey eq '$ID' or OriginalEntityId eq '$ID')"
- $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey, ETag, JSON
+ $Filter = "PartitionKey eq 'StandardsTemplateV2' and (RowKey eq '$ID' or OriginalEntityId eq '$ID' or OriginalEntityId eq guid'$ID')"
+ $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter
if ($ClearRow.JSON) {
$TemplateName = (ConvertFrom-Json -InputObject $ClearRow.JSON -ErrorAction SilentlyContinue).templateName
} else {
$TemplateName = ''
}
- Remove-AzDataTableEntity -Force @Table -Entity $ClearRow
+ $Entities = Get-AzDataTableEntity @Table -Filter $Filter
+ Remove-AzDataTableEntity -Force @Table -Entity $Entities
$Result = "Removed Standards Template named: '$($TemplateName)' with id: $($ID)"
Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev Info
$StatusCode = [HttpStatusCode]::OK
diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1
index 907e06857fa7..0612ec3ef23b 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1
@@ -33,8 +33,7 @@ function Invoke-ListLogs {
$TenantList = Get-Tenants -IncludeErrors | Where-Object { $_.customerId -in $AllowedTenants }
}
- if ($AllowedTenants -contains 'AllTenants' -or ($AllowedTenants -notcontains 'AllTenants' -and ($TenantList.defaultDomainName -contains $Row.Tenant -or $Row.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $Row.TenantId)) ) {
-
+ if ($AllowedTenants -contains 'AllTenants' -or ($AllowedTenants -notcontains 'AllTenants' -and ($TenantList.defaultDomainName -contains $Row.Tenant -or $Row.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $Row.TenantId -or $TenantList.initialDomainName -contains $Row.Tenant)) ) {
if ($Row.StandardTemplateId) {
$Standard = ($Templates | Where-Object { $_.RowKey -eq $Row.StandardTemplateId }).JSON | ConvertFrom-Json
diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1
index a8c64da33fec..7f28dac0cbf0 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1
@@ -27,18 +27,21 @@ function Start-AuditLogSearchCreation {
$StartTime = ($Now.AddSeconds(-$Now.Seconds)).AddHours(-1)
$EndTime = $Now.AddSeconds(-$Now.Seconds)
- Write-Information 'Audit Logs: Creating new searches'
+ # Pre-expand tenant groups once per config entry to avoid repeated calls per tenant
+ foreach ($ConfigEntry in $ConfigEntries) {
+ $ConfigEntry | Add-Member -MemberType NoteProperty -Name 'ExpandedTenants' -Value (Expand-CIPPTenantGroups -TenantFilter ($ConfigEntry.Tenants)).value -Force
+ }
+
+ Write-Information "Audit Logs: Building batch for $($TenantList.Count) tenants across $($ConfigEntries.Count) config entries"
$Batch = foreach ($Tenant in $TenantList) {
- Write-Information "Processing tenant $($Tenant.defaultDomainName) - $($Tenant.customerId)"
$TenantInConfig = $false
$MatchingConfigs = [System.Collections.Generic.List[object]]::new()
foreach ($ConfigEntry in $ConfigEntries) {
if ($ConfigEntry.excludedTenants.value -contains $Tenant.defaultDomainName) {
continue
}
- $TenantsList = Expand-CIPPTenantGroups -TenantFilter ($ConfigEntry.Tenants)
- if ($TenantsList.value -contains $Tenant.defaultDomainName -or $TenantsList.value -contains 'AllTenants') {
+ if ($ConfigEntry.ExpandedTenants -contains $Tenant.defaultDomainName -or $ConfigEntry.ExpandedTenants -contains 'AllTenants') {
$TenantInConfig = $true
$MatchingConfigs.Add($ConfigEntry)
}
diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1
index ef944134eef3..57f64ac7cde1 100644
--- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1
+++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-CIPPOrchestrator.ps1
@@ -70,7 +70,7 @@ function Start-CIPPOrchestrator {
# Clean up the stored input object after starting the orchestration
try {
- $Entities = Get-AzDataTableEntity @OrchestratorTable -Filter "PartitionKey eq 'Input' and (RowKey eq '$InputObjectGuid' or OriginalEntityId eq '$InputObjectGuid')" -Property PartitionKey, RowKey
+ $Entities = Get-AzDataTableEntity @OrchestratorTable -Filter "PartitionKey eq 'Input' and (RowKey eq '$InputObjectGuid' or OriginalEntityId eq '$InputObjectGuid' or OriginalEntityId eq guid'$InputObjectGuid')" -Property PartitionKey, RowKey
Remove-AzDataTableEntity @OrchestratorTable -Entity $Entities -Force
Write-Information "Cleaned up stored input object: $InputObjectGuid"
} catch {
diff --git a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1
index 5ffcaf908254..aedf3b274f98 100644
--- a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1
+++ b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1
@@ -113,11 +113,36 @@ function Get-CIPPTenantAlignment {
$TemplateAssignedTenants = @()
$AppliestoAllTenants = $false
+ # Build excluded tenants list (mirrors Get-CIPPStandards logic, including group expansion)
+ $ExcludedTenantValues = [System.Collections.Generic.List[string]]::new()
+ if ($Template.excludedTenants) {
+ $ExcludeList = if ($Template.excludedTenants -is [System.Collections.IEnumerable] -and -not ($Template.excludedTenants -is [string])) {
+ $Template.excludedTenants
+ } else {
+ @($Template.excludedTenants)
+ }
+ foreach ($excludeItem in $ExcludeList) {
+ $ExcludeValue = $excludeItem.value
+ if ($excludeItem.type -eq 'Group') {
+ $GroupMembers = $TenantGroups | Where-Object { $_.Id -eq $ExcludeValue }
+ if ($GroupMembers -and $GroupMembers.Members) {
+ foreach ($member in $GroupMembers.Members.defaultDomainName) {
+ $ExcludedTenantValues.Add($member)
+ }
+ }
+ } else {
+ if ($ExcludeValue) { $ExcludedTenantValues.Add($ExcludeValue) }
+ }
+ }
+ }
+ $ExcludedTenantsSet = [System.Collections.Generic.HashSet[string]]::new()
+ foreach ($item in $ExcludedTenantValues) { [void]$ExcludedTenantsSet.Add($item) }
+
if ($Template.tenantFilter -and $Template.tenantFilter.Count -gt 0) {
# Extract tenant values from the tenantFilter array
$TenantValues = [System.Collections.Generic.List[string]]::new()
foreach ($filterItem in $Template.tenantFilter) {
- if ($filterItem.type -eq 'group') {
+ if ($filterItem.type -eq 'Group') {
# Look up group members by Id (GUID in the value field)
$GroupMembers = $TenantGroups | Where-Object { $_.Id -eq $filterItem.value }
if ($GroupMembers -and $GroupMembers.Members) {
@@ -224,6 +249,10 @@ function Get-CIPPTenantAlignment {
} else { $null }
foreach ($TenantName in $TenantStandards.Keys) {
+ # Skip explicitly excluded tenants regardless of AllTenants or specific assignment
+ if ($ExcludedTenantsSet.Contains($TenantName)) {
+ continue
+ }
# Check tenant scope with HashSet and cache tenant data
if (-not $AppliestoAllTenants) {
if ($TemplateAssignedTenantsSet -and -not $TemplateAssignedTenantsSet.Contains($TenantName)) {
diff --git a/Modules/CIPPCore/Public/Get-CIPPMDEOnboardingReport.ps1 b/Modules/CIPPCore/Public/Get-CIPPMDEOnboardingReport.ps1
new file mode 100644
index 000000000000..adfc44a8f99b
--- /dev/null
+++ b/Modules/CIPPCore/Public/Get-CIPPMDEOnboardingReport.ps1
@@ -0,0 +1,56 @@
+function Get-CIPPMDEOnboardingReport {
+ <#
+ .SYNOPSIS
+ Generates an MDE onboarding status report from the CIPP Reporting database
+ .PARAMETER TenantFilter
+ The tenant to generate the report for
+ #>
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$TenantFilter
+ )
+
+ try {
+ if ($TenantFilter -eq 'AllTenants') {
+ $AllItems = Get-CIPPDbItem -TenantFilter 'allTenants' -Type 'MDEOnboarding'
+ $Tenants = @($AllItems | Where-Object { $_.RowKey -ne 'MDEOnboarding-Count' } | Select-Object -ExpandProperty PartitionKey -Unique)
+
+ $TenantList = Get-Tenants -IncludeErrors
+ $Tenants = $Tenants | Where-Object { $TenantList.defaultDomainName -contains $_ }
+
+ $AllResults = [System.Collections.Generic.List[PSCustomObject]]::new()
+ foreach ($Tenant in $Tenants) {
+ try {
+ $TenantResults = Get-CIPPMDEOnboardingReport -TenantFilter $Tenant
+ foreach ($Result in $TenantResults) {
+ $Result | Add-Member -NotePropertyName 'Tenant' -NotePropertyValue $Tenant -Force
+ $AllResults.Add($Result)
+ }
+ } catch {
+ Write-LogMessage -API 'MDEOnboardingReport' -tenant $Tenant -message "Failed to get report for tenant: $($_.Exception.Message)" -sev Warning
+ }
+ }
+ return $AllResults
+ }
+
+ $Items = Get-CIPPDbItem -TenantFilter $TenantFilter -Type 'MDEOnboarding' | Where-Object { $_.RowKey -ne 'MDEOnboarding-Count' }
+ if (-not $Items) {
+ throw 'No MDE onboarding data found in reporting database. Sync the report data first.'
+ }
+
+ $CacheTimestamp = ($Items | Where-Object { $_.Timestamp } | Sort-Object Timestamp -Descending | Select-Object -First 1).Timestamp
+
+ $AllResults = [System.Collections.Generic.List[PSCustomObject]]::new()
+ foreach ($Item in $Items) {
+ $ParsedData = $Item.Data | ConvertFrom-Json
+ $ParsedData | Add-Member -NotePropertyName 'CacheTimestamp' -NotePropertyValue $CacheTimestamp -Force
+ $AllResults.Add($ParsedData)
+ }
+
+ return $AllResults
+ } catch {
+ Write-LogMessage -API 'MDEOnboardingReport' -tenant $TenantFilter -message "Failed to generate MDE onboarding report: $($_.Exception.Message)" -sev Error
+ throw
+ }
+}
diff --git a/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1 b/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1
index a86ce4751645..7798ab8a4047 100644
--- a/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1
+++ b/Modules/CIPPCore/Public/Get-CIPPTextReplacement.ps1
@@ -12,7 +12,7 @@ function Get-CIPPTextReplacement {
Get-CIPPTextReplacement -TenantFilter 'contoso.com' -Text 'Hello %tenantname%'
#>
param (
- [string]$TenantFilter,
+ [string]$TenantFilter = $env:TenantID,
$Text,
[switch]$EscapeForJson
)
diff --git a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1
index 93c373c1fa98..19e6aad21817 100644
--- a/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1
+++ b/Modules/CIPPCore/Public/GraphHelper/New-ExoRequest.ps1
@@ -56,7 +56,7 @@ function New-ExoRequest {
}
$ExoBody = Get-CIPPTextReplacement -TenantFilter $tenantid -Text $ExoBody -EscapeForJson
- $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $tenantid -or $_.customerId -eq $tenantid }
+ $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $tenantid -or $_.customerId -eq $tenantid -or $_.initialDomainName -eq $tenantid } | Select-Object -First 1
if (-not $Tenant -and $NoAuthCheck -eq $true) {
$Tenant = [PSCustomObject]@{
customerId = $tenantid
diff --git a/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1
index 0cfeb492af04..194ed10330fd 100644
--- a/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1
+++ b/Modules/CIPPCore/Public/Invoke-CIPPDBCacheCollection.ps1
@@ -117,6 +117,7 @@ function Invoke-CIPPDBCacheCollection {
'ManagedDeviceEncryptionStates'
'IntuneAppProtectionPolicies'
'DetectedApps'
+ 'MDEOnboarding'
)
}
diff --git a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1
index b9fba00543b2..923eb9f0b060 100644
--- a/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1
+++ b/Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1
@@ -10,7 +10,8 @@ function New-CIPPAlertTemplate {
$CIPPURL,
$Tenant,
$AuditLogLink,
- $AlertComment
+ $AlertComment,
+ $CustomSubject
)
$Appname = '[{"Application Name":"ACOM Azure Website","Application IDs":"23523755-3a2b-41ca-9315-f81f3f566a95"},{"Application Name":"AEM-DualAuth","Application IDs":"69893ee3-dd10-4b1c-832d-4870354be3d8"},{"Application Name":"ASM Campaign Servicing","Application IDs":"0cb7b9ec-5336-483b-bc31-b15b5788de71"},{"Application Name":"Azure Advanced Threat Protection","Application IDs":"7b7531ad-5926-4f2d-8a1d-38495ad33e17"},{"Application Name":"Azure Data Lake","Application IDs":"e9f49c6b-5ce5-44c8-925d-015017e9f7ad"},{"Application Name":"Azure Lab Services Portal","Application IDs":"835b2a73-6e10-4aa5-a979-21dfda45231c"},{"Application Name":"Azure Portal","Application IDs":"c44b4083-3bb0-49c1-b47d-974e53cbdf3c"},{"Application Name":"AzureSupportCenter","Application IDs":"37182072-3c9c-4f6a-a4b3-b3f91cacffce"},{"Application Name":"Bing","Application IDs":"9ea1ad79-fdb6-4f9a-8bc3-2b70f96e34c7"},{"Application Name":"CPIM Service","Application IDs":"bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4"},{"Application Name":"CRM Power BI Integration","Application IDs":"e64aa8bc-8eb4-40e2-898b-cf261a25954f"},{"Application Name":"Dataverse","Application IDs":"00000007-0000-0000-c000-000000000000"},{"Application Name":"Enterprise Roaming and Backup","Application IDs":"60c8bde5-3167-4f92-8fdb-059f6176dc0f"},{"Application Name":"IAM Supportability","Application IDs":"a57aca87-cbc0-4f3c-8b9e-dc095fdc8978"},{"Application Name":"IrisSelectionFrontDoor","Application IDs":"16aeb910-ce68-41d1-9ac3-9e1673ac9575"},{"Application Name":"MCAPI Authorization Prod","Application IDs":"d73f4b35-55c9-48c7-8b10-651f6f2acb2e"},{"Application Name":"Media Analysis and Transformation Service","Application IDs":"944f0bd1-117b-4b1c-af26-804ed95e767e
0cd196ee-71bf-4fd6-a57c-b491ffd4fb1e"},{"Application Name":"Microsoft 365 Support Service","Application IDs":"ee272b19-4411-433f-8f28-5c13cb6fd407"},{"Application Name":"Microsoft App Access Panel","Application IDs":"0000000c-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Approval Management","Application IDs":"65d91a3d-ab74-42e6-8a2f-0add61688c74
38049638-cc2c-4cde-abe4-4479d721ed44"},{"Application Name":"Microsoft Authentication Broker","Application IDs":"29d9ed98-a469-4536-ade2-f981bc1d605e"},{"Application Name":"Microsoft Azure CLI","Application IDs":"04b07795-8ddb-461a-bbee-02f9e1bf7b46"},{"Application Name":"Microsoft Azure PowerShell","Application IDs":"1950a258-227b-4e31-a9cf-717495945fc2"},{"Application Name":"Microsoft Bing Search","Application IDs":"cf36b471-5b44-428c-9ce7-313bf84528de"},{"Application Name":"Microsoft Bing Search for Microsoft Edge","Application IDs":"2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8"},{"Application Name":"Microsoft Bing Default Search Engine","Application IDs":"1786c5ed-9644-47b2-8aa0-7201292175b6"},{"Application Name":"Microsoft Defender for Cloud Apps","Application IDs":"3090ab82-f1c1-4cdf-af2c-5d7a6f3e2cc7"},{"Application Name":"Microsoft Docs","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Dynamics ERP","Application IDs":"00000015-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Edge Insider Addons Prod","Application IDs":"6253bca8-faf2-4587-8f2f-b056d80998a7"},{"Application Name":"Microsoft Exchange Online Protection","Application IDs":"00000007-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Forms","Application IDs":"c9a559d2-7aab-4f13-a6ed-e7e9c52aec87"},{"Application Name":"Microsoft Graph","Application IDs":"00000003-0000-0000-c000-000000000000"},{"Application Name":"Microsoft Intune Web Company Portal","Application IDs":"74bcdadc-2fdc-4bb3-8459-76d06952a0e9"},{"Application Name":"Microsoft Intune Windows Agent","Application IDs":"fc0f3af4-6835-4174-b806-f7db311fd2f3"},{"Application Name":"Microsoft Learn","Application IDs":"18fbca16-2224-45f6-85b0-f7bf2b39b3f3"},{"Application Name":"Microsoft Office","Application IDs":"d3590ed6-52b3-4102-aeff-aad2292ab01c"},{"Application Name":"Microsoft Office 365 Portal","Application IDs":"00000006-0000-0ff1-ce00-000000000000"},{"Application Name":"Microsoft Office Web Apps Service","Application IDs":"67e3df25-268a-4324-a550-0de1c7f97287"},{"Application Name":"Microsoft Online Syndication Partner Portal","Application IDs":"d176f6e7-38e5-40c9-8a78-3998aab820e7"},{"Application Name":"Microsoft password reset service","Application IDs":"93625bc8-bfe2-437a-97e0-3d0060024faa"},{"Application Name":"Microsoft Power BI","Application IDs":"871c010f-5e61-4fb1-83ac-98610a7e9110"},{"Application Name":"Microsoft Storefronts","Application IDs":"28b567f6-162c-4f54-99a0-6887f387bbcc"},{"Application Name":"Microsoft Stream Portal","Application IDs":"cf53fce8-def6-4aeb-8d30-b158e7b1cf83"},{"Application Name":"Microsoft Substrate Management","Application IDs":"98db8bd6-0cc0-4e67-9de5-f187f1cd1b41"},{"Application Name":"Microsoft Support","Application IDs":"fdf9885b-dd37-42bf-82e5-c3129ef5a302"},{"Application Name":"Microsoft Teams","Application IDs":"1fec8e78-bce4-4aaf-ab1b-5451cc387264"},{"Application Name":"Microsoft Teams Services","Application IDs":"cc15fd57-2c6c-4117-a88c-83b1d56b4bbe"},{"Application Name":"Microsoft Teams Web Client","Application IDs":"5e3ce6c0-2b1f-4285-8d4b-75ee78787346"},{"Application Name":"Microsoft Whiteboard Services","Application IDs":"95de633a-083e-42f5-b444-a4295d8e9314"},{"Application Name":"O365 Suite UX","Application IDs":"4345a7b9-9a63-4910-a426-35363201d503"},{"Application Name":"Office 365 Exchange Online","Application IDs":"00000002-0000-0ff1-ce00-000000000000"},{"Application Name":"Office 365 Management","Application IDs":"00b41c95-dab0-4487-9791-b9d2c32c80f2"},{"Application Name":"Office 365 Search Service","Application IDs":"66a88757-258c-4c72-893c-3e8bed4d6899"},{"Application Name":"Office 365 SharePoint Online","Application IDs":"00000003-0000-0ff1-ce00-000000000000"},{"Application Name":"Office Delve","Application IDs":"94c63fef-13a3-47bc-8074-75af8c65887a"},{"Application Name":"Office Online Add-in SSO","Application IDs":"93d53678-613d-4013-afc1-62e9e444a0a5"},{"Application Name":"Office Online Client AAD- Augmentation Loop","Application IDs":"2abdc806-e091-4495-9b10-b04d93c3f040"},{"Application Name":"Office Online Client AAD- Loki","Application IDs":"b23dd4db-9142-4734-867f-3577f640ad0c"},{"Application Name":"Office Online Client AAD- Maker","Application IDs":"17d5e35f-655b-4fb0-8ae6-86356e9a49f5"},{"Application Name":"Office Online Client MSA- Loki","Application IDs":"b6e69c34-5f1f-4c34-8cdf-7fea120b8670"},{"Application Name":"Office Online Core SSO","Application IDs":"243c63a3-247d-41c5-9d83-7788c43f1c43"},{"Application Name":"Office Online Search","Application IDs":"a9b49b65-0a12-430b-9540-c80b3332c127"},{"Application Name":"Office.com","Application IDs":"4b233688-031c-404b-9a80-a4f3f2351f90"},{"Application Name":"Office365 Shell WCSS-Client","Application IDs":"89bee1f7-5e6e-4d8a-9f3d-ecd601259da7"},{"Application Name":"OfficeClientService","Application IDs":"0f698dd4-f011-4d23-a33e-b36416dcb1e6"},{"Application Name":"OfficeHome","Application IDs":"4765445b-32c6-49b0-83e6-1d93765276ca"},{"Application Name":"OfficeShredderWacClient","Application IDs":"4d5c2d63-cf83-4365-853c-925fd1a64357"},{"Application Name":"OMSOctopiPROD","Application IDs":"62256cef-54c0-4cb4-bcac-4c67989bdc40"},{"Application Name":"OneDrive SyncEngine","Application IDs":"ab9b8c07-8f02-4f72-87fa-80105867a763"},{"Application Name":"OneNote","Application IDs":"2d4d3d8e-2be3-4bef-9f87-7875a61c29de"},{"Application Name":"Outlook Mobile","Application IDs":"27922004-5251-4030-b22d-91ecd9a37ea4"},{"Application Name":"Partner Customer Delegated Admin Offline Processor","Application IDs":"a3475900-ccec-4a69-98f5-a65cd5dc5306"},{"Application Name":"Password Breach Authenticator","Application IDs":"bdd48c81-3a58-4ea9-849c-ebea7f6b6360"},{"Application Name":"Power BI Service","Application IDs":"00000009-0000-0000-c000-000000000000"},{"Application Name":"SharedWithMe","Application IDs":"ffcb16e8-f789-467c-8ce9-f826a080d987"},{"Application Name":"SharePoint Online Web Client Extensibility","Application IDs":"08e18876-6177-487e-b8b5-cf950c1e598c"},{"Application Name":"Signup","Application IDs":"b4bddae8-ab25-483e-8670-df09b9f1d0ea"},{"Application Name":"Skype for Business Online","Application IDs":"00000004-0000-0ff1-ce00-000000000000"},{"Application Name":"Sway","Application IDs":"905fcf26-4eb7-48a0-9ff0-8dcc7194b5ba"},{"Application Name":"Universal Store Native Client","Application IDs":"268761a2-03f3-40df-8a8b-c3db24145b6b"},{"Application Name":"Vortex [wsfed enabled]","Application IDs":"5572c4c0-d078-44ce-b81c-6cbf8d3ed39e"},{"Application Name":"Windows Azure Active Directory","Application IDs":"00000002-0000-0000-c000-000000000000"},{"Application Name":"Windows Azure Service Management API","Application IDs":"797f4846-ba00-4fd7-ba43-dac1f8f63013"},{"Application Name":"WindowsDefenderATP Portal","Application IDs":"a3b79187-70b2-4139-83f9-6016c58cd27b"},{"Application Name":"Windows Search","Application IDs":"26a7ee05-5602-4d76-a7ba-eae8b7b67941"},{"Application Name":"Windows Spotlight","Application IDs":"1b3c667f-cde3-4090-b60b-3d2abd0117f0"},{"Application Name":"Windows Store for Business","Application IDs":"45a330b1-b1ec-4cc1-9161-9f03992aa49f"},{"Application Name":"Yammer","Application IDs":"00000005-0000-0ff1-ce00-000000000000"},{"Application Name":"Yammer Web","Application IDs":"c1c74fed-04c9-4704-80dc-9f79a2e515cb"},{"Application Name":"Yammer Web Embed","Application IDs":"e1ef36fd-b883-4dbf-97f0-9ece4b576fc6"}]' | ConvertFrom-Json | Where-Object -Property 'Application IDs' -EQ $data.applicationId
# Get the function app root directory by navigating from the module location
@@ -103,9 +104,9 @@ function New-CIPPAlertTemplate {
}
}
if ($FoundForwarding -eq $true) {
- $Title = "$($TenantFilter) - New forwarding or redirect Rule Detected for $($data.UserId)"
+ $Title = "$($Tenant) - New forwarding or redirect Rule Detected for $($data.UserId)"
} else {
- $Title = "$($TenantFilter) - New Rule Detected for $($data.UserId)"
+ $Title = "$($Tenant) - New Rule Detected for $($data.UserId)"
}
$RuleTable = ($Data.CIPPParameters | ConvertFrom-Json | ConvertTo-Html -Fragment | Out-String).Replace('