Skip to content

Commit 6b3f8ad

Browse files
authored
Merge pull request #396 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents d72f2fb + d523823 commit 6b3f8ad

File tree

6 files changed

+272
-25
lines changed

6 files changed

+272
-25
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
function Get-CIPPAlertLicenseAssignmentErrors {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
#>
6+
[CmdletBinding()]
7+
Param (
8+
[Parameter(Mandatory)]
9+
$TenantFilter,
10+
[Alias('input')]
11+
$InputValue
12+
)
13+
14+
# Define error code translations for human-readable messages
15+
$ErrorTranslations = @(
16+
@{
17+
ErrorCode = "CountViolation"
18+
Description = "Not enough licenses available - the organization has exceeded the number of available licenses for this SKU"
19+
},
20+
@{
21+
ErrorCode = "MutuallyExclusiveViolation"
22+
Description = "Conflicting licenses assigned - this license cannot be assigned alongside another license the user already has"
23+
},
24+
@{
25+
ErrorCode = "ProhibitedInUsageLocationViolation"
26+
Description = "License not available in user's location - this license cannot be assigned to users in the user's current usage location"
27+
},
28+
@{
29+
ErrorCode = "UniquenessViolation"
30+
Description = "Duplicate license assignment - this license can only be assigned once per user"
31+
},
32+
@{
33+
ErrorCode = "Unknown"
34+
Description = "Unknown license assignment error - an unspecified error occurred during license assignment"
35+
}
36+
)
37+
38+
try {
39+
# Get all users with license assignment states from Graph API
40+
$Users = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$select=id,userPrincipalName,displayName,licenseAssignmentStates&`$top=999" -tenantid $TenantFilter
41+
42+
# Filter users who have license assignment violations
43+
$UsersWithViolations = $Users | Where-Object {
44+
$_.licenseAssignmentStates -and
45+
($_.licenseAssignmentStates | Where-Object {
46+
$_.error -and (
47+
$_.error -like "*CountViolation*" -or
48+
$_.error -like "*MutuallyExclusiveViolation*" -or
49+
$_.error -like "*ProhibitedInUsageLocationViolation*" -or
50+
$_.error -like "*UniquenessViolation*" -or
51+
$_.error -like "*Unknown*"
52+
)
53+
})
54+
}
55+
56+
# Build alert messages for users with violations
57+
$LicenseAssignmentErrors = foreach ($User in $UsersWithViolations) {
58+
$ViolationErrors = $User.licenseAssignmentStates | Where-Object {
59+
$_.error -and (
60+
$_.error -like "*CountViolation*" -or
61+
$_.error -like "*MutuallyExclusiveViolation*" -or
62+
$_.error -like "*ProhibitedInUsageLocationViolation*" -or
63+
$_.error -like "*UniquenessViolation*" -or
64+
$_.error -like "*Unknown*"
65+
)
66+
}
67+
68+
foreach ($Violation in $ViolationErrors) {
69+
# Find matching error translation
70+
$ErrorTranslation = $ErrorTranslations | Where-Object { $Violation.error -like "*$($_.ErrorCode)*" } | Select-Object -First 1
71+
$HumanReadableError = if ($ErrorTranslation) {
72+
$ErrorTranslation.Description
73+
} else {
74+
"Unknown license assignment error: $($Violation.error)"
75+
}
76+
77+
$PrettyName = Convert-SKUname -skuID $Violation.skuId
78+
79+
"$($User.userPrincipalName): $HumanReadableError (License: $PrettyName)"
80+
}
81+
}
82+
83+
# If errors are found, write alert
84+
if ($LicenseAssignmentErrors) {
85+
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $LicenseAssignmentErrors
86+
}
87+
88+
} catch {
89+
Write-LogMessage -message "Failed to check license assignment errors: $($_.exception.message)" -API 'License Assignment Alerts' -tenant $TenantFilter -sev Error
90+
}
91+
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ function Push-CippDriftManagement {
3131
Status = $_.status
3232
}
3333
}
34-
$GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -CIPPURL $CIPPURL -Tenant $Item.tenant -InputObject 'driftStandard'
34+
35+
$GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -CIPPURL $CIPPURL -Tenant $Item.Tenant -InputObject 'driftStandard' -AuditLogLink $drift.standardId
3536
$CIPPAlert = @{
3637
Type = 'email'
3738
Title = $GenerateEmail.title
@@ -51,7 +52,7 @@ function Push-CippDriftManagement {
5152
Type = 'webhook'
5253
Title = $GenerateEmail.title
5354
JSONContent = $WebhookData
54-
TenantFilter = $Item.tenant
55+
TenantFilter = $Item.Tenant
5556
}
5657
Write-Host 'Sending Webhook Content'
5758
Send-CIPPAlert @CippAlert -altWebhook $webhook
@@ -60,7 +61,7 @@ function Push-CippDriftManagement {
6061
Type = 'psa'
6162
Title = $GenerateEmail.title
6263
HTMLContent = $GenerateEmail.htmlcontent
63-
TenantFilter = $TenantFilter
64+
TenantFilter = $Item.Tenant
6465
}
6566
Send-CIPPAlert @CIPPAlert
6667
return $true

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,19 @@ function Invoke-ExecUpdateDriftDeviation {
7272
Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Scheduled drift remediation task for $Setting" -Sev 'Info'
7373
}
7474
if ($Deviation.status -eq 'deniedDelete') {
75-
if ($Deviation.standardName -like 'ConditionalAccessTemplate*') {
76-
$ID = $Deviation.standardName -replace 'ConditionalAccessTemplates.', ''
77-
Write-Host "Going to delete CA Policy with ID $ID. Deviation Name is $($Deviation.standardName)"
78-
$null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies/$($ID)" -type DELETE -tenant $TenantFilter -asapp $true
79-
"Deleted CA Policy $($ID)"
80-
Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted Conditional Access Policy with ID $($ID)" -Sev 'Info'
75+
$Policy = $Deviation.receivedValue | ConvertFrom-Json -ErrorAction SilentlyContinue
76+
Write-Host "Policy is $($Policy)"
77+
$URLName = Get-CIPPURLName -Template $Policy
78+
if ($Policy -and $URLName) {
79+
Write-Host "Going to delete Policy with ID $($policy.ID) Deviation Name is $($Deviation.standardName)"
80+
$null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/$($URLName)/$($policy.id)" -type DELETE -tenant $TenantFilter
81+
"Deleted Policy $($ID)"
82+
Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted Policy with ID $($ID)" -Sev 'Info'
83+
} else {
84+
"could not find policy with ID $($ID)"
85+
Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Could not find Policy with ID $($ID) to delete for remediation" -Sev 'Warning'
8186
}
8287

83-
if ($Deviation.standardName -like 'IntuneTemplates*') {
84-
New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($UrlName)('$($PolicyId)')" -type DELETE -tenant $TenantFilter
85-
"Deleted Intune Policy $($ID)"
86-
Write-LogMessage -tenant $TenantFilter -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Deleted Intune Policy with ID $($ID)" -Sev 'Info'
87-
88-
}
8988

9089
}
9190
} catch {
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
function Get-CIPPURLName {
2+
<#
3+
.SYNOPSIS
4+
Gets the correct Microsoft Graph URL based on the OData type of a template
5+
.DESCRIPTION
6+
This function examines the @odata.type property of a JSON template object and returns
7+
the appropriate full Microsoft Graph API URL for that resource type.
8+
.PARAMETER Template
9+
The template object containing the @odata.type property to analyze
10+
.FUNCTIONALITY
11+
Internal
12+
.EXAMPLE
13+
Get-CIPPURLName -Template $MyTemplate
14+
.EXAMPLE
15+
$Template | Get-CIPPURLName
16+
#>
17+
[CmdletBinding()]
18+
param(
19+
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
20+
[PSCustomObject]$Template
21+
)
22+
23+
# Extract the OData type from the template
24+
$ODataType = $Template.'@odata.type'
25+
if ($Template.urlName) { return $Template.urlName }
26+
27+
if (-not $ODataType) {
28+
Write-Warning 'No @odata.type property found in template'
29+
return $null
30+
}
31+
32+
# Determine the full Microsoft Graph URL based on the OData type
33+
$URLName = switch -wildcard ($ODataType) {
34+
# Device Compliance Policies
35+
'*CompliancePolicy' {
36+
'deviceManagement/deviceCompliancePolicies'
37+
}
38+
'*deviceCompliancePolicy' {
39+
'deviceManagement/deviceCompliancePolicies'
40+
}
41+
42+
# Managed App Policies (App Protection)
43+
'*ManagedAppProtection' {
44+
'deviceAppManagement/managedAppPolicies'
45+
}
46+
'*managedAppPolicies' {
47+
'deviceAppManagement/managedAppPolicies'
48+
}
49+
'*managedAppPolicy' {
50+
'deviceAppManagement/managedAppPolicies'
51+
}
52+
'*appProtectionPolicy' {
53+
'deviceAppManagement/managedAppPolicies'
54+
}
55+
56+
# Configuration Policies (Settings Catalog)
57+
'*configurationPolicies' {
58+
'deviceManagement/configurationPolicies'
59+
}
60+
'*deviceManagementConfigurationPolicy' {
61+
'deviceManagement/configurationPolicies'
62+
}
63+
64+
# Windows Driver Update Profiles
65+
'*windowsDriverUpdateProfiles' {
66+
'deviceManagement/windowsDriverUpdateProfiles'
67+
}
68+
'*windowsDriverUpdateProfile' {
69+
'deviceManagement/windowsDriverUpdateProfiles'
70+
}
71+
72+
# Device Configurations
73+
'*deviceConfigurations' {
74+
'deviceManagement/deviceConfigurations'
75+
}
76+
'*deviceConfiguration' {
77+
'deviceManagement/deviceConfigurations'
78+
}
79+
80+
# Group Policy Configurations (Administrative Templates)
81+
'*groupPolicyConfigurations' {
82+
'deviceManagement/groupPolicyConfigurations'
83+
}
84+
'*groupPolicyConfiguration' {
85+
'deviceManagement/groupPolicyConfigurations'
86+
}
87+
88+
# Conditional Access Policies
89+
'*conditionalAccessPolicy' {
90+
'identity/conditionalAccess/policies'
91+
}
92+
93+
# Device Enrollment Configurations
94+
'*deviceEnrollmentConfiguration' {
95+
'deviceManagement/deviceEnrollmentConfigurations'
96+
}
97+
'*enrollmentConfiguration' {
98+
'deviceManagement/deviceEnrollmentConfigurations'
99+
}
100+
101+
# Mobile App Configurations
102+
'*mobileAppConfiguration' {
103+
'deviceAppManagement/mobileAppConfigurations'
104+
}
105+
'*appConfiguration' {
106+
'deviceAppManagement/mobileAppConfigurations'
107+
}
108+
109+
# Windows Feature Update Profiles
110+
'*windowsFeatureUpdateProfile' {
111+
'deviceManagement/windowsFeatureUpdateProfiles'
112+
}
113+
114+
# Device Health Scripts (Remediation Scripts)
115+
'*deviceHealthScript' {
116+
'deviceManagement/deviceHealthScripts'
117+
}
118+
119+
# Device Management Scripts (PowerShell Scripts)
120+
'*deviceManagementScript' {
121+
'deviceManagement/deviceManagementScripts'
122+
}
123+
124+
# Mobile Applications
125+
'*mobileApp' {
126+
'deviceAppManagement/mobileApps'
127+
}
128+
'*winGetApp' {
129+
'deviceAppManagement/mobileApps'
130+
}
131+
'*officeSuiteApp' {
132+
'deviceAppManagement/mobileApps'
133+
}
134+
135+
# Named Locations
136+
'*namedLocation' {
137+
'identity/conditionalAccess/namedLocations'
138+
}
139+
'*ipNamedLocation' {
140+
'identity/conditionalAccess/namedLocations'
141+
}
142+
'*countryNamedLocation' {
143+
'identity/conditionalAccess/namedLocations'
144+
}
145+
146+
# Default fallback
147+
default {
148+
Write-Warning "Unknown OData type: $ODataType"
149+
$null
150+
}
151+
}
152+
153+
return $URLName
154+
}
155+

Modules/CIPPCore/Public/Get-CIPPDrift.ps1

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,47 +84,47 @@ function Get-CIPPDrift {
8484
# Always get live data when not in AllTenants mode
8585
$IntuneRequests = @(
8686
@{
87-
id = 'deviceAppManagement'
87+
id = 'deviceAppManagement/managedAppPolicies'
8888
url = 'deviceAppManagement/managedAppPolicies'
8989
method = 'GET'
9090
}
9191
@{
92-
id = 'deviceCompliancePolicies'
92+
id = 'deviceManagement/deviceCompliancePolicies'
9393
url = 'deviceManagement/deviceCompliancePolicies'
9494
method = 'GET'
9595
}
9696
@{
97-
id = 'groupPolicyConfigurations'
97+
id = 'deviceManagement/groupPolicyConfigurations'
9898
url = 'deviceManagement/groupPolicyConfigurations'
9999
method = 'GET'
100100
}
101101
@{
102-
id = 'deviceConfigurations'
102+
id = 'deviceManagement/deviceConfigurations'
103103
url = 'deviceManagement/deviceConfigurations'
104104
method = 'GET'
105105
}
106106
@{
107-
id = 'configurationPolicies'
107+
id = 'deviceManagement/configurationPolicies'
108108
url = 'deviceManagement/configurationPolicies'
109109
method = 'GET'
110110
}
111111
@{
112-
id = 'windowsDriverUpdateProfiles'
112+
id = 'deviceManagement/windowsDriverUpdateProfiles'
113113
url = 'deviceManagement/windowsDriverUpdateProfiles'
114114
method = 'GET'
115115
}
116116
@{
117-
id = 'windowsFeatureUpdateProfiles'
117+
id = 'deviceManagement/windowsFeatureUpdateProfiles'
118118
url = 'deviceManagement/windowsFeatureUpdateProfiles'
119119
method = 'GET'
120120
}
121121
@{
122-
id = 'windowsQualityUpdatePolicies'
122+
id = 'deviceManagement/windowsQualityUpdatePolicies'
123123
url = 'deviceManagement/windowsQualityUpdatePolicies'
124124
method = 'GET'
125125
}
126126
@{
127-
id = 'windowsQualityUpdateProfiles'
127+
id = 'deviceManagement/windowsQualityUpdateProfiles'
128128
url = 'deviceManagement/windowsQualityUpdateProfiles'
129129
method = 'GET'
130130
}
@@ -220,6 +220,7 @@ function Get-CIPPDrift {
220220
# Check for extra Intune policies not in template
221221
foreach ($TenantPolicy in $TenantIntunePolicies) {
222222
$PolicyFound = $false
223+
$tenantPolicy.policy | Add-Member -MemberType NoteProperty -Name 'URLName' -Value $TenantPolicy.Type -Force
223224
$TenantPolicyName = if ($TenantPolicy.Policy.displayName) { $TenantPolicy.Policy.displayName } else { $TenantPolicy.Policy.name }
224225

225226
foreach ($TemplatePolicy in $TemplateIntuneTemplates) {

Modules/CIPPCore/Public/New-CIPPAlertTemplate.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function New-CIPPAlertTemplate {
3131
$Title = "CIPP Alert - Standard Drift Detected for $($Tenant)"
3232
$DataHTML = ($Data | ConvertTo-Html | Out-String).Replace('<table>', ' <table class="table-modern">')
3333
$IntroText = "<p>You've setup your instance to receive alerts when a tenant is drifting away from your standard. This seems to have happened! We've found the following deviations. </p>$dataHTML"
34-
$ButtonUrl = "$CIPPURL/cipp/logs"
34+
$ButtonUrl = "$CIPPURL/tenant/standards/manage-drift?tenantFilter=$($Tenant)&templateId=$($AuditLogLink)"
3535
$ButtonText = 'Investigate and remediate deviations'
3636
$AfterButtonText = 'Click the button above to go to the logbook and investigate the deviations. You can also use the standards page to remediate the deviations.'
3737
}

0 commit comments

Comments
 (0)