From 358966a628ea285f87b31c270d65fe63fd5a743d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 20 Jun 2025 19:01:31 +0200 Subject: [PATCH 001/125] Refactor BEC remediation process to use Set-CIPPMailboxRule for disabling inbox rules, improving error handling and logging for user actions. --- .../Users/Invoke-ExecBECRemediate.ps1 | 5 ++- .../Webhooks/Invoke-CIPPWebhookProcessing.ps1 | 34 ++++++++++++------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 index 51d8203a77c6..bbc1746c1b57 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 @@ -35,11 +35,10 @@ function Invoke-ExecBECRemediate { if (($Rules | Measure-Object).Count -gt 0) { $Rules | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object { try { - $null = New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } - "Disabled Inbox Rule '$($_.Identity)' for $Username" + Set-CIPPMailboxRule -Username $Username -TenantFilter $TenantFilter -RuleId $_.Identity -RuleName $_.Name -Disable -APIName $APIName -Headers $Headers $RuleDisabled++ } catch { - "Failed to disable Inbox Rule '$($_.Identity)' for $Username" + $_.Exception.Message $RuleFailed++ } } diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 index 42adae4b24de..24092e79827a 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 @@ -29,23 +29,31 @@ function Invoke-CippWebhookProcessing { Set-CIPPSignInState -TenantFilter $TenantFilter -User $data.UserId -AccountEnabled $false -APIName 'Alert Engine' -Headers 'Alert Engine' } 'becremediate' { - $username = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($data.UserId)" -tenantid $TenantFilter).UserPrincipalName - Set-CIPPResetPassword -UserID $username -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' - Set-CIPPSignInState -userid $username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' - Revoke-CIPPSessions -userid $username -username $username -Headers 'Alert Engine' -APIName 'Alert Engine' -tenantFilter $TenantFilter + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($data.UserId)" -tenantid $TenantFilter).UserPrincipalName + Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + Revoke-CIPPSessions -userid $Username -username $Username -Headers 'Alert Engine' -APIName 'Alert Engine' -tenantFilter $TenantFilter $RuleDisabled = 0 - New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $username; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object { - $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } - "Disabled Inbox Rule $($_.Identity) for $username" - $RuleDisabled++ + $RuleFailed = 0 + New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $Username; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object { + try { + Set-CIPPMailboxRule -Username $Username -TenantFilter $TenantFilter -RuleId $_.Identity -RuleName $_.Name -Disable -APIName 'Alert Engine' -Headers 'Alert Engine' + $RuleDisabled++ + } catch { + $_.Exception.Message + $RuleFailed++ + } } - if ($RuleDisabled) { - "Disabled $RuleDisabled Inbox Rules for $username" + if ($RuleDisabled -gt 0) { + "Disabled $RuleDisabled Inbox Rules for $Username" } else { - "No Inbox Rules found for $username. We have not disabled any rules." + "No Inbox Rules found for $Username. We have not disabled any rules." } - "Completed BEC Remediate for $username" - Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $username" -sev 'Info' + if ($RuleFailed -gt 0) { + "Failed to disable $RuleFailed Inbox Rules for $Username" + } + "Completed BEC Remediate for $Username" + Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username" -sev 'Info' } 'cippcommand' { $CommandSplat = @{} From 1c4e9f44e98a5500bb02435e182340363eec74b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 18 Jun 2025 21:03:34 +0200 Subject: [PATCH 002/125] Feat: Add add Room list function --- .../Resources/Invoke-AddRoomList.ps1 | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 new file mode 100644 index 000000000000..057cc0b4a1c0 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 @@ -0,0 +1,66 @@ +using namespace System.Net + +Function Invoke-AddRoomList { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.Room.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + $Tenant = $Request.Body.tenantFilter ?? $Request.Body.tenantid + + $Results = [System.Collections.Generic.List[Object]]::new() + $RoomListObject = $Request.Body + + # Construct email address from username and domain + $EmailAddress = if ($RoomListObject.primDomain.value) { + "$($RoomListObject.username)@$($RoomListObject.primDomain.value)" + } else { + "$($RoomListObject.username)@$($Tenant)" + } + + # Parameters for New-DistributionGroup with RoomList + $AddRoomListParams = @{ + Name = $RoomListObject.username + DisplayName = $RoomListObject.displayName + RoomList = $true + PrimarySMTPAddress = $EmailAddress + } + + # Add optional parameters if they exist + if (![string]::IsNullOrWhiteSpace($RoomListObject.description)) { + $AddRoomListParams.Notes = $RoomListObject.description + } + + if (![string]::IsNullOrWhiteSpace($RoomListObject.alias)) { + $AddRoomListParams.Alias = $RoomListObject.alias + } + + try { + $AddRoomListRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'New-DistributionGroup' -cmdParams $AddRoomListParams + $Results.Add("Successfully created room list: $($RoomListObject.displayName).") + Write-LogMessage -Headers $Headers -API $APINAME -tenant $Tenant -message "Created room list $($RoomListObject.displayName) with id $($AddRoomListRequest.identity)" -Sev 'Info' + + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Message = "Failed to create room list: $($RoomListObject.displayName). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -Sev 'Error' -LogData $ErrorMessage + $Results.Add($Message) + $StatusCode = [HttpStatusCode]::InternalServerError + } + + $Body = [pscustomobject] @{ 'Results' = @($Results) } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $Body + }) +} From 36ec6fc6a0a82872688d286a47e4a75bb35a4fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 18 Jun 2025 21:48:48 +0200 Subject: [PATCH 003/125] Refactor Invoke-ListRoomLists to use EXO commands for immediate room list retrieval and update error handling for better clarity. Adjusted tenant filter variable casing in Invoke-ListGroups for consistency. Remove optional parameters from Invoke-AddRoomList function for streamlined execution. Enhance Remove-CIPPGroup function to support Room Lists and update Invoke-ListRoomLists to include calculatedGroupType property for better group identification. Remove Room List GroupType why the fuck wont this stupid thing just work wtf am i doing wrong remove comment Change to results as api return Update Invoke-ListRoomLists and Invoke-EditGroup to include 'Room List' in group type checks and properties for improved functionality and consistency. only groupinfo is broken now Revert standalone edit page revert Enhance owner management in Invoke-EditRoomList and Invoke-ListRoomLists functions by explicitly converting ManagedBy objects to strings and validating GUID formats. Invalid GUIDs are now handled with placeholder objects for better error management. --- .../Resources/Invoke-AddRoomList.ps1 | 9 - .../Resources/Invoke-EditRoomList.ps1 | 177 ++++++++++++++++++ .../Resources/Invoke-ListRoomLists.ps1 | 98 +++++++++- .../Groups/Invoke-EditGroup.ps1 | 2 +- .../Groups/Invoke-ListGroups.ps1 | 2 +- 5 files changed, 267 insertions(+), 21 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomList.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 index 057cc0b4a1c0..71135e9f31bb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomList.ps1 @@ -34,15 +34,6 @@ Function Invoke-AddRoomList { PrimarySMTPAddress = $EmailAddress } - # Add optional parameters if they exist - if (![string]::IsNullOrWhiteSpace($RoomListObject.description)) { - $AddRoomListParams.Notes = $RoomListObject.description - } - - if (![string]::IsNullOrWhiteSpace($RoomListObject.alias)) { - $AddRoomListParams.Alias = $RoomListObject.alias - } - try { $AddRoomListRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'New-DistributionGroup' -cmdParams $AddRoomListParams $Results.Add("Successfully created room list: $($RoomListObject.displayName).") diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomList.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomList.ps1 new file mode 100644 index 000000000000..dfe1a1393259 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-EditRoomList.ps1 @@ -0,0 +1,177 @@ +using namespace System.Net + +Function Invoke-EditRoomList { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.Room.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + $Results = [System.Collections.Generic.List[string]]::new() + $RoomListObj = $Request.Body + $GroupId = $RoomListObj.groupId + $TenantId = $RoomListObj.tenantFilter + + try { + # Edit basic room list properties + if ($RoomListObj.displayName -or $RoomListObj.description -or $RoomListObj.mailNickname) { + $SetRoomListParams = @{ + Identity = $GroupId + } + + if ($RoomListObj.displayName) { + $SetRoomListParams.DisplayName = $RoomListObj.displayName + } + + if ($RoomListObj.description) { + $SetRoomListParams.Description = $RoomListObj.description + } + + if ($RoomListObj.mailNickname) { + $SetRoomListParams.Name = $RoomListObj.mailNickname + } + + try { + $null = New-ExoRequest -tenantid $TenantId -cmdlet 'Set-DistributionGroup' -cmdParams $SetRoomListParams -useSystemMailbox $true + $Results.Add("Successfully updated room list properties for $($RoomListObj.displayName)") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Updated room list properties for $($RoomListObj.displayName)" -Sev 'Info' + } catch { + $Results.Add("Failed to update room list properties: $($_.Exception.Message)") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Failed to update room list properties: $($_.Exception.Message)" -Sev 'Error' + } + } + + # Add room members + if ($RoomListObj.AddMember) { + foreach ($Member in $RoomListObj.AddMember) { + try { + $MemberEmail = if ($Member.value) { $Member.value } else { $Member } + $AddMemberParams = @{ + Identity = $GroupId + Member = $MemberEmail + BypassSecurityGroupManagerCheck = $true + } + + $null = New-ExoRequest -tenantid $TenantId -cmdlet 'Add-DistributionGroupMember' -cmdParams $AddMemberParams -useSystemMailbox $true + $Results.Add("Successfully added room $MemberEmail to room list") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Added room $MemberEmail to room list $GroupId" -Sev 'Info' + } catch { + $Results.Add("Failed to add room $MemberEmail : $($_.Exception.Message)") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Failed to add room $MemberEmail : $($_.Exception.Message)" -Sev 'Error' + } + } + } + + # Remove room members + if ($RoomListObj.RemoveMember) { + foreach ($Member in $RoomListObj.RemoveMember) { + try { + $MemberEmail = if ($Member.value) { $Member.value } else { $Member } + $RemoveMemberParams = @{ + Identity = $GroupId + Member = $MemberEmail + BypassSecurityGroupManagerCheck = $true + } + + $null = New-ExoRequest -tenantid $TenantId -cmdlet 'Remove-DistributionGroupMember' -cmdParams $RemoveMemberParams -useSystemMailbox $true + $Results.Add("Successfully removed room $MemberEmail from room list") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Removed room $MemberEmail from room list $GroupId" -Sev 'Info' + } catch { + $Results.Add("Failed to remove room $MemberEmail from room list: $($_.Exception.Message)") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Failed to remove room $MemberEmail from room list: $($_.Exception.Message)" -Sev 'Error' + } + } + } + + # Handle owners (ManagedBy property) + if ($RoomListObj.AddOwner -or $RoomListObj.RemoveOwner) { + try { + # Get current owners + $CurrentGroup = New-ExoRequest -tenantid $TenantId -cmdlet 'Get-DistributionGroup' -cmdParams @{ Identity = $GroupId } -useSystemMailbox $true + $CurrentOwners = [System.Collections.Generic.List[string]]::new() + + if ($CurrentGroup.ManagedBy) { + # Convert ManagedBy objects to strings explicitly + foreach ($ManagedByItem in $CurrentGroup.ManagedBy) { + $StringValue = [string]$ManagedByItem + $CurrentOwners.Add($StringValue) + } + } + + # Remove owners + if ($RoomListObj.RemoveOwner) { + foreach ($Owner in $RoomListObj.RemoveOwner) { + $OwnerToRemove = if ($Owner.addedFields.id) { $Owner.addedFields.id } else { $Owner.value } + if ($CurrentOwners -contains $OwnerToRemove) { + $CurrentOwners.Remove($OwnerToRemove) + $Results.Add("Removed owner $(if ($Owner.label) { $Owner.label } else { $OwnerToRemove }) from room list") + } + } + } + + # Add owners + if ($RoomListObj.AddOwner) { + foreach ($Owner in $RoomListObj.AddOwner) { + $OwnerToAdd = if ($Owner.addedFields.id) { $Owner.addedFields.id } else { $Owner.value } + if ($CurrentOwners -notcontains $OwnerToAdd) { + $CurrentOwners.Add($OwnerToAdd) + $Results.Add("Added owner $(if ($Owner.label) { $Owner.label } else { $OwnerToAdd }) to room list") + } + } + } + + # Update ManagedBy with new owners list + $SetOwnersParams = @{ + Identity = $GroupId + ManagedBy = $CurrentOwners.ToArray() + } + + $null = New-ExoRequest -tenantid $TenantId -cmdlet 'Set-DistributionGroup' -cmdParams $SetOwnersParams -useSystemMailbox $true + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Updated owners for room list $GroupId" -Sev 'Info' + } catch { + $Results.Add("Failed to update room list owners: $($_.Exception.Message)") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Failed to update room list owners: $($_.Exception.Message)" -Sev 'Error' + } + } + + # Handle external email settings + if ($null -ne $RoomListObj.allowExternal) { + try { + $SetExternalParams = @{ + Identity = $GroupId + RequireSenderAuthenticationEnabled = !$RoomListObj.allowExternal + } + + $null = New-ExoRequest -tenantid $TenantId -cmdlet 'Set-DistributionGroup' -cmdParams $SetExternalParams -useSystemMailbox $true + + if ($RoomListObj.allowExternal) { + $Results.Add('Enabled external email access for room list') + } else { + $Results.Add('Disabled external email access for room list') + } + + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Updated external email settings for room list $GroupId" -Sev 'Info' + } catch { + $Results.Add("Failed to update external email settings: $($_.Exception.Message)") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Failed to update external email settings: $($_.Exception.Message)" -Sev 'Error' + } + } + + } catch { + $Results.Add("An error occurred while editing the room list: $($_.Exception.Message)") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Failed to edit room list: $($_.Exception.Message)" -Sev 'Error' + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @{'Results' = @($Results) } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 index be10ef655ae7..a48f28b25152 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 @@ -16,25 +16,103 @@ Function Invoke-ListRoomLists { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.tenantFilter + $GroupID = $Request.Query.groupID + $Members = $Request.Query.members + $Owners = $Request.Query.owners try { - $params = @{ - uri = 'https://graph.microsoft.com/beta/places/microsoft.graph.roomlist' - tenantid = $TenantFilter - AsApp = $true - } - $GraphRequest = New-GraphGetRequest @params + if ($GroupID) { + # Get specific room list with detailed information + $GroupInfo = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams @{Identity = $GroupID } -useSystemMailbox $true | + Select-Object -ExcludeProperty *data.type* + + $Result = [PSCustomObject]@{ + groupInfo = $GroupInfo | Select-Object *, @{ Name = 'primDomain'; Expression = { $_.PrimarySmtpAddress -split '@' | Select-Object -Last 1 } } + members = @{} + owners = @{} + allowExternal = (!$GroupInfo.RequireSenderAuthenticationEnabled) + } + + # Get members if requested + if ($Members -eq 'true') { + $RoomListMembers = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroupMember' -cmdParams @{Identity = $GroupID } | Select-Object -ExcludeProperty *data.type* -Property @{Name = 'id'; Expression = { $_.ExternalDirectoryObjectId } }, + @{Name = 'displayName'; Expression = { $_.DisplayName } }, + @{Name = 'mail'; Expression = { $_.PrimarySmtpAddress } }, + @{Name = 'mailNickname'; Expression = { $_.Alias } }, + @{Name = 'userPrincipalName'; Expression = { $_.PrimarySmtpAddress } } + $Result.members = @($RoomListMembers) + } + + # Get owners if requested + if ($Owners -eq 'true' -and $GroupInfo.ManagedBy) { + try { + # Separate valid and invalid GUIDs + $ValidOwnerIds = [System.Collections.Generic.List[string]]::new() + $InvalidOwnerIds = [System.Collections.Generic.List[string]]::new() + + foreach ($OwnerId in $GroupInfo.ManagedBy) { + $OwnerIdString = [string]$OwnerId + # Check if it's a valid GUID format + if ($OwnerIdString -match '^[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}$') { + $ValidOwnerIds.Add($OwnerIdString) + } else { + $InvalidOwnerIds.Add($OwnerIdString) + Write-Warning "Found invalid GUID for owner: $OwnerIdString" + } + } + + $AllOwners = [System.Collections.Generic.List[PSCustomObject]]::new() + + # Get valid owners from Graph API + if ($ValidOwnerIds.Count -gt 0) { + $body = ConvertTo-Json -InputObject @{ids = @($ValidOwnerIds) } -Compress + $OwnersData = New-GraphPOSTRequest -tenantid $TenantFilter -uri 'https://graph.microsoft.com/beta/directoryObjects/getByIds' -body $body + foreach ($Owner in $OwnersData.value) { + $AllOwners.Add($Owner) + } + } - $StatusCode = [HttpStatusCode]::OK + # Add invalid GUIDs as placeholder objects so they can be removed + foreach ($InvalidId in $InvalidOwnerIds) { + $PlaceholderOwner = [PSCustomObject]@{ + id = $InvalidId + displayName = "Invalid Owner ID: $InvalidId" + userPrincipalName = "invalid-$InvalidId" + '@odata.type' = '#microsoft.graph.user' + } + $AllOwners.Add($PlaceholderOwner) + } + + $Result.owners = @($AllOwners) + + } catch { + Write-Warning "Failed to get owners: $($_.Exception.Message)" + $Result.owners = @() + } + } + + + + $StatusCode = [HttpStatusCode]::OK + $ResponseBody = $Result + } else { + # Get all room lists (original functionality) + $RoomLists = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams @{RecipientTypeDetails = 'RoomList'; ResultSize = 'Unlimited' } | + Select-Object Guid, DisplayName, PrimarySmtpAddress, Alias, Phone, Identity, Notes, Description, Id -ExcludeProperty *data.type* + $RoomLists | Add-Member -MemberType NoteProperty -Name groupType -Value 'Room List' + $StatusCode = [HttpStatusCode]::OK + $ResponseBody = @{ Results = @($RoomLists | Sort-Object DisplayName) } + } } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - $StatusCode = [HttpStatusCode]::Forbidden - $GraphRequest = $ErrorMessage + $StatusCode = [HttpStatusCode]::InternalServerError + $ResponseBody = $ErrorMessage } + # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = @($GraphRequest | Sort-Object displayName) + Body = $ResponseBody }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 index 86f1abdd4922..f2b215d0d794 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 @@ -387,4 +387,4 @@ function Invoke-EditGroup { Body = $Body }) -} +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 index 6b5117824ec7..7cff4b78809b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 @@ -129,4 +129,4 @@ function Invoke-ListGroups { Body = $GraphRequest }) -} +} \ No newline at end of file From 86ea72f941599d047dc6fb78b00e84347f56e4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 23 Jun 2025 23:42:26 +0200 Subject: [PATCH 004/125] Remove redundant groupType property assignment in Invoke-ListRoomLists for cleaner code and improved functionality. --- .../Email-Exchange/Resources/Invoke-ListRoomLists.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 index a48f28b25152..fc17e325ad3f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-ListRoomLists.ps1 @@ -99,7 +99,6 @@ Function Invoke-ListRoomLists { # Get all room lists (original functionality) $RoomLists = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams @{RecipientTypeDetails = 'RoomList'; ResultSize = 'Unlimited' } | Select-Object Guid, DisplayName, PrimarySmtpAddress, Alias, Phone, Identity, Notes, Description, Id -ExcludeProperty *data.type* - $RoomLists | Add-Member -MemberType NoteProperty -Name groupType -Value 'Room List' $StatusCode = [HttpStatusCode]::OK $ResponseBody = @{ Results = @($RoomLists | Sort-Object DisplayName) } } From 8c655d1ed206881e16fb5feeeef487dd56e871f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 21:59:43 +0200 Subject: [PATCH 005/125] Set-CIPPMailboxType throw support --- .../Administration/Invoke-ExecConvertMailbox.ps1 | 14 ++++++-------- .../Users/Invoke-CIPPOffboardingJob.ps1 | 14 +++++++++----- Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 | 6 +++--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecConvertMailbox.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecConvertMailbox.ps1 index b8bd40c0915a..40c35048cb4a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecConvertMailbox.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecConvertMailbox.ps1 @@ -11,27 +11,25 @@ Function Invoke-ExecConvertMailbox { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint - $TenantFilter = $Request.Body.tenantFilter - Write-LogMessage -Headers $Request.Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + $Headers = $Request.Headers + Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' # Interact with query parameters or the body of the request. + $TenantFilter = $Request.Body.tenantFilter $UserID = $Request.Body.ID $MailboxType = $Request.Body.MailboxType try { - $ConvertedMailbox = Set-CIPPMailboxType -UserID $UserID -TenantFilter $TenantFilter -APIName $APIName -Headers $Request.Headers -MailboxType $MailboxType - if ($ConvertedMailbox -like 'Could not convert*') { throw $ConvertedMailbox } - $Results = [pscustomobject]@{'Results' = "$ConvertedMailbox" } + $Results = Set-CIPPMailboxType -UserID $UserID -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -MailboxType $MailboxType $StatusCode = [HttpStatusCode]::OK } catch { - $ErrorMessage = $_.Exception.Message - $Results = [pscustomobject]@{'Results' = "$ErrorMessage" } + $Results = $_.Exception.Message $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = @{'Results' = $Results } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index d0ad323f12f7..4aeccdbeb008 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -11,20 +11,24 @@ function Invoke-CIPPOffboardingJob { if ($Options -is [string]) { $Options = $Options | ConvertFrom-Json } - $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($username)?`$select=id" -tenantid $TenantFilter).id - Write-Host "Running offboarding job for $username with options: $($Options | ConvertTo-Json -Depth 10)" + $UserID = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Username)?`$select=id" -tenantid $TenantFilter).id + Write-Host "Running offboarding job for $Username with options: $($Options | ConvertTo-Json -Depth 10)" $Return = switch ($Options) { { $_.ConvertToShared -eq $true } { - Set-CIPPMailboxType -Headers $Headers -tenantFilter $TenantFilter -userid $username -username $username -MailboxType 'Shared' -APIName $APIName + try { + Set-CIPPMailboxType -Headers $Headers -tenantFilter $TenantFilter -userid $UserID -username $Username -MailboxType 'Shared' -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.RevokeSessions -eq $true } { - Revoke-CIPPSessions -tenantFilter $TenantFilter -username $username -userid $userid -Headers $Headers -APIName $APIName + Revoke-CIPPSessions -tenantFilter $TenantFilter -username $Username -userid $UserID -Headers $Headers -APIName $APIName } { $_.ResetPass -eq $true } { Set-CIPPResetPassword -tenantFilter $TenantFilter -UserID $username -Headers $Headers -APIName $APIName } { $_.RemoveGroups -eq $true } { - Remove-CIPPGroups -userid $userid -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Username "$Username" + Remove-CIPPGroups -userid $UserID -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Username "$Username" } { $_.HideFromGAL -eq $true } { Set-CIPPHideFromGAL -tenantFilter $TenantFilter -UserID $username -HideFromGAL $true -Headers $Headers -APIName $APIName diff --git a/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 b/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 index 60f9a6fb4fbf..ed8fdd7060e8 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMailboxType.ps1 @@ -13,13 +13,13 @@ function Set-CIPPMailboxType { try { if ([string]::IsNullOrWhiteSpace($Username)) { $Username = $UserID } $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $UserID; Type = $MailboxType } -Anchor $Username - $Message = "Converted $Username to a $MailboxType mailbox" + $Message = "Successfully converted $Username to a $MailboxType mailbox" Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Info' -tenant $TenantFilter return $Message } catch { $ErrorMessage = Get-CippException -Exception $_ - $Message = "Could not convert $Username to a $MailboxType mailbox. Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to convert $Username to a $MailboxType mailbox. Error: $($ErrorMessage.NormalizedError)" Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return $Message + throw $Message } } From 7d6d9afaf2414fa5c39538a368308420d09f6abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 22:06:13 +0200 Subject: [PATCH 006/125] Support throw for Revoke-CIPPSessions --- .../Users/Invoke-CIPPOffboardingJob.ps1 | 6 +++- .../Users/Invoke-ExecRevokeSessions.ps1 | 3 +- .../CIPPCore/Public/Revoke-CIPPSessions.ps1 | 12 ++++---- .../Webhooks/Invoke-CIPPWebhookProcessing.ps1 | 30 +++++++++++-------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 4aeccdbeb008..54169d16052a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -22,7 +22,11 @@ function Invoke-CIPPOffboardingJob { } } { $_.RevokeSessions -eq $true } { - Revoke-CIPPSessions -tenantFilter $TenantFilter -username $Username -userid $UserID -Headers $Headers -APIName $APIName + try { + Revoke-CIPPSessions -tenantFilter $TenantFilter -username $Username -userid $UserID -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.ResetPass -eq $true } { Set-CIPPResetPassword -tenantFilter $TenantFilter -UserID $username -Headers $Headers -APIName $APIName diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 index 4d39d67fbbec..54af83616810 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecRevokeSessions.ps1 @@ -20,8 +20,7 @@ Function Invoke-ExecRevokeSessions { $Username = $Request.Query.Username ?? $Request.Body.Username try { - $Result = Revoke-CIPPSessions -UserID $ID -TenantFilter $TenantFilter -Username $Username -APIName $APIName -Headers $Request.Headers - if ($Result -match '^Failed') { throw $Result } + $Result = Revoke-CIPPSessions -UserID $ID -TenantFilter $TenantFilter -Username $Username -APIName $APIName -Headers $Headers $StatusCode = [HttpStatusCode]::OK } catch { $Result = $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 b/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 index e9d319008a2d..2718233be838 100644 --- a/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 +++ b/Modules/CIPPCore/Public/Revoke-CIPPSessions.ps1 @@ -9,15 +9,15 @@ function Revoke-CIPPSessions { ) try { - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)/invalidateAllRefreshTokens" -tenantid $TenantFilter -type POST -body '{}' -verbose - Write-LogMessage -headers $Headers -API $APIName -message "Revoked sessions for $($username)" -Sev 'Info' -tenant $TenantFilter - return "Success. All sessions by $username have been revoked" + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserID)/invalidateAllRefreshTokens" -tenantid $TenantFilter -type POST -body '{}' -verbose + $Result = "Successfully revoked sessions for $($Username)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' -tenant $TenantFilter + return $Result } catch { $ErrorMessage = Get-CippException -Exception $_ - $Result = "Failed to revoke sessions for $($username). Error: $($ErrorMessage.NormalizedError)" + $Result = "Failed to revoke sessions for $($Username). Error: $($ErrorMessage.NormalizedError)" Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - # TODO - needs to be changed to throw, but the rest of the functions using this cant handle anything but a return. - return $Result + throw $Result } } diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 index 42adae4b24de..b5d52f0f268e 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 @@ -19,20 +19,24 @@ function Invoke-CippWebhookProcessing { } $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $TenantFilter } - Write-Host "Received data. Our Action List is $($data.CIPPAction)" + Write-Host "Received data. Our Action List is $($Data.CIPPAction)" - $ActionList = ($data.CIPPAction | ConvertFrom-Json -ErrorAction SilentlyContinue).value + $ActionList = ($Data.CIPPAction | ConvertFrom-Json -ErrorAction SilentlyContinue).value $ActionResults = foreach ($action in $ActionList) { Write-Host "this is our action: $($action | ConvertTo-Json -Depth 15 -Compress)" switch ($action) { 'disableUser' { - Set-CIPPSignInState -TenantFilter $TenantFilter -User $data.UserId -AccountEnabled $false -APIName 'Alert Engine' -Headers 'Alert Engine' + Set-CIPPSignInState -TenantFilter $TenantFilter -User $Data.UserId -AccountEnabled $false -APIName 'Alert Engine' -Headers 'Alert Engine' } 'becremediate' { - $username = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($data.UserId)" -tenantid $TenantFilter).UserPrincipalName - Set-CIPPResetPassword -UserID $username -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' - Set-CIPPSignInState -userid $username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' - Revoke-CIPPSessions -userid $username -username $username -Headers 'Alert Engine' -APIName 'Alert Engine' -tenantFilter $TenantFilter + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Data.UserId)" -tenantid $TenantFilter).UserPrincipalName + Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + try { + Revoke-CIPPSessions -userid $Username -username $Username -Headers 'Alert Engine' -APIName 'Alert Engine' -tenantFilter $TenantFilter + } catch { + Write-Host "Failed to revoke sessions for $Username`: $($_.Exception.Message)" + } $RuleDisabled = 0 New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $username; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object { $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } @@ -50,11 +54,11 @@ function Invoke-CippWebhookProcessing { 'cippcommand' { $CommandSplat = @{} $action.parameters.psobject.properties | ForEach-Object { $CommandSplat.Add($_.name, $_.value) } - if ($CommandSplat['userid']) { $CommandSplat['userid'] = $data.userid } - if ($CommandSplat['tenantfilter']) { $CommandSplat['tenantfilter'] = $tenantfilter } - if ($CommandSplat['tenant']) { $CommandSplat['tenant'] = $tenantfilter } - if ($CommandSplat['user']) { $CommandSplat['user'] = $data.userid } - if ($CommandSplat['username']) { $CommandSplat['username'] = $data.userid } + if ($CommandSplat['userid']) { $CommandSplat['userid'] = $Data.UserId } + if ($CommandSplat['tenantfilter']) { $CommandSplat['tenantfilter'] = $TenantFilter } + if ($CommandSplat['tenant']) { $CommandSplat['tenant'] = $TenantFilter } + if ($CommandSplat['user']) { $CommandSplat['user'] = $Data.UserId } + if ($CommandSplat['username']) { $CommandSplat['username'] = $Data.UserId } & $action.command.value @CommandSplat } } @@ -69,7 +73,7 @@ function Invoke-CippWebhookProcessing { ActionUrl = $GenerateJSON.ButtonUrl ActionText = $GenerateJSON.ButtonText RawData = $Data - IP = $data.ClientIP + IP = $Data.ClientIP PotentialLocationInfo = $LocationInfo ActionsTaken = $ActionResults AuditRecord = $AuditRecord From 140c595203f8d6667e908d9e772f4831738ec1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 22:10:17 +0200 Subject: [PATCH 007/125] Support throw for Set-CIPPResetPassword --- .../Users/Invoke-CIPPOffboardingJob.ps1 | 8 ++++++-- .../Users/Invoke-ExecResetPass.ps1 | 1 - .../CIPPCore/Public/Set-CIPPResetPassword.ps1 | 14 ++++++------- .../Webhooks/Invoke-CIPPWebhookProcessing.ps1 | 20 +++++++++++-------- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 54169d16052a..26ae6b51cfe7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -29,10 +29,14 @@ function Invoke-CIPPOffboardingJob { } } { $_.ResetPass -eq $true } { - Set-CIPPResetPassword -tenantFilter $TenantFilter -UserID $username -Headers $Headers -APIName $APIName + try { + Set-CIPPResetPassword -tenantFilter $TenantFilter -UserID $username -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.RemoveGroups -eq $true } { - Remove-CIPPGroups -userid $UserID -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Username "$Username" + Remove-CIPPGroups -userid $UserID -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Username $Username } { $_.HideFromGAL -eq $true } { Set-CIPPHideFromGAL -tenantFilter $TenantFilter -UserID $username -HideFromGAL $true -Headers $Headers -APIName $APIName diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 index d2080b726ab8..879a7b65b432 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetPass.ps1 @@ -24,7 +24,6 @@ Function Invoke-ExecResetPass { try { $Result = Set-CIPPResetPassword -UserID $ID -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers -forceChangePasswordNextSignIn $MustChange -DisplayName $DisplayName - if ($Result.state -eq 'Error') { throw $Result.resultText } $StatusCode = [HttpStatusCode]::OK } catch { $Result = $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 index 69b780169c69..b206a7f12834 100644 --- a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 @@ -26,27 +26,25 @@ function Set-CIPPResetPassword { if ($PasswordLink) { $password = $PasswordLink } - Write-LogMessage -headers $Headers -API $APIName -message "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn" -Sev 'Info' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message "Successfully reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn" -Sev 'Info' -tenant $TenantFilter if ($UserDetails.onPremisesSyncEnabled -eq $true) { return [pscustomobject]@{ - resultText = "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password. WARNING: This user is AD synced. Please confirm passthrough or writeback is enabled." + resultText = "Successfully reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password. WARNING: This user is AD synced. Please confirm passthrough or writeback is enabled." copyField = $password state = 'warning' } } else { return [pscustomobject]@{ - resultText = "Reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password" + resultText = "Successfully reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn. The new password is $password" copyField = $password state = 'success' } } } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not reset password for $DisplayName, $($UserID). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return [pscustomobject]@{ - resultText = "Could not reset password for $DisplayName, $($UserID). Error: $($ErrorMessage.NormalizedError)" - state = 'Error' - } + $Message = "Failed to reset password for $DisplayName, $($UserID). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } diff --git a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 index b5d52f0f268e..1d70ab6aba98 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 @@ -30,7 +30,11 @@ function Invoke-CippWebhookProcessing { } 'becremediate' { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Data.UserId)" -tenantid $TenantFilter).UserPrincipalName - Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + try { + Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + } catch { + Write-Host "Failed to reset password for $Username`: $($_.Exception.Message)" + } Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' try { Revoke-CIPPSessions -userid $Username -username $Username -Headers 'Alert Engine' -APIName 'Alert Engine' -tenantFilter $TenantFilter @@ -38,18 +42,18 @@ function Invoke-CippWebhookProcessing { Write-Host "Failed to revoke sessions for $Username`: $($_.Exception.Message)" } $RuleDisabled = 0 - New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $username; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object { - $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } - "Disabled Inbox Rule $($_.Identity) for $username" + New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $Username; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object { + $null = New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } + "Disabled Inbox Rule $($_.Identity) for $Username" $RuleDisabled++ } if ($RuleDisabled) { - "Disabled $RuleDisabled Inbox Rules for $username" + "Disabled $RuleDisabled Inbox Rules for $Username" } else { - "No Inbox Rules found for $username. We have not disabled any rules." + "No Inbox Rules found for $Username. We have not disabled any rules." } - "Completed BEC Remediate for $username" - Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $username" -sev 'Info' + "Completed BEC Remediate for $Username" + Write-LogMessage -API 'BECRemediate' -tenant $tenantfilter -message "Executed Remediation for $Username" -sev 'Info' } 'cippcommand' { $CommandSplat = @{} From 1f99858b77177472f383af8338ad0b2b845e3fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 22:13:37 +0200 Subject: [PATCH 008/125] Support throw in Set-CIPPHideFromGAL and fix message --- .../Administration/Users/Invoke-CIPPOffboardingJob.ps1 | 6 +++++- Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 26ae6b51cfe7..94871765ddf8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -39,7 +39,11 @@ function Invoke-CIPPOffboardingJob { Remove-CIPPGroups -userid $UserID -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Username $Username } { $_.HideFromGAL -eq $true } { - Set-CIPPHideFromGAL -tenantFilter $TenantFilter -UserID $username -HideFromGAL $true -Headers $Headers -APIName $APIName + try { + Set-CIPPHideFromGAL -tenantFilter $TenantFilter -UserID $username -HideFromGAL $true -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.DisableSignIn -eq $true } { Set-CIPPSignInState -TenantFilter $TenantFilter -userid $username -AccountEnabled $false -Headers $Headers -APIName $APIName diff --git a/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 b/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 index 23d8bc8e8462..0c87d039ae71 100644 --- a/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPHideFromGAL.ps1 @@ -15,8 +15,8 @@ function Set-CIPPHideFromGAL { return $Result } catch { $ErrorMessage = Get-CippException -Exception $_ - $Result = "Failed to hide $($UserId) from GAL. Error: $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return $Result + $Message = "Failed to set $($UserId) to $Text in GAL. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } From 01cc78cffebdbec8bdd68150992081c6ff40199c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 22:39:17 +0200 Subject: [PATCH 009/125] Support throw for Set-CIPPSignInState --- .../Administration/Invoke-AddSharedMailbox.ps1 | 17 ++++++++--------- .../Resources/Invoke-AddEquipmentMailbox.ps1 | 9 ++++----- .../Resources/Invoke-AddRoomMailbox.ps1 | 9 ++++----- .../Users/Invoke-CIPPOffboardingJob.ps1 | 6 +++++- .../Users/Invoke-ExecDisableUser.ps1 | 1 - Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 | 11 ++++++----- .../Webhooks/Invoke-CIPPWebhookProcessing.ps1 | 12 ++++++++++-- 7 files changed, 37 insertions(+), 28 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-AddSharedMailbox.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-AddSharedMailbox.ps1 index 797025084402..3b359bf13d4e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-AddSharedMailbox.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-AddSharedMailbox.ps1 @@ -14,7 +14,7 @@ Function Invoke-AddSharedMailbox { $Headers = $Request.Headers Write-LogMessage -Headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - $Results = [System.Collections.ArrayList]@() + $Results = [System.Collections.Generic.List[string]]::new() $MailboxObject = $Request.Body $Tenant = $MailboxObject.tenantID $Aliases = $MailboxObject.addedAliases -Split '\n' @@ -29,18 +29,18 @@ Function Invoke-AddSharedMailbox { Shared = $true } $AddSharedRequest = New-ExoRequest -tenantid $Tenant -cmdlet 'New-Mailbox' -cmdParams $BodyToShip - $Body = $Results.Add("Successfully created shared mailbox: $Email.") + $Results.Add("Successfully created shared mailbox: $Email.") Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message "Created shared mailbox $($MailboxObject.displayName) with email $Email" -Sev 'Info' # Block sign-in for the mailbox try { $null = Set-CIPPSignInState -userid $AddSharedRequest.ExternalDirectoryObjectId -TenantFilter $Tenant -APIName $APIName -Headers $Headers -AccountEnabled $false - $Body = $Results.Add("Blocked sign-in for shared mailbox $Email") + $Results.Add("Blocked sign-in for shared mailbox $Email") } catch { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to block sign-in for shared mailbox $Email. Error: $($ErrorMessage.NormalizedError)" Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -Sev 'Error' -LogData $ErrorMessage - $Body = $Results.Add($Message) + $Results.Add($Message) } # Add aliases to the mailbox if any are provided @@ -54,13 +54,13 @@ Function Invoke-AddSharedMailbox { $null = New-ExoRequest -tenantid $Tenant -cmdlet 'Set-Mailbox' -cmdParams $AliasBodyToShip -UseSystemMailbox $true $Message = "Added aliases to $Email : $($Aliases -join ',')" Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -Sev 'Info' - $Body = $Results.Add($Message) + $Results.Add($Message) } catch { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to add aliases to $Email : $($ErrorMessage.NormalizedError)" Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -Sev 'Error' -LogData $ErrorMessage - $Body = $Results.Add($Message) + $Results.Add($Message) } } $StatusCode = [HttpStatusCode]::OK @@ -68,16 +68,15 @@ Function Invoke-AddSharedMailbox { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to create shared mailbox. $($ErrorMessage.NormalizedError)" Write-LogMessage -Headers $Headers -API $APIName -tenant $Tenant -message $Message -Sev 'Error' -LogData $ErrorMessage - $Body = $Results.Add($Message) + $Results.Add($Message) $StatusCode = [HttpStatusCode]::Forbidden } - $Body = [pscustomobject] @{ Results = @($Results) } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Body + Body = @{ Results = @($Results) } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddEquipmentMailbox.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddEquipmentMailbox.ps1 index 93497681a93a..1bff3ee58b80 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddEquipmentMailbox.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddEquipmentMailbox.ps1 @@ -34,12 +34,11 @@ Function Invoke-AddEquipmentMailbox { # Block sign-in for the mailbox try { - $BlockSignInRequest = Set-CIPPSignInState -userid $AddEquipmentRequest.ExternalDirectoryObjectId -TenantFilter $Tenant -APIName $APINAME -Headers $Headers -AccountEnabled $false - if ($BlockSignInRequest -like 'Could not disable*') { throw $BlockSignInRequest } - $Results.Add("Blocked sign-in for Equipment mailbox; $($MailboxObject.userPrincipalName)") + $null = Set-CIPPSignInState -userid $AddEquipmentRequest.ExternalDirectoryObjectId -TenantFilter $Tenant -APIName $APINAME -Headers $Headers -AccountEnabled $false + $Results.Add("Successfully blocked sign-in for Equipment mailbox $($MailboxObject.userPrincipalName)") } catch { - $ErrorMessage = Get-CippException -Exception $_ - $Results.Add("Failed to block sign-in for Equipment mailbox: $($MailboxObject.userPrincipalName). Error: $($ErrorMessage.NormalizedError)") + $ErrorMessage = $_.Exception.Message + $Results.Add("Failed to block sign-in for Equipment mailbox: $($MailboxObject.userPrincipalName). Error: $ErrorMessage") } Write-LogMessage -headers $Headers -API $APIName -tenant $Tenant -message "Created equipment mailbox $($MailboxObject.displayName)" -Sev 'Info' $StatusCode = [HttpStatusCode]::OK diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomMailbox.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomMailbox.ps1 index d174b644ab39..50888f859309 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomMailbox.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Resources/Invoke-AddRoomMailbox.ps1 @@ -32,12 +32,11 @@ Function Invoke-AddRoomMailbox { # Block sign-in for the mailbox try { - $BlockSignInRequest = Set-CIPPSignInState -userid $AddRoomRequest.ExternalDirectoryObjectId -TenantFilter $Tenant -APIName $APINAME -Headers $Headers -AccountEnabled $false - if ($BlockSignInRequest -like 'Could not disable*') { throw $BlockSignInRequest } - $Results.Add("Blocked sign-in for Room mailbox; $($MailboxObject.userPrincipalName)") + $null = Set-CIPPSignInState -userid $AddRoomRequest.ExternalDirectoryObjectId -TenantFilter $Tenant -APIName $APINAME -Headers $Headers -AccountEnabled $false + $Results.Add("Successfully blocked sign-in for Room mailbox $($MailboxObject.userPrincipalName)") } catch { - $ErrorMessage = Get-CippException -Exception $_ - $Results.Add("Failed to block sign-in for Room mailbox: $($MailboxObject.userPrincipalName). Error: $($ErrorMessage.NormalizedError)") + $ErrorMessage = $_.Exception.Message + $Results.Add("Failed to block sign-in for Room mailbox: $($MailboxObject.userPrincipalName). Error: $ErrorMessage") } $StatusCode = [HttpStatusCode]::OK } catch { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 94871765ddf8..f974784cf8c0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -46,7 +46,11 @@ function Invoke-CIPPOffboardingJob { } } { $_.DisableSignIn -eq $true } { - Set-CIPPSignInState -TenantFilter $TenantFilter -userid $username -AccountEnabled $false -Headers $Headers -APIName $APIName + try { + Set-CIPPSignInState -TenantFilter $TenantFilter -userid $username -AccountEnabled $false -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.OnedriveAccess } { $Options.OnedriveAccess | ForEach-Object { Set-CIPPSharePointPerms -tenantFilter $TenantFilter -userid $username -OnedriveAccessUser $_.value -Headers $Headers -APIName $APIName } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 index c1e6748b7f32..d521b371535c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecDisableUser.ps1 @@ -22,7 +22,6 @@ Function Invoke-ExecDisableUser { try { $Result = Set-CIPPSignInState -UserID $ID -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -AccountEnabled $Enable - if ($Result -like 'Could not disable*' -or $Result -like 'WARNING: User is AD Sync enabled*') { throw $Result } $StatusCode = [HttpStatusCode]::OK } catch { $Result = $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 b/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 index bf2266cae08b..ced2b88f701f 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSignInState.ps1 @@ -15,17 +15,18 @@ function Set-CIPPSignInState { $body = ConvertTo-Json -InputObject $body -Compress -Depth 5 $UserDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)?`$select=onPremisesSyncEnabled" -noPagination $true -tenantid $TenantFilter -verbose $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter -type PATCH -body $body -verbose - Write-LogMessage -headers $Headers -API $APIName -message "Set account enabled state to $AccountEnabled for $UserID" -Sev 'Info' -tenant $TenantFilter + Write-LogMessage -headers $Headers -API $APIName -message "Successfully set account enabled state to $AccountEnabled for $UserID" -Sev 'Info' -tenant $TenantFilter if ($UserDetails.onPremisesSyncEnabled -eq $true) { - return 'WARNING: User is AD Sync enabled. Please enable/disable in AD.' + throw "WARNING: User $UserID is AD Sync enabled. Please enable/disable in the local AD." } else { - return "Set account enabled state to $AccountEnabled for $UserID" + return "Successfully set account enabled state to $AccountEnabled for $UserID" } } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not disable sign in for $UserID. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Could not disable $UserId. Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to set sign-in state 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/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 index 1d70ab6aba98..b7361fe4b2ea 100644 --- a/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Invoke-CIPPWebhookProcessing.ps1 @@ -26,7 +26,11 @@ function Invoke-CippWebhookProcessing { Write-Host "this is our action: $($action | ConvertTo-Json -Depth 15 -Compress)" switch ($action) { 'disableUser' { - Set-CIPPSignInState -TenantFilter $TenantFilter -User $Data.UserId -AccountEnabled $false -APIName 'Alert Engine' -Headers 'Alert Engine' + try { + Set-CIPPSignInState -TenantFilter $TenantFilter -User $Data.UserId -AccountEnabled $false -APIName 'Alert Engine' -Headers 'Alert Engine' + } catch { + Write-Host "Failed to disable user $($Data.UserId)`: $($_.Exception.Message)" + } } 'becremediate' { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Data.UserId)" -tenantid $TenantFilter).UserPrincipalName @@ -35,7 +39,11 @@ function Invoke-CippWebhookProcessing { } catch { Write-Host "Failed to reset password for $Username`: $($_.Exception.Message)" } - Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + try { + Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName 'Alert Engine' -Headers 'Alert Engine' + } catch { + Write-Host "Failed to disable sign-in for $Username`: $($_.Exception.Message)" + } try { Revoke-CIPPSessions -userid $Username -username $Username -Headers 'Alert Engine' -APIName 'Alert Engine' -tenantFilter $TenantFilter } catch { From 6ebc95b61c09bfd343068bb7b133b3562cbdfbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 22:57:45 +0200 Subject: [PATCH 010/125] Support throw in Set-CIPPSharePointPerms and Set-CIPPMailboxAccess --- .../Users/Invoke-CIPPOffboardingJob.ps1 | 24 ++++++++++++++++--- .../CIPPCore/Public/Set-CIPPMailboxAccess.ps1 | 17 ++++++------- .../Public/Set-CIPPSharePointPerms.ps1 | 11 +++++---- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index f974784cf8c0..71c090a3fbd6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -53,13 +53,31 @@ function Invoke-CIPPOffboardingJob { } } { $_.OnedriveAccess } { - $Options.OnedriveAccess | ForEach-Object { Set-CIPPSharePointPerms -tenantFilter $TenantFilter -userid $username -OnedriveAccessUser $_.value -Headers $Headers -APIName $APIName } + $Options.OnedriveAccess | ForEach-Object { + try { + Set-CIPPSharePointPerms -tenantFilter $TenantFilter -userid $username -OnedriveAccessUser $_.value -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } + } } { $_.AccessNoAutomap } { - $Options.AccessNoAutomap | ForEach-Object { Set-CIPPMailboxAccess -tenantFilter $TenantFilter -userid $username -AccessUser $_.value -Automap $false -AccessRights @('FullAccess') -Headers $Headers -APIName $APIName } + $Options.AccessNoAutomap | ForEach-Object { + try { + Set-CIPPMailboxAccess -tenantFilter $TenantFilter -userid $username -AccessUser $_.value -Automap $false -AccessRights @('FullAccess') -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } + } } { $_.AccessAutomap } { - $Options.AccessAutomap | ForEach-Object { Set-CIPPMailboxAccess -tenantFilter $TenantFilter -userid $username -AccessUser $_.value -Automap $true -AccessRights @('FullAccess') -Headers $Headers -APIName $APIName } + $Options.AccessAutomap | ForEach-Object { + try { + Set-CIPPMailboxAccess -tenantFilter $TenantFilter -userid $username -AccessUser $_.value -Automap $true -AccessRights @('FullAccess') -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } + } } { $_.OOO } { try { diff --git a/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 b/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 index 7ac44a00b9ee..c4ab09866086 100644 --- a/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPMailboxAccess.ps1 @@ -11,18 +11,15 @@ function Set-CIPPMailboxAccess { ) try { - $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-MailboxPermission' -cmdParams @{Identity = $userid; user = $AccessUser; automapping = $Automap; accessRights = $AccessRights; InheritanceType = 'all' } -Anchor $userid + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-MailboxPermission' -cmdParams @{Identity = $userid; user = $AccessUser; AutoMapping = $Automap; accessRights = $AccessRights; InheritanceType = 'all' } -Anchor $userid - if ($Automap) { - Write-LogMessage -headers $Headers -API $APIName -message "Gave $AccessRights permissions to $($AccessUser) on $($userid) with automapping" -Sev 'Info' -tenant $TenantFilter - return "Added $($AccessUser) to $($userid) Shared Mailbox with automapping, with the following permissions: $AccessRights" - } else { - Write-LogMessage -headers $Headers -API $APIName -message "Gave $AccessRights permissions to $($AccessUser) on $($userid) without automapping" -Sev 'Info' -tenant $TenantFilter - return "Added $($AccessUser) to $($userid) Shared Mailbox without automapping, with the following permissions: $AccessRights" - } + $Message = "Successfully added $($AccessUser) to $($userid) Shared Mailbox $($Automap ? 'with' : 'without') AutoMapping, with the following permissions: $AccessRights" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Info' -tenant $TenantFilter + return $Message } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not add mailbox permissions for $($AccessUser) on $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Could not add shared mailbox permissions for $($userid). Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to add mailbox permissions for $($AccessUser) on $($userid). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } diff --git a/Modules/CIPPCore/Public/Set-CIPPSharePointPerms.ps1 b/Modules/CIPPCore/Public/Set-CIPPSharePointPerms.ps1 index 17bf2358d2de..ffc4dbd72fd6 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSharePointPerms.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSharePointPerms.ps1 @@ -42,17 +42,18 @@ function Set-CIPPSharePointPerms { $request = New-GraphPostRequest -scope "$($SharePointInfo.AdminUrl)/.default" -tenantid $TenantFilter -Uri "$($SharePointInfo.AdminUrl)/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' # Write-Host $($request) if (!$request.ErrorInfo.ErrorMessage) { - $Message = "$($OnedriveAccessUser) has been $($RemovePermission ? 'removed from' : 'given') access to $URL" + $Message = "Successfully $($RemovePermission ? 'removed' : 'added') $($OnedriveAccessUser) as an owner of $URL" Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev Info -tenant $TenantFilter return $Message } else { - $message = "Failed to change access: $($request.ErrorInfo.ErrorMessage)" - Write-LogMessage -headers $Headers -API $APIName -message $message -Sev Error -tenant $TenantFilter + $Message = "Failed to change access: $($request.ErrorInfo.ErrorMessage)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev Error -tenant $TenantFilter throw $Message } } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not add new owner to $($OnedriveAccessUser) on $URL. Error: $($ErrorMessage.NormalizedError)" -Sev Error -tenant $TenantFilter -LogData $ErrorMessage - return "Could not add owner for $($URL). Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to set SharePoint permissions for $($OnedriveAccessUser) on $URL. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev Error -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } From 8868dfde8de4067cb2597583c911679a2227ee32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 23:20:09 +0200 Subject: [PATCH 011/125] Support throw for Set-CIPPForwarding and refactor function --- .../Invoke-ExecEditMailboxPermissions.ps1 | 3 +- .../Invoke-ExecEmailForward.ps1 | 67 ++++++++----------- .../Users/Invoke-CIPPOffboardingJob.ps1 | 18 ++++- .../CIPPCore/Public/Set-CIPPForwarding.ps1 | 31 ++++----- 4 files changed, 62 insertions(+), 57 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEditMailboxPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEditMailboxPermissions.ps1 index 4743382dd6c9..c3fa53a4d50f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEditMailboxPermissions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEditMailboxPermissions.ps1 @@ -11,7 +11,8 @@ Function Invoke-ExecEditMailboxPermissions { param($Request, $TriggerMetadata) $APIName = $Request.Params.CIPPEndpoint - Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' $Username = $request.body.userID $Tenantfilter = $request.body.tenantfilter if ($username -eq $null) { exit } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 index c9839dd12cf3..b06455ee99d5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 @@ -10,66 +10,57 @@ Function Invoke-ExecEmailForward { [CmdletBinding()] param($Request, $TriggerMetadata) - $Tenantfilter = $request.body.tenantfilter - $username = $request.body.userid - if ($request.body.ForwardInternal -is [string]) { - $ForwardingAddress = $request.body.ForwardInternal - } else {($request.body.ForwardInternal.value) - $ForwardingAddress = $request.body.ForwardInternal.value - } - $ForwardingSMTPAddress = $request.body.ForwardExternal - $ForwardOption = $request.body.forwardOption + $APIName = $Request.Params.CIPPEndpoint - [bool]$KeepCopy = if ($request.body.KeepCopy -eq 'true') { $true } else { $false } + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' + + + $TenantFilter = $Request.Body.tenantFilter + $Username = $Request.Body.userID + if ($Request.Body.ForwardInternal -is [string]) { + $ForwardingAddress = $Request.Body.ForwardInternal + } else { + $ForwardingAddress = $Request.Body.ForwardInternal.value + } + $ForwardingSMTPAddress = $Request.Body.ForwardExternal + $ForwardOption = $Request.Body.forwardOption + [bool]$KeepCopy = if ($Request.Body.KeepCopy -eq 'true') { $true } else { $false } if ($ForwardOption -eq 'internalAddress') { try { - Set-CIPPForwarding -userid $username -tenantFilter $TenantFilter -APIName $APINAME -Headers $Request.Headers -Forward $ForwardingAddress -KeepCopy $KeepCopy - if (-not $request.body.KeepCopy) { - $results = "Forwarding all email for $($username) to $($ForwardingAddress) and not keeping a copy" - } else { - $results = "Forwarding all email for $($username) to $($ForwardingAddress) and keeping a copy" - } + $Results = Set-CIPPForwarding -UserID $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -Forward $ForwardingAddress -KeepCopy $KeepCopy + $StatusCode = [HttpStatusCode]::OK } catch { - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Could not add forwarding for $($username)" -Sev 'Error' -tenant $TenantFilter - $results = "Could not add forwarding for $($username). Error: $($_.Exception.Message)" - + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } } if ($ForwardOption -eq 'ExternalAddress') { try { - Set-CIPPForwarding -userid $username -tenantFilter $TenantFilter -APIName $APINAME -Headers $Request.Headers -forwardingSMTPAddress $ForwardingSMTPAddress -KeepCopy $KeepCopy - if (-not $request.body.KeepCopy) { - $results = "Forwarding all email for $($username) to $($ForwardingSMTPAddress) and not keeping a copy" - } else { - $results = "Forwarding all email for $($username) to $($ForwardingSMTPAddress) and keeping a copy" - } + $Results = Set-CIPPForwarding -UserID $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -ForwardingSMTPAddress $ForwardingSMTPAddress -KeepCopy $KeepCopy + $StatusCode = [HttpStatusCode]::OK } catch { - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Could not add forwarding for $($username)" -Sev 'Error' -tenant $TenantFilter - $results = "Could not add forwarding for $($username). Error: $($_.Exception.Message)" - + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } - } if ($ForwardOption -eq 'disabled') { try { - Set-CIPPForwarding -userid $username -username $username -tenantFilter $Tenantfilter -Headers $Request.Headers -APIName $APIName -Disable $true - $results = "Disabled Email Forwarding for $($username)" + $Results = Set-CIPPForwarding -UserID $Username -Username $Username -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Disable $true + $StatusCode = [HttpStatusCode]::OK } catch { - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Could not disable Email forwarding for $($username)" -Sev 'Error' -tenant $TenantFilter - $results = "Could not disable Email forwarding for $($username). Error: $($_.Exception.Message)" - + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } } - $Body = @{'Results' = @($results) } - # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $Body + StatusCode = $StatusCode + Body = @{'Results' = @($Results) } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 71c090a3fbd6..99b53268f54c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -88,14 +88,26 @@ function Invoke-CIPPOffboardingJob { } { $_.forward } { if (!$Options.KeepCopy) { - Set-CIPPForwarding -userid $userid -username $username -tenantFilter $TenantFilter -Forward $Options.forward.value -Headers $Headers -APIName $APIName + try { + Set-CIPPForwarding -userid $userid -username $username -tenantFilter $TenantFilter -Forward $Options.forward.value -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } else { $KeepCopy = [boolean]$Options.KeepCopy - Set-CIPPForwarding -userid $userid -username $username -tenantFilter $TenantFilter -Forward $Options.forward.value -KeepCopy $KeepCopy -Headers $Headers -APIName $APIName + try { + Set-CIPPForwarding -userid $userid -username $username -tenantFilter $TenantFilter -Forward $Options.forward.value -KeepCopy $KeepCopy -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } } { $_.disableForwarding } { - Set-CIPPForwarding -userid $userid -username $username -tenantFilter $TenantFilter -Disable $true -Headers $Headers -APIName $APIName + try { + Set-CIPPForwarding -userid $userid -username $username -tenantFilter $TenantFilter -Disable $true -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.RemoveLicenses -eq $true } { Remove-CIPPLicense -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Schedule diff --git a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 index 81e8cf53c9fe..2db867e16b51 100644 --- a/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPForwarding.ps1 @@ -36,10 +36,10 @@ function Set-CIPPForwarding { #> [CmdletBinding(SupportsShouldProcess = $true)] param( - [string]$userid, - [string]$forwardingSMTPAddress, - [string]$tenantFilter, - [string]$username, + [string]$UserID, + [string]$ForwardingSMTPAddress, + [string]$TenantFilter, + [string]$Username, $Headers, [string]$APIName = 'Forwarding', [string]$Forward, @@ -49,25 +49,26 @@ function Set-CIPPForwarding { try { - if (!$username) { $username = $userid } - if ($PSCmdlet.ShouldProcess($username, 'Set forwarding')) { + if (!$Username) { $Username = $UserID } + if ($PSCmdlet.ShouldProcess($Username, 'Set forwarding')) { if ($Disable -eq $true) { - Write-Output "Disabling forwarding for $username" - $null = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; ForwardingSMTPAddress = $null; ForwardingAddress = $null ; DeliverToMailboxAndForward = $false } -Anchor $username - $Message = "Disabled forwarding for $username" + Write-Output "Disabling forwarding for $Username" + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $UserID; ForwardingSMTPAddress = $null; ForwardingAddress = $null ; DeliverToMailboxAndForward = $false } -Anchor $Username + $Message = "Successfully disabled forwarding for $Username" } elseif ($Forward) { - $null = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; ForwardingSMTPAddress = $null; ForwardingAddress = $Forward ; DeliverToMailboxAndForward = $KeepCopy } -Anchor $username - $Message = "Forwarding all email for $username to Internal Address $Forward and keeping a copy set to $KeepCopy" + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $UserID; ForwardingSMTPAddress = $null; ForwardingAddress = $Forward ; DeliverToMailboxAndForward = $KeepCopy } -Anchor $Username + $Message = "Successfully set forwarding for $Username to Internal Address $Forward with keeping a copy set to $KeepCopy" } elseif ($forwardingSMTPAddress) { - $null = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Set-mailbox' -cmdParams @{Identity = $userid; ForwardingSMTPAddress = $forwardingSMTPAddress; ForwardingAddress = $null ; DeliverToMailboxAndForward = $KeepCopy } -Anchor $username - $Message = "Forwarding all email for $username to External Address $ForwardingSMTPAddress and keeping a copy set to $KeepCopy" + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Mailbox' -cmdParams @{Identity = $UserID; ForwardingSMTPAddress = $ForwardingSMTPAddress; ForwardingAddress = $null ; DeliverToMailboxAndForward = $KeepCopy } -Anchor $Username + $Message = "Successfully set forwarding for $Username to External Address $ForwardingSMTPAddress with keeping a copy set to $KeepCopy" } } Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Info' -tenant $TenantFilter return $Message } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not add forwarding for $($username). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Could not add forwarding for $($username). Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to set forwarding for $($Username). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } From 6cff746fb1cbb5b948e99c447eb1399a68d9da41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 23:21:42 +0200 Subject: [PATCH 012/125] Refactor into switch and add comments --- .../Invoke-ExecEmailForward.ps1 | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 index b06455ee99d5..593c69f6eaac 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecEmailForward.ps1 @@ -27,33 +27,37 @@ Function Invoke-ExecEmailForward { $ForwardOption = $Request.Body.forwardOption [bool]$KeepCopy = if ($Request.Body.KeepCopy -eq 'true') { $true } else { $false } - if ($ForwardOption -eq 'internalAddress') { - try { - $Results = Set-CIPPForwarding -UserID $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -Forward $ForwardingAddress -KeepCopy $KeepCopy - $StatusCode = [HttpStatusCode]::OK - } catch { - $Results = $_.Exception.Message - $StatusCode = [HttpStatusCode]::InternalServerError + # Process the forwarding option based on the type selected + switch ($ForwardOption) { + 'internalAddress' { + # Set up internal forwarding to another mailbox within the organization + try { + $Results = Set-CIPPForwarding -UserID $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -Forward $ForwardingAddress -KeepCopy $KeepCopy + $StatusCode = [HttpStatusCode]::OK + } catch { + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError + } } - } - - if ($ForwardOption -eq 'ExternalAddress') { - try { - $Results = Set-CIPPForwarding -UserID $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -ForwardingSMTPAddress $ForwardingSMTPAddress -KeepCopy $KeepCopy - $StatusCode = [HttpStatusCode]::OK - } catch { - $Results = $_.Exception.Message - $StatusCode = [HttpStatusCode]::InternalServerError + 'ExternalAddress' { + # Set up external forwarding to an SMTP address outside the organization + try { + $Results = Set-CIPPForwarding -UserID $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -ForwardingSMTPAddress $ForwardingSMTPAddress -KeepCopy $KeepCopy + $StatusCode = [HttpStatusCode]::OK + } catch { + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError + } } - } - - if ($ForwardOption -eq 'disabled') { - try { - $Results = Set-CIPPForwarding -UserID $Username -Username $Username -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Disable $true - $StatusCode = [HttpStatusCode]::OK - } catch { - $Results = $_.Exception.Message - $StatusCode = [HttpStatusCode]::InternalServerError + 'disabled' { + # Disable email forwarding for the specified user + try { + $Results = Set-CIPPForwarding -UserID $Username -Username $Username -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Disable $true + $StatusCode = [HttpStatusCode]::OK + } catch { + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError + } } } From 1a603ba688d8b5d78e4ba656aa340f7353109428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 23:40:59 +0200 Subject: [PATCH 013/125] Support throw for Remove-CIPPUser --- .../Users/Invoke-CIPPOffboardingJob.ps1 | 6 ++++- Modules/CIPPCore/Public/Remove-CIPPUser.ps1 | 22 ++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 99b53268f54c..0ed3f101aa61 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -113,7 +113,11 @@ function Invoke-CIPPOffboardingJob { Remove-CIPPLicense -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -Schedule } { $_.DeleteUser -eq $true } { - Remove-CIPPUser -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName + try { + Remove-CIPPUser -UserID $userid -Username $Username -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.RemoveRules -eq $true } { Write-Host "Removing rules for $username" diff --git a/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 b/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 index d892e9421227..92b9ae2c7529 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPUser.ps1 @@ -2,22 +2,28 @@ function Remove-CIPPUser { [CmdletBinding()] param ( $Headers, - $userid, - $username, + [parameter(Mandatory = $true)] + [string]$UserID, + [string]$Username, $APIName = 'Remove User', $TenantFilter ) + + try { - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)" -type DELETE -tenant $TenantFilter - Write-LogMessage -headers $Headers -API $APIName -message "Deleted account $username" -Sev 'Info' -tenant $TenantFilter - return "Deleted the user account $username" + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserID)" -type DELETE -tenant $TenantFilter + $Result = "Successfully deleted user with ID: '$UserID'" + if (-not [string]::IsNullOrEmpty($Username)) { $Result += " and Username: '$Username'" } + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' -tenant $TenantFilter + return $Result } catch { $ErrorMessage = Get-CippException -Exception $_ - $Message = "Could not delete $username. Error: $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return $Message + $Result = "Failed to delete user with ID: '$UserID'. Error: $($ErrorMessage.NormalizedError)" + if (-not [string]::IsNullOrEmpty($Username)) { $Result += " and Username: '$Username'" } + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Result } } From a19beb0054cee8da815454c68e26fd0087f937e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 23:41:03 +0200 Subject: [PATCH 014/125] Refactor to use Remove-CIPPUser --- .../Administration/Users/Invoke-RemoveUser.ps1 | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUser.ps1 index 6b5db1ad6733..48d7379aaf71 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-RemoveUser.ps1 @@ -17,21 +17,15 @@ Function Invoke-RemoveUser { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter $UserID = $Request.Query.ID ?? $Request.Body.ID - $UserPrincipalName = $Request.Query.userPrincipalName ?? $Request.Body.userPrincipalName + $Username = $Request.Query.userPrincipalName ?? $Request.Body.userPrincipalName if (!$UserID) { exit } try { - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserID)" -type DELETE -tenant $TenantFilter - $Result = "Successfully deleted user with ID: '$UserID'" - if ($UserPrincipalName) { $Result += " and User Principal Name: '$UserPrincipalName'" } - Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Info' -tenant $TenantFilter + $Result = Remove-CIPPUser -UserID $UserID -Username $Username -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName $StatusCode = [HttpStatusCode]::OK } catch { - $ErrorMessage = Get-CippException -Exception $_ - $Result = "Failed to delete user $($UserID). $($ErrorMessage.NormalizedError)" - if ($UserPrincipalName) { $Result += " User Principal Name: '$($UserPrincipalName)'" } - Write-LogMessage -Headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + $Result = $_.Exception.Message $StatusCode = [HttpStatusCode]::InternalServerError } From 1b137400c4db327aaaed42b82dd7fb5c6ffd9740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 24 Jun 2025 23:45:30 +0200 Subject: [PATCH 015/125] Support throw for Remove-CIPPMailboxRule --- .../Invoke-ExecRemoveMailboxRule.ps1 | 14 ++++++------- .../Users/Invoke-CIPPOffboardingJob.ps1 | 6 +++++- .../Public/Remove-CIPPMailboxRule.ps1 | 21 ++++++++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecRemoveMailboxRule.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecRemoveMailboxRule.ps1 index 4693975d05a9..ec262837d003 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecRemoveMailboxRule.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecRemoveMailboxRule.ps1 @@ -20,19 +20,19 @@ Function Invoke-ExecRemoveMailboxRule { $RuleId = $Request.Query.ruleId ?? $Request.Body.ruleId $Username = $Request.Query.userPrincipalName ?? $Request.Body.userPrincipalName - # Remove the rule - $Results = Remove-CIPPMailboxRule -username $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -RuleId $RuleId -RuleName $RuleName - - if ($Results -like '*Could not delete*') { - $StatusCode = [HttpStatusCode]::InternalServerError - } else { + try { + # Remove the rule + $Results = Remove-CIPPMailboxRule -username $Username -TenantFilter $TenantFilter -APIName $APIName -Headers $Headers -RuleId $RuleId -RuleName $RuleName $StatusCode = [HttpStatusCode]::OK + } catch { + $Results = $_.Exception.Message + $StatusCode = [HttpStatusCode]::InternalServerError } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = @{ Results = $Results } + Body = @{ 'Results' = $Results } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 0ed3f101aa61..7de7a2cc7100 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -121,7 +121,11 @@ function Invoke-CIPPOffboardingJob { } { $_.RemoveRules -eq $true } { Write-Host "Removing rules for $username" - Remove-CIPPMailboxRule -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -RemoveAllRules + try { + Remove-CIPPMailboxRule -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName -RemoveAllRules + } catch { + $_.Exception.Message + } } { $_.RemoveMobile -eq $true } { Remove-CIPPMobileDevice -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName diff --git a/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 b/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 index aeb62aad3446..579c7f2d4801 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPMailboxRule.ps1 @@ -18,30 +18,35 @@ function Remove-CIPPMailboxRule { $Rules = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $Username; IncludeHidden = $true } | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } Write-Host "$($Rules.count) rules found" if ($null -eq $Rules) { - Write-LogMessage -headers $Headers -API $APIName -message "No Rules for $($Username) to delete" -Sev 'Info' -tenant $TenantFilter - return "No rules for $($Username) to delete" + $Message = "No rules found for $($Username) to delete" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Info' -tenant $TenantFilter + return $Message } else { ForEach ($rule in $Rules) { $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-InboxRule' -Anchor $Username -cmdParams @{Identity = $rule.Identity } } + $Message = "Successfully deleted all rules for $($Username)" Write-LogMessage -headers $Headers -API $APIName -message "Deleted rules for $($Username)" -Sev 'Info' -tenant $TenantFilter - return "Deleted rules for $($Username)" + return $Message } } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not delete rules for $($Username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Could not delete rules for $($Username). Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to delete rules for $($Username). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } else { # Only delete 1 rule try { $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-InboxRule' -Anchor $Username -cmdParams @{Identity = $RuleId } + $Message = "Successfully deleted mailbox rule $($RuleName) for $($Username)" Write-LogMessage -headers $Headers -API $APIName -message "Deleted mailbox rule $($RuleName) for $($Username)" -Sev 'Info' -tenant $TenantFilter - return "Deleted mailbox rule $($RuleName) for $($Username)" + return $Message } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not delete rule for $($Username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Could not delete rule for $($Username). Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to delete rule for $($Username). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } } From d4dd3caf60b18a9a55becd659fdb6c166ca2c79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 25 Jun 2025 00:10:49 +0200 Subject: [PATCH 016/125] Support throw for Remove-CIPPMobileDevice and modernize function --- .../Users/Invoke-CIPPOffboardingJob.ps1 | 6 +++- .../Public/Remove-CIPPMobileDevice.ps1 | 33 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 7de7a2cc7100..895ead6afb0d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -128,7 +128,11 @@ function Invoke-CIPPOffboardingJob { } } { $_.RemoveMobile -eq $true } { - Remove-CIPPMobileDevice -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName + try { + Remove-CIPPMobileDevice -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.removeCalendarInvites -eq $true } { Remove-CIPPCalendarInvites -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName diff --git a/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 b/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 index d1d0a8cb45ba..e436fce09d5c 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPMobileDevice.ps1 @@ -1,28 +1,37 @@ function Remove-CIPPMobileDevice { [CmdletBinding()] param( - $userid, - $tenantFilter, - $username, + $UserId, + $TenantFilter, + $Username, $APIName = 'Remove Mobile', $Headers ) try { - $devices = New-ExoRequest -tenantid $tenantFilter -cmdlet 'Get-MobileDevice' -Anchor $username -cmdParams @{mailbox = $username } | ForEach-Object { + $RemovedDevices = [System.Collections.Generic.List[string]]::new() + $ErrorDevices = [System.Collections.Generic.List[string]]::new() + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-MobileDevice' -Anchor $Username -cmdParams @{mailbox = $Username } | ForEach-Object { try { - New-ExoRequest -tenantid $tenantFilter -cmdlet 'Remove-MobileDevice' -Anchor $username -cmdParams @{Identity = $_.Identity } - "Removed device: $($_.FriendlyName)" + $MobileDevice = $_ + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-MobileDevice' -Anchor $Username -cmdParams @{Identity = $MobileDevice.Identity } + $RemovedDevices.Add("$($MobileDevice.FriendlyName)") } catch { - "Could not remove device: $($_.FriendlyName)" + $ErrorDevices.Add("$($MobileDevice.FriendlyName)") } } - if (!$Devices) { $Devices = 'No mobile devices have been removed as we could not find any' } - Write-LogMessage -headers $Headers -API $APIName -message "Deleted mobile devices for $($username)" -Sev 'Info' -tenant $tenantFilter - return $devices + if ($ErrorDevices.Count -eq 0) { + $Message = "Successfully removed $($RemovedDevices.Count) mobile devices for $($Username): $($RemovedDevices -join '; ')" + } else { + $Message = "Failed to remove all mobile devices for $($Username). Successfully removed $($RemovedDevices.Count) mobile devices: $($RemovedDevices -join '; '). Failed to remove $($ErrorDevices.Count) mobile devices: $($ErrorDevices -join '; ')" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter + } + return $Message } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not delete mobile devices for $($username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $tenantFilter -LogData $ErrorMessage - return "Could not delete mobile devices for $($username). Error: $($ErrorMessage.NormalizedError)" + $Message = "Failed to remove mobile devices for $($Username). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Message -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Message } } + From cb7b0d3b732e374deb6bd8e1321b8280eddad90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 25 Jun 2025 00:16:08 +0200 Subject: [PATCH 017/125] Add .cursor/rules to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index a807be8c00b6..4bb4cb1069b0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ Logs ExcludedTenants SendNotifications/config.json .env + + +# Cursor IDE +.cursor/rules From e831f0f77ad30851f2b839a4e1b4dc41d8f9d553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 25 Jun 2025 00:17:48 +0200 Subject: [PATCH 018/125] Support throw for Remove-CIPPCalendarInvites --- .../Users/Invoke-CIPPOffboardingJob.ps1 | 6 +++++- .../Public/Remove-CIPPCalendarInvites.ps1 | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 895ead6afb0d..879117aed12f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -135,7 +135,11 @@ function Invoke-CIPPOffboardingJob { } } { $_.removeCalendarInvites -eq $true } { - Remove-CIPPCalendarInvites -userid $userid -username $Username -tenantFilter $TenantFilter -Headers $Headers -APIName $APIName + try { + Remove-CIPPCalendarInvites -UserID $userid -Username $Username -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } { $_.removePermissions } { if ($RunScheduled) { diff --git a/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 index 0fa8eab58997..4c2919398974 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPCalendarInvites.ps1 @@ -1,22 +1,23 @@ function Remove-CIPPCalendarInvites { [CmdletBinding()] param( - $userid, - $tenantFilter, - $username, + $UserID, + $TenantFilter, + $Username, $APIName = 'Remove Calendar Invites', $Headers ) try { - - New-ExoRequest -tenantid $tenantFilter -cmdlet 'Remove-CalendarEvents' -Anchor $username -cmdParams @{Identity = $username; QueryWindowInDays = 730 ; CancelOrganizedMeetings = $true ; Confirm = $false } - Write-LogMessage -headers $Headers -API $APIName -message "Cancelled all calendar invites for $($username)" -Sev 'Info' -tenant $tenantFilter - "Cancelled all calendar invites for $($username)" + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Remove-CalendarEvents' -Anchor $Username -cmdParams @{Identity = $Username; QueryWindowInDays = 730 ; CancelOrganizedMeetings = $true ; Confirm = $false } + $Result = "Successfully cancelled all calendar invites for $($Username)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Info' -tenant $TenantFilter + return $Result } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $Headers -API $APIName -message "Could not cancel calendar invites for $($username): $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $tenantFilter -LogData $ErrorMessage - return "Could not cancel calendar invites for $($username). Error: $($ErrorMessage.NormalizedError)" + $Result = "Failed to cancel calendar invites for $($Username). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + throw $Result } } From 1e7ea9e5260324973feb1a5d0cff16bd05be9820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 25 Jun 2025 00:23:25 +0200 Subject: [PATCH 019/125] Support throw for Clear-CIPPImmutableID --- .../CIPPCore/Public/Clear-CIPPImmutableId.ps1 | 17 +++++++++-------- .../Users/Invoke-CIPPOffboardingJob.ps1 | 10 +++++++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Modules/CIPPCore/Public/Clear-CIPPImmutableId.ps1 b/Modules/CIPPCore/Public/Clear-CIPPImmutableId.ps1 index ca9d60c172de..eaba79df769d 100644 --- a/Modules/CIPPCore/Public/Clear-CIPPImmutableId.ps1 +++ b/Modules/CIPPCore/Public/Clear-CIPPImmutableId.ps1 @@ -2,21 +2,22 @@ function Clear-CIPPImmutableId { [CmdletBinding()] param ( $TenantFilter, - $userid, + $UserID, $Headers, - $APIName + $APIName = 'Clear Immutable ID' ) try { $Body = [pscustomobject]@{ onPremisesImmutableId = $null } $Body = ConvertTo-Json -InputObject $Body -Depth 5 -Compress - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$userid" -tenantid $TenantFilter -type PATCH -body $Body - Write-LogMessage -headers $Headers -API $APIName -message "Successfully cleared immutable ID for $userid" -sev Info - return 'Successfully cleared immutable ID for user.' + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$UserID" -tenantid $TenantFilter -type PATCH -body $Body + $Result = "Successfully cleared immutable ID for user $UserID" + Write-LogMessage -headers $Headers -API $APIName -message $Result -sev Info -tenant $TenantFilter + return $Result } catch { $ErrorMessage = Get-CippException -Exception $_ - $Message = "Could not clear immutable ID for $($userid): $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -API $APIName -message $Message -sev Error -LogData $ErrorMessage - return $Message + $Result = "Failed to clear immutable ID for $($UserID). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -message $Result -sev Error -tenant $TenantFilter -LogData $ErrorMessage + throw $Result } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 879117aed12f..c4722bf1a57d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -165,11 +165,15 @@ function Invoke-CIPPOffboardingJob { "Removal of permissions queued. This task will run in the background and send it's results to the logbook." } } - { $_.'RemoveMFADevices' } { + { $_.RemoveMFADevices -eq $true } { Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers } - { $_.'ClearImmutableId' -eq $true } { - Clear-CIPPImmutableID -UserID $userid -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName + { $_.ClearImmutableId -eq $true } { + try { + Clear-CIPPImmutableID -UserID $userid -TenantFilter $TenantFilter -Headers $Headers -APIName $APIName + } catch { + $_.Exception.Message + } } } return $Return From f15b13d927b5e4bf47b4719b58732b1929752a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 25 Jun 2025 00:28:11 +0200 Subject: [PATCH 020/125] Support throw for Remove-CIPPUserMFA too --- .../Users/Invoke-CIPPOffboardingJob.ps1 | 6 ++- .../Users/Invoke-ExecResetMFA.ps1 | 1 - .../CIPPCore/Public/Remove-CIPPUserMFA.ps1 | 44 ++++++++++++------- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index c4722bf1a57d..2fa91fdb82b8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -166,7 +166,11 @@ function Invoke-CIPPOffboardingJob { } } { $_.RemoveMFADevices -eq $true } { - Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers + try { + Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers + } catch { + $_.Exception.Message + } } { $_.ClearImmutableId -eq $true } { try { 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 a72a7f82e40e..0755de89c493 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 @@ -19,7 +19,6 @@ Function Invoke-ExecResetMFA { $UserID = $Request.Query.ID ?? $Request.Body.ID try { $Result = Remove-CIPPUserMFA -UserPrincipalName $UserID -TenantFilter $TenantFilter -Headers $Headers - if ($Result -match '^Failed') { throw $Result } $StatusCode = [HttpStatusCode]::OK } catch { $Result = $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Remove-CIPPUserMFA.ps1 b/Modules/CIPPCore/Public/Remove-CIPPUserMFA.ps1 index 2d9efd1e30e2..722d0b73a616 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPUserMFA.ps1 @@ -23,16 +23,21 @@ function Remove-CIPPUserMFA { [Parameter(Mandatory = $true)] [string]$TenantFilter, [Parameter(Mandatory = $false)] - $Headers + $Headers, + [Parameter(Mandatory = $false)] + $APIName = 'Remove MFA Methods' ) Write-Information "Getting auth methods for $UserPrincipalName" try { $AuthMethods = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/authentication/methods" -tenantid $TenantFilter -AsApp $true } catch { - Write-LogMessage -headers $Headers -API 'Remove-CIPPUserMFA' -tenant $TenantFilter -message "Failed to get MFA methods for user $UserPrincipalName" -sev 'Error' -LogData (Get-CippException -Exception $_) - return "Failed to get MFA methods for user $UserPrincipalName - $($_.Exception.Message)" + $ErrorMessage = Get-CippException -Exception $_ + $Message = "Failed to get MFA methods for user $UserPrincipalName. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Message -sev 'Error' -LogData $ErrorMessage + throw $Message } + $Requests = [System.Collections.Generic.List[object]]::new() foreach ($Method in $AuthMethods) { if ($Method.'@odata.type' -and $Method.'@odata.type' -ne '#microsoft.graph.passwordAuthenticationMethod') { @@ -44,22 +49,31 @@ function Remove-CIPPUserMFA { }) } } + if (($Requests | Measure-Object).Count -eq 0) { - Write-LogMessage -headers $Headers -API 'Remove-CIPPUserMFA' -tenant $TenantFilter -message "No MFA methods found for user $UserPrincipalName" -sev 'Info' - $Results = "No MFA methods found for user $($UserPrincipalName)" + $Results = "No MFA methods found for user $UserPrincipalName" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Results -sev 'Info' + return $Results } else { if ($PSCmdlet.ShouldProcess("Remove MFA methods for $UserPrincipalName")) { - $Results = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter -asapp $true -erroraction stop - if ($Results.status -eq 204) { - Write-LogMessage -headers $Headers -API 'Remove-CIPPUserMFA' -tenant $TenantFilter -message "Successfully removed MFA methods for user $UserPrincipalName" -sev 'Info' - $Results = [pscustomobject]@{'Results' = "Successfully completed request. User $($Request.Query.ID) must supply MFA at next logon" } - } else { - $FailedAuthMethods = (($Results | Where-Object { $_.status -ne 204 }).id -split '-')[0] -join ', ' - Write-LogMessage -headers $Headers -API 'Remove-CIPPUserMFA' -tenant $TenantFilter -message "Failed to remove MFA methods for $FailedAuthMethods" -sev 'Error' - $Results = "Failed to reset MFA methods for $FailedAuthMethods" + try { + $Results = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter -asapp $true -ErrorAction Stop + if ($Results.status -eq 204) { + $Message = "Successfully removed MFA methods for user $UserPrincipalName. User must supply MFA at next logon" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Message -sev 'Info' + return $Message + } else { + $FailedAuthMethods = (($Results | Where-Object { $_.status -ne 204 }).id -split '-')[0] -join ', ' + $Message = "Failed to remove MFA methods for $FailedAuthMethods on user $UserPrincipalName" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Message -sev 'Error' + throw $Message + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Message = "Failed to remove MFA methods for user $UserPrincipalName. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Message -sev 'Error' -LogData $ErrorMessage + throw $Message } } } - - return $Results } From 1c340412dad77aba77963a0853438155e24662a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 26 Jun 2025 23:36:19 +0200 Subject: [PATCH 021/125] Enhance Invoke-EditGroup and Invoke-ListGroups functions to support hideFromOutlookClients - Fix GroupName variable in EditGroup --- .../Groups/Invoke-EditGroup.ps1 | 25 ++++++++++++++++++- .../Groups/Invoke-ListGroups.ps1 | 17 +++++++------ 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 index 34ec25c7abdc..a7181d464239 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 @@ -15,7 +15,8 @@ function Invoke-EditGroup { $Results = [System.Collections.Generic.List[string]]@() $UserObj = $Request.Body $GroupType = $UserObj.groupId.addedFields.groupType ? $UserObj.groupId.addedFields.groupType : $UserObj.groupType - $GroupName = $UserObj.groupName ? $UserObj.groupName : $UserObj.groupId.addedFields.groupName + # groupName is used in the Add to Group user action, displayName is used in the Edit Group page + $GroupName = $UserObj.groupName ?? $UserObj.displayName ?? $UserObj.groupId.addedFields.groupName $GroupId = $UserObj.groupId.value ?? $UserObj.groupId $OrgGroup = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)" -tenantid $UserObj.tenantFilter @@ -406,6 +407,28 @@ function Invoke-EditGroup { } } + # Only process hideFromOutlookClients if it was explicitly sent and is a Microsoft 365 group + if ($null -ne $UserObj.hideFromOutlookClients -and $GroupType -eq 'Microsoft 365') { + try { + $Params = @{ Identity = $GroupId; HiddenFromExchangeClientsEnabled = $UserObj.hideFromOutlookClients } + $null = New-ExoRequest -tenantid $TenantId -cmdlet 'Set-UnifiedGroup' -cmdParams $Params -useSystemMailbox $true + + if ($UserObj.hideFromOutlookClients -eq $true) { + $Results.Add("Successfully hidden group mailbox from Outlook for $($GroupName).") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Successfully hidden group mailbox from Outlook for $($GroupName)." -Sev 'Info' + } else { + $Results.Add("Successfully made group mailbox visible in Outlook for $($GroupName).") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Successfully made group mailbox visible in Outlook for $($GroupName)." -Sev 'Info' + } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-Warning "Error in hideFromOutlookClients: $($ErrorMessage.NormalizedError) - $($_.InvocationInfo.ScriptLineNumber)" + $action = if ($UserObj.hideFromOutlookClients -eq $true) { 'hide' } else { 'show' } + $Results.Add("Failed to $action group mailbox in Outlook for $($GroupName).") + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message "Failed to $action group mailbox in Outlook for $($GroupName). Error:$($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + } + } + $body = @{'Results' = @($Results) } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 index a516f92158f7..138fe3334eec 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-ListGroups.ps1 @@ -69,14 +69,14 @@ function Invoke-ListGroups { # get the outside the organization RequireSenderAuthenticationEnabled setting $OnlyAllowInternal = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-DistributionGroup' -cmdParams @{Identity = $GroupID } -Select 'RequireSenderAuthenticationEnabled' -useSystemMailbox $true | Select-Object -ExpandProperty RequireSenderAuthenticationEnabled } elseif ($GroupType -eq 'Microsoft 365') { - $UnifiedGroup = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-UnifiedGroup' -cmdParams @{Identity = $GroupID } -Select 'RequireSenderAuthenticationEnabled,subscriptionEnabled,AutoSubscribeNewMembers' -useSystemMailbox $true - $OnlyAllowInternal = $UnifiedGroup.RequireSenderAuthenticationEnabled + $UnifiedGroupInfo = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-UnifiedGroup' -cmdParams @{Identity = $GroupID } -Select 'RequireSenderAuthenticationEnabled,subscriptionEnabled,AutoSubscribeNewMembers,HiddenFromExchangeClientsEnabled' -useSystemMailbox $true + $OnlyAllowInternal = $UnifiedGroupInfo.RequireSenderAuthenticationEnabled } else { $OnlyAllowInternal = $null } if ($GroupType -eq 'Microsoft 365') { - if ($UnifiedGroup.subscriptionEnabled -eq $true -and $UnifiedGroup.AutoSubscribeNewMembers -eq $true) { $SendCopies = $true } else { $SendCopies = $false } + if ($UnifiedGroupInfo.subscriptionEnabled -eq $true -and $UnifiedGroupInfo.AutoSubscribeNewMembers -eq $true) { $SendCopies = $true } else { $SendCopies = $false } } else { $SendCopies = $null } @@ -85,7 +85,7 @@ function Invoke-ListGroups { if ($BulkRequestArrayList.Count -gt 0) { $RawGraphRequest = New-GraphBulkRequest -tenantid $TenantFilter -scope 'https://graph.microsoft.com/.default' -Requests @($BulkRequestArrayList) -asapp $true $GraphRequest = [PSCustomObject]@{ - groupInfo = ($RawGraphRequest | Where-Object { $_.id -eq 1 }).body | Select-Object *, @{ Name = 'primDomain'; Expression = { $_.mail -split '@' | Select-Object -Last 1 } }, + groupInfo = ($RawGraphRequest | Where-Object { $_.id -eq 1 }).body | Select-Object *, @{ Name = 'primDomain'; Expression = { $_.mail -split '@' | Select-Object -Last 1 } }, @{Name = 'teamsEnabled'; Expression = { if ($_.resourceProvisioningOptions -Like '*Team*') { $true } else { $false } } }, @{Name = 'calculatedGroupType'; Expression = { if ($_.mailEnabled -and $_.securityEnabled) { 'Mail-Enabled Security' } @@ -94,10 +94,11 @@ function Invoke-ListGroups { if (([string]::isNullOrEmpty($_.groupTypes)) -and ($_.mailEnabled) -and (!$_.securityEnabled)) { 'Distribution List' } } }, @{Name = 'dynamicGroupBool'; Expression = { if ($_.groupTypes -contains 'DynamicMembership') { $true } else { $false } } } - members = ($RawGraphRequest | Where-Object { $_.id -eq 2 }).body.value - owners = ($RawGraphRequest | Where-Object { $_.id -eq 3 }).body.value - allowExternal = (!$OnlyAllowInternal) - sendCopies = $SendCopies + members = ($RawGraphRequest | Where-Object { $_.id -eq 2 }).body.value + owners = ($RawGraphRequest | Where-Object { $_.id -eq 3 }).body.value + allowExternal = (!$OnlyAllowInternal) + sendCopies = $SendCopies + hideFromOutlookClients = if ($GroupType -eq 'Microsoft 365') { $UnifiedGroupInfo.HiddenFromExchangeClientsEnabled } else { $null } } } else { $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupID)/$($members)?`$top=999&select=$SelectString" -tenantid $TenantFilter | Select-Object *, @{ Name = 'primDomain'; Expression = { $_.mail -split '@' | Select-Object -Last 1 } }, From 578c8802a03d386e02c14a0c43c00b5b861d863c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 27 Jun 2025 00:15:11 +0200 Subject: [PATCH 022/125] Speed up some standards and fix alerting in DefaultSharingLink --- .../Invoke-CIPPStandardDefaultSharingLink.ps1 | 12 +++++++----- .../Standards/Invoke-CIPPStandardSPAzureB2B.ps1 | 4 ++-- .../Standards/Invoke-CIPPStandardSPDirectSharing.ps1 | 4 ++-- .../Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 | 2 +- .../Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 | 4 ++-- .../Invoke-CIPPStandardSPEmailAttestation.ps1 | 4 ++-- .../Invoke-CIPPStandardSPExternalUserExpiration.ps1 | 4 ++-- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 index 9093ab528e51..f3378b7d0a8e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 @@ -30,7 +30,7 @@ function Invoke-CIPPStandardDefaultSharingLink { param($Tenant, $Settings) $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | - Select-Object -Property DefaultSharingLinkType, DefaultLinkPermission + Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType, DefaultLinkPermission $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq 2) -and ($CurrentState.DefaultLinkPermission -eq 1) @@ -44,11 +44,11 @@ function Invoke-CIPPStandardDefaultSharingLink { } try { - Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + $CurrentState | Set-CIPPSPOTenant -Properties $Properties Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully set default sharing link settings' -Sev Info } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to set default sharing link settings. Error: $ErrorMessage" -Sev Error + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to set default sharing link settings. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage } } } @@ -57,7 +57,9 @@ function Invoke-CIPPStandardDefaultSharingLink { if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Default sharing link settings are configured correctly' -Sev Info } else { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Default sharing link settings are not configured correctly' -Sev Alert + $Message = 'Default sharing link settings are not configured correctly' + Write-StandardsAlert -message $Message -object $CurrentState -tenant $Tenant -standardName 'DefaultSharingLink' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message $Message -Sev Alert } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 index 016f4697e400..4c940025bfca 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 @@ -32,7 +32,7 @@ function Invoke-CIPPStandardSPAzureB2B { param($Tenant, $Settings) $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | - Select-Object -Property EnableAzureADB2BIntegration + Select-Object -Property _ObjectIdentity_, TenantFilter, EnableAzureADB2BIntegration $StateIsCorrect = ($CurrentState.EnableAzureADB2BIntegration -eq $true) @@ -45,7 +45,7 @@ function Invoke-CIPPStandardSPAzureB2B { } try { - Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + $CurrentState | Set-CIPPSPOTenant -Properties $Properties Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully set the SharePoint Azure B2B to enabled' -Sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 index e91952957966..75aad9810b96 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -33,7 +33,7 @@ function Invoke-CIPPStandardSPDirectSharing { param($Tenant, $Settings) $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | - Select-Object -Property DefaultSharingLinkType + Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq 'Direct' -or $CurrentState.DefaultSharingLinkType -eq 1) @@ -46,7 +46,7 @@ function Invoke-CIPPStandardSPDirectSharing { } try { - Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + $CurrentState | Set-CIPPSPOTenant -Properties $Properties Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully set the SharePoint Default Direct Sharing to Direct' -Sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 index 57a4f292367c..2ec1c824be30 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 @@ -46,7 +46,7 @@ function Invoke-CIPPStandardSPDisableLegacyWorkflows { } try { - Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + $CurrentState | Set-CIPPSPOTenant -Properties $Properties Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully disabled Legacy Workflows' -Sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 index c61ec77268fb..f6771ea8bb0d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 @@ -33,7 +33,7 @@ function Invoke-CIPPStandardSPDisallowInfectedFiles { param($Tenant, $Settings) $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | - Select-Object -Property DisallowInfectedFileDownload + Select-Object -Property _ObjectIdentity_, TenantFilter, DisallowInfectedFileDownload $StateIsCorrect = ($CurrentState.DisallowInfectedFileDownload -eq $true) @@ -46,7 +46,7 @@ function Invoke-CIPPStandardSPDisallowInfectedFiles { } try { - Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + $CurrentState | Set-CIPPSPOTenant -Properties $Properties Write-LogMessage -API 'Standards' -tenant $tenant -Message 'Successfully disallowed downloading SharePoint infected files.' -Sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 index c26879a60c2b..507aae2c175e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 @@ -33,7 +33,7 @@ function Invoke-CIPPStandardSPEmailAttestation { param($Tenant, $Settings) - $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property EmailAttestationReAuthDays, EmailAttestationRequired + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, EmailAttestationReAuthDays, EmailAttestationRequired $StateIsCorrect = ($CurrentState.EmailAttestationReAuthDays -eq [int]$Settings.Days) -and ($CurrentState.EmailAttestationRequired -eq $true) @@ -48,7 +48,7 @@ function Invoke-CIPPStandardSPEmailAttestation { } try { - $Response = Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + $Response = $CurrentState | Set-CIPPSPOTenant -Properties $Properties if ($Response.ErrorInfo.ErrorMessage) { $ErrorMessage = Get-NormalizedError -Message $Response.ErrorInfo.ErrorMessage Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to set re-authentication with verification code restriction. Error: $ErrorMessage" -Sev Error diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 index 7db6ed6a53e1..1c34c563ab82 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 @@ -33,7 +33,7 @@ function Invoke-CIPPStandardSPExternalUserExpiration { param($Tenant, $Settings) $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | - Select-Object -Property ExternalUserExpireInDays, ExternalUserExpirationRequired + Select-Object -Property _ObjectIdentity_, TenantFilter, ExternalUserExpireInDays, ExternalUserExpirationRequired $StateIsCorrect = ($CurrentState.ExternalUserExpireInDays -eq $Settings.Days) -and ($CurrentState.ExternalUserExpirationRequired -eq $true) @@ -48,7 +48,7 @@ function Invoke-CIPPStandardSPExternalUserExpiration { } try { - Get-CIPPSPOTenant -TenantFilter $Tenant | Set-CIPPSPOTenant -Properties $Properties + $CurrentState | Set-CIPPSPOTenant -Properties $Properties Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully set External User Expiration' -Sev Info } catch { $ErrorMessage = Get-CippException -Exception $_ From 885da03c0bb652e30baaa1d1ca1e67cd1acad39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 27 Jun 2025 01:06:29 +0200 Subject: [PATCH 023/125] Speed up some standards and fix alerting in DefaultSharingLink --- .../Invoke-CIPPStandardDefaultSharingLink.ps1 | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 index f3378b7d0a8e..3efd8efdba32 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 @@ -7,20 +7,22 @@ function Invoke-CIPPStandardDefaultSharingLink { .SYNOPSIS (Label) Set Default Sharing Link Settings .DESCRIPTION - (Helptext) Sets the default sharing link type to Internal and permission to View in SharePoint and OneDrive. - (DocsDescription) Sets the default sharing link type to Internal and permission to View in SharePoint and OneDrive. + (Helptext) Configure the SharePoint default sharing link type and permission. This setting controls both the type of sharing link created by default and the permission level assigned to those links. + (DocsDescription) Sets the default sharing link type (Direct or Internal) and permission (View) in SharePoint and OneDrive. Direct sharing means links only work for specific people, while Internal sharing means links work for anyone in the organization. .NOTES CAT SharePoint Standards TAG ADDEDCOMPONENT + [{"type":"autoComplete","multiple":false,"creatable":false,"label":"Default Sharing Link Type","name":"standards.DefaultSharingLink.SharingLinkType","options":[{"label":"Direct - Only specific people","value":"Direct"},{"label":"Internal - Anyone in the organization","value":"Internal"}]}] IMPACT Medium Impact ADDEDDATE 2025-06-13 POWERSHELLEQUIVALENT - Set-SPOTenant -DefaultSharingLinkType Internal -DefaultLinkPermission View + Set-SPOTenant -DefaultSharingLinkType [Direct|Internal] -DefaultLinkPermission View RECOMMENDEDBY + CIS UPDATECOMMENTBLOCK Run the Tools\Update-StandardsComments.ps1 script to update this comment block .LINK @@ -29,23 +31,37 @@ function Invoke-CIPPStandardDefaultSharingLink { param($Tenant, $Settings) + # Determine the desired sharing link type (default to Internal if not specified) + $DesiredSharingLinkType = $Settings.SharingLinkType.value ?? 'Internal' + + # Map the string values to numeric values for SharePoint + $SharingLinkTypeMap = @{ + 'Direct' = 1 + 'Internal' = 2 + 'Anyone' = 3 + } + + $DesiredSharingLinkTypeValue = $SharingLinkTypeMap[$DesiredSharingLinkType] + Write-Warning "DesiredSharingLinkTypeValue: $DesiredSharingLinkTypeValue" + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType, DefaultLinkPermission - $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq 2) -and ($CurrentState.DefaultLinkPermission -eq 1) + # Check if the current state matches the desired configuration + $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq $DesiredSharingLinkTypeValue) -and ($CurrentState.DefaultLinkPermission -eq 1) if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Default sharing link settings are already configured correctly' -Sev Info + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Default sharing link settings are already configured correctly (Type: $DesiredSharingLinkType, Permission: View)" -Sev Info } else { $Properties = @{ - DefaultSharingLinkType = 2 # Internal + DefaultSharingLinkType = $DesiredSharingLinkTypeValue DefaultLinkPermission = 1 # View } try { $CurrentState | Set-CIPPSPOTenant -Properties $Properties - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully set default sharing link settings' -Sev Info + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Successfully set default sharing link settings (Type: $DesiredSharingLinkType, Permission: View)" -Sev Info } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to set default sharing link settings. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage @@ -55,9 +71,23 @@ function Invoke-CIPPStandardDefaultSharingLink { if ($Settings.alert -eq $true) { if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Default sharing link settings are configured correctly' -Sev Info + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Default sharing link settings are configured correctly (Type: $DesiredSharingLinkType, Permission: View)" -Sev Info } else { - $Message = 'Default sharing link settings are not configured correctly' + # Determine current values for alert message + $CurrentSharingType = switch ($CurrentState.DefaultSharingLinkType) { + 1 { 'Direct' } + 2 { 'Internal' } + 3 { 'Anyone' } + default { 'Unknown' } + } + $CurrentPermission = switch ($CurrentState.DefaultLinkPermission) { + 0 { 'Edit' } + 1 { 'View' } + 2 { 'Edit' } + default { 'Unknown' } + } + + $Message = "Default sharing link settings are not configured correctly. Current: Type=$CurrentSharingType, Permission=$CurrentPermission. Expected: Type=$DesiredSharingLinkType, Permission=View" Write-StandardsAlert -message $Message -object $CurrentState -tenant $Tenant -standardName 'DefaultSharingLink' -standardId $Settings.standardId Write-LogMessage -API 'Standards' -Tenant $Tenant -Message $Message -Sev Alert } From 3da186cdea7f8aedc582adbd496c86eeba920a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 27 Jun 2025 01:11:48 +0200 Subject: [PATCH 024/125] Remove debug warning for DesiredSharingLinkTypeValue in Invoke-CIPPStandardDefaultSharingLink function --- .../Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 index 3efd8efdba32..ed5be966e106 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 @@ -40,9 +40,7 @@ function Invoke-CIPPStandardDefaultSharingLink { 'Internal' = 2 'Anyone' = 3 } - $DesiredSharingLinkTypeValue = $SharingLinkTypeMap[$DesiredSharingLinkType] - Write-Warning "DesiredSharingLinkTypeValue: $DesiredSharingLinkTypeValue" $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType, DefaultLinkPermission From 1ce871e3fbf7baee739b4ed6aeebf196ce377150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 27 Jun 2025 01:25:43 +0200 Subject: [PATCH 025/125] Deprecate SPDirectSharing --- .../Invoke-CIPPStandardSPDirectSharing.ps1 | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 index 75aad9810b96..6e9f363bb993 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -32,47 +32,5 @@ function Invoke-CIPPStandardSPDirectSharing { param($Tenant, $Settings) - $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | - Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType - - $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq 'Direct' -or $CurrentState.DefaultSharingLinkType -eq 1) - - if ($Settings.remediate -eq $true) { - if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'SharePoint Default Direct Sharing is already enabled' -Sev Info - } else { - $Properties = @{ - DefaultSharingLinkType = 1 - } - - try { - $CurrentState | Set-CIPPSPOTenant -Properties $Properties - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully set the SharePoint Default Direct Sharing to Direct' -Sev Info - } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to set the SharePoint Default Direct Sharing to Direct. Error: $ErrorMessage" -Sev Error - } - } - } - - if ($Settings.alert -eq $true) { - if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'SharePoint Direct Sharing is enabled' -Sev Info - } else { - $Message = 'SharePoint Default Direct Sharing is not enabled.' - Write-StandardsAlert -message $Message -object $CurrentState -tenant $Tenant -standardName 'SPDirectSharing' -standardId $Settings.standardId - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message $Message -Sev Alert - } - } - - if ($Settings.report -eq $true) { - Add-CIPPBPAField -FieldName 'DirectSharing' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant - - if ($StateIsCorrect) { - $FieldValue = $true - } else { - $FieldValue = $CurrentState - } - Set-CIPPStandardsCompareField -FieldName 'standards.SPDirectSharing' -FieldValue $FieldValue -Tenant $Tenant - } + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'This standard has been deprecated in favor of the Set Default Sharing Link Settings standard. Please update your standards to use new standard.' -Sev Error } From 1b0b5c6ea90de9b788faa6f99926246bcb9d5012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 27 Jun 2025 01:32:54 +0200 Subject: [PATCH 026/125] Fix wrong alert type --- .../Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 index 6e9f363bb993..37141d426f84 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -32,5 +32,5 @@ function Invoke-CIPPStandardSPDirectSharing { param($Tenant, $Settings) - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'This standard has been deprecated in favor of the Set Default Sharing Link Settings standard. Please update your standards to use new standard.' -Sev Error + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'This standard has been deprecated in favor of the Set Default Sharing Link Settings standard. Please update your standards to use new standard.' -Sev Alert } From f95cfc5f1889081170da46d77598de2aaff4ec7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 28 Jun 2025 11:14:20 +0200 Subject: [PATCH 027/125] Fix: Fix tablefilter --- .../Invoke-ListAppConsentRequests.ps1 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 index 89472cd5821a..9c865b11db3b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 @@ -22,7 +22,7 @@ function Invoke-ListAppConsentRequests { } $appConsentRequests = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $TenantFilter # Need the beta endpoint to get consentType - $Results = foreach ($app in $appConsentRequests) { + $AllResults = foreach ($app in $appConsentRequests) { $userConsentRequests = New-GraphGetRequest -Uri "https://graph.microsoft.com/v1.0/identityGovernance/appConsent/appConsentRequests/$($app.id)/userConsentRequests" -tenantid $TenantFilter $userConsentRequests | ForEach-Object { [pscustomobject]@{ @@ -48,6 +48,21 @@ function Invoke-ListAppConsentRequests { } } } + + # Apply filtering if requested + if ($Request.Query.Filter -eq $true) { + Write-Host 'Applying filters to app consent requests' + $RequestStatus = $Request.Query.RequestStatus + + if ($RequestStatus) { + Write-Host "Filtering by RequestStatus: $RequestStatus" + $Results = $AllResults | Where-Object { $_.requestStatus -eq $RequestStatus } + } else { + $Results = $AllResults + } + } else { + $Results = $AllResults + } $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-CippException -Exception $_ From b737c182bedc05e68af21d3d98f332af7bcb63c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 28 Jun 2025 12:56:33 +0200 Subject: [PATCH 028/125] Enhance Invoke-ListAppConsentRequests: Add server-side filtering for RequestStatus and improve error handling --- .../Invoke-ListAppConsentRequests.ps1 | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 index 9c865b11db3b..0029cf95e0ba 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Invoke-ListAppConsentRequests.ps1 @@ -15,14 +15,34 @@ function Invoke-ListAppConsentRequests { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.tenantFilter + $RequestStatus = $Request.Query.RequestStatus + $Filter = $Request.Query.Filter try { if ($TenantFilter -eq 'AllTenants') { throw 'AllTenants is not yet supported' } - $appConsentRequests = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $TenantFilter # Need the beta endpoint to get consentType - $AllResults = foreach ($app in $appConsentRequests) { + # Apply server-side filtering if requested + $Uri = 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' # Need the beta endpoint to get consentType + if ($Filter -eq $true -and $RequestStatus) { + switch ($RequestStatus) { + 'InProgress' { + $FilterQuery = "userConsentRequests/any (u:u/status eq '$RequestStatus')" + $Uri = "$Uri`?`$filter=$([System.Web.HttpUtility]::UrlEncode($FilterQuery))" + Write-Host "Applying server-side filter for RequestStatus: $RequestStatus" + $ServerSideFilteringApplied = $true + } + default { + # All the other values are not supported yet even if the Graph API docs say they are. -Bobby + $ServerSideFilteringApplied = $false + } + } + } + + $appConsentRequests = New-GraphGetRequest -Uri $Uri -tenantid $TenantFilter + + $Results = foreach ($app in $appConsentRequests) { $userConsentRequests = New-GraphGetRequest -Uri "https://graph.microsoft.com/v1.0/identityGovernance/appConsent/appConsentRequests/$($app.id)/userConsentRequests" -tenantid $TenantFilter $userConsentRequests | ForEach-Object { [pscustomobject]@{ @@ -49,26 +69,18 @@ function Invoke-ListAppConsentRequests { } } - # Apply filtering if requested - if ($Request.Query.Filter -eq $true) { - Write-Host 'Applying filters to app consent requests' - $RequestStatus = $Request.Query.RequestStatus - + # Apply filtering if requested. Has to be done before and after the foreach loop, as the serverside filter is only supported for InProgress. + if ($Filter -eq $true -and $ServerSideFilteringApplied -eq $false) { if ($RequestStatus) { Write-Host "Filtering by RequestStatus: $RequestStatus" - $Results = $AllResults | Where-Object { $_.requestStatus -eq $RequestStatus } - } else { - $Results = $AllResults + $Results = $Results | Where-Object { $_.requestStatus -eq $RequestStatus } } - } else { - $Results = $AllResults } $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-CippException -Exception $_ $StatusCode = [HttpStatusCode]::InternalServerError - Write-LogMessage -Headers $Headers -API $APIName -message 'app consent request list failed' -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - $Results = @{ appDisplayName = "Error: $($ErrorMessage.NormalizedError)" } + $Results = "Error: $($ErrorMessage.NormalizedError)" } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ From cf7d592504980da53e48d6ef1e79ff3d10df4c0e Mon Sep 17 00:00:00 2001 From: ngms-psh Date: Sun, 29 Jun 2025 17:27:51 +0200 Subject: [PATCH 029/125] Add PATCH method used by Invoke-EditIntuneScript --- CIPPHttpTrigger/function.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIPPHttpTrigger/function.json b/CIPPHttpTrigger/function.json index 5aa5c500a642..ae38c5d81a87 100644 --- a/CIPPHttpTrigger/function.json +++ b/CIPPHttpTrigger/function.json @@ -7,7 +7,7 @@ "type": "httpTrigger", "direction": "in", "name": "Request", - "methods": ["get", "post"], + "methods": ["get", "post", "patch"], "route": "{CIPPEndpoint}" }, { From 57fec347053dd0dee76be77e1803e6208259d2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 29 Jun 2025 18:20:42 +0200 Subject: [PATCH 030/125] Revert "Deprecate SPDirectSharing" This reverts commit 1ce871e3fbf7baee739b4ed6aeebf196ce377150. --- .../Invoke-CIPPStandardSPDirectSharing.ps1 | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 index 37141d426f84..9538b3e34325 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -32,5 +32,49 @@ function Invoke-CIPPStandardSPDirectSharing { param($Tenant, $Settings) - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'This standard has been deprecated in favor of the Set Default Sharing Link Settings standard. Please update your standards to use new standard.' -Sev Alert + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'This standard has been deprecated in favor of the "Set Default Sharing Link Settings" standard. Please update your standards to use new standard. However this will continue to function.' -Sev Alert + + $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | + Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType + + $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq 'Direct' -or $CurrentState.DefaultSharingLinkType -eq 1) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'SharePoint Default Direct Sharing is already enabled' -Sev Info + } else { + $Properties = @{ + DefaultSharingLinkType = 1 + } + + try { + $CurrentState | Set-CIPPSPOTenant -Properties $Properties + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Successfully set the SharePoint Default Direct Sharing to Direct' -Sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to set the SharePoint Default Direct Sharing to Direct. Error: $ErrorMessage" -Sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'SharePoint Direct Sharing is enabled' -Sev Info + } else { + $Message = 'SharePoint Default Direct Sharing is not enabled.' + Write-StandardsAlert -message $Message -object $CurrentState -tenant $Tenant -standardName 'SPDirectSharing' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message $Message -Sev Alert + } + } + + if ($Settings.report -eq $true) { + Add-CIPPBPAField -FieldName 'DirectSharing' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant + + if ($StateIsCorrect) { + $FieldValue = $true + } else { + $FieldValue = $CurrentState + } + Set-CIPPStandardsCompareField -FieldName 'standards.SPDirectSharing' -FieldValue $FieldValue -Tenant $Tenant + } } From edda5010f85f6eae6f6e2110cdb82fff7523e2e2 Mon Sep 17 00:00:00 2001 From: ngms-psh Date: Sun, 29 Jun 2025 19:38:23 +0200 Subject: [PATCH 031/125] Add PATCH method used by Invoke-EditIntuneScript --- CIPPHttpTrigger/function.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIPPHttpTrigger/function.json b/CIPPHttpTrigger/function.json index 5aa5c500a642..ae38c5d81a87 100644 --- a/CIPPHttpTrigger/function.json +++ b/CIPPHttpTrigger/function.json @@ -7,7 +7,7 @@ "type": "httpTrigger", "direction": "in", "name": "Request", - "methods": ["get", "post"], + "methods": ["get", "post", "patch"], "route": "{CIPPEndpoint}" }, { From 3a967467f1d735d7b856e2719b111b290dc73cc8 Mon Sep 17 00:00:00 2001 From: ngms-psh Date: Sun, 29 Jun 2025 19:47:25 +0200 Subject: [PATCH 032/125] Changed method for EditIntuneScript to POST --- .../Endpoint/MEM/Invoke-EditIntuneScript.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-EditIntuneScript.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-EditIntuneScript.ps1 index 715bdd7b3c52..c084cd79206a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-EditIntuneScript.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-EditIntuneScript.ps1 @@ -72,6 +72,12 @@ function Invoke-EditIntuneScript { } } 'PATCH' { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = "Method $($Request.Method) is not supported." + }) + } + 'POST' { # Parse the script data to determine type $scriptData = $Request.Body.IntuneScript | ConvertFrom-Json $scriptType = $Request.Body.ScriptType @@ -116,8 +122,5 @@ function Invoke-EditIntuneScript { }) } } - 'POST' { - Write-Output 'Adding script' - } } } From 2dcec4474c23f94520e28fa9f3afb9793d347b5e Mon Sep 17 00:00:00 2001 From: ngms-psh Date: Sun, 29 Jun 2025 19:49:02 +0200 Subject: [PATCH 033/125] Revert to only allowing GET and POST methods --- CIPPHttpTrigger/function.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIPPHttpTrigger/function.json b/CIPPHttpTrigger/function.json index ae38c5d81a87..5aa5c500a642 100644 --- a/CIPPHttpTrigger/function.json +++ b/CIPPHttpTrigger/function.json @@ -7,7 +7,7 @@ "type": "httpTrigger", "direction": "in", "name": "Request", - "methods": ["get", "post", "patch"], + "methods": ["get", "post"], "route": "{CIPPEndpoint}" }, { From bc30160bf73a38fcd40a8e7633e8d5eebd37f03d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 29 Jun 2025 18:16:33 +0200 Subject: [PATCH 034/125] new standard CustomBannedPasswordList Bit by bit Update now should work Working now for real this time i swear --- ...e-CIPPStandardCustomBannedPasswordList.ps1 | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 new file mode 100644 index 000000000000..c4be7cc3fd11 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 @@ -0,0 +1,238 @@ +function Invoke-CIPPStandardCustomBannedPasswordList { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) CustomBannedPasswordList + .SYNOPSIS + (Label) Update Entra ID Custom Banned Password List + .DESCRIPTION + (Helptext) Updates the Entra ID custom banned password list with organization-specific terms. Requires Entra ID P1 or P2 licenses. Enter words separated by commas or new lines. Each word must be 4-16 characters long. Maximum 1,000 words allowed. + (DocsDescription) Updates the Entra ID custom banned password list with organization-specific terms that should be blocked from user passwords. This supplements the global banned password list maintained by Microsoft. The custom list is limited to 1,000 key base terms of 4-16 characters each. Entra ID will block variations and combinations of these terms in user passwords. + .NOTES + CAT + Global Standards + TAG + "CIS M365 5.0 (5.2.3.2)" + ADDEDCOMPONENT + {"type":"textArea","name":"standards.CustomBannedPasswordList.BannedWords","label":"Banned Words List","placeholder":"Enter banned words separated by commas or new lines (4-16 characters each, max 1000 words)","required":true,"rows":10} + IMPACT + Medium Impact + ADDEDDATE + 2025-06-28 + POWERSHELLEQUIVALENT + Get-MgBetaDirectorySetting, New-MgBetaDirectorySetting, Update-MgBetaDirectorySetting + RECOMMENDEDBY + "CIS", "CIPP" + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/list-standards + #> + + param($Tenant, $Settings) + + $PasswordRuleTemplateId = '5cf42378-d67d-4f36-ba46-e8b86229381d' + + # Parse and validate banned words from input + $BannedWordsInput = $Settings.BannedWords + if ([string]::IsNullOrWhiteSpace($BannedWordsInput)) { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'CustomBannedPasswordList: No banned words provided' -sev Error + return + } + + # Split input by commas, newlines, or semicolons and clean up + $BannedWordsList = $BannedWordsInput -split '[,;\r\n]+' | ForEach-Object { ($_.Trim()) } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique + + # Validate word count + if ($BannedWordsList.Count -gt 1000) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "CustomBannedPasswordList: Too many banned words provided ($($BannedWordsList.Count)). Maximum allowed is 1000." -sev Error + return + } + + # Validate word length (4-16 characters), remove duplicates and invalid words + $ValidBannedWordsList = [System.Collections.Generic.List[string]]::new() + $InvalidWords = [System.Collections.Generic.List[string]]::new() + + foreach ($Word in $BannedWordsList) { + if ($Word.Length -ge 4 -and $Word.Length -le 16) { + $ValidBannedWordsList.Add($Word) + } else { + $InvalidWords.Add($Word) + } + } + $BannedWordsList = $ValidBannedWordsList | Select-Object -Unique + + # Alert if invalid words are found + if ($InvalidWords.Count -gt 0) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "CustomBannedPasswordList: Invalid words found in input (must be 4-16 characters). Please remove the following words: $($InvalidWords -join ', ')" -sev Warning + } + + # Get existing directory settings for password rules + try { + $ExistingSettings = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/settings' -tenantid $Tenant | Where-Object { $_.templateId -eq $PasswordRuleTemplateId } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to get existing Custom Banned Password List: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + return + } + + if ($Settings.remediate -eq $true) { + Write-Host 'Time to remediate Custom Banned Password List' + + if ($null -eq $ExistingSettings) { + Write-Host 'No existing Custom Banned Password List found, creating new one' + # Create new directory setting with default values if it doesn't exist + try { + $Body = @{ + templateId = $PasswordRuleTemplateId + values = @( + @{ + name = 'EnableBannedPasswordCheck' + value = 'True' + } + @{ + name = 'BannedPasswordList' + value = $BannedWordsList -join ([char]9) + } + @{ + name = 'LockoutDurationInSeconds' + value = '60' + } + @{ + name = 'LockoutThreshold' + value = '10' + } + @{ + name = 'EnableBannedPasswordCheckOnPremises' + value = 'False' + } + @{ + name = 'BannedPasswordCheckOnPremisesMode' + value = 'Audit' + } + ) + } + $JsonBody = ConvertTo-Json -Depth 10 -InputObject $Body -Compress + + $ExistingSettings = New-GraphPostRequest -tenantid $Tenant -Uri 'https://graph.microsoft.com/beta/settings' -Type POST -Body $JsonBody + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Custom Banned Password List created with $($BannedWordsList.Count) words." -sev Info + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to create Custom Banned Password List: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } else { + Write-Host 'Existing Custom Banned Password List found, updating it' + # Update existing directory setting + try { + # Get the current passwords and check if all the new words are already in the list + $CurrentBannedWords = $ExistingSettings.values | Where-Object { $_.name -eq 'BannedPasswordList' } + $CurrentBannedWords = $CurrentBannedWords.value -split ([char]9) + + # Check if the new words are already in the list + $NewBannedWords = $BannedWordsList | Where-Object { $CurrentBannedWords -notcontains $_ } + if ($NewBannedWords.Count -eq 0 -and ($ExistingSettings.values | Where-Object { $_.name -eq 'EnableBannedPasswordCheck' }).value -eq 'True') { + Write-Host 'No new words to add' + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Custom Banned Password List is already configured with $($CurrentBannedWords.Count) words." -sev Info + } else { + Write-Host "$($NewBannedWords.Count) new words to add" + $AllBannedWords = [System.Collections.Generic.List[string]]::new() + $NewBannedWords | ForEach-Object { $AllBannedWords.Add($_) } + $CurrentBannedWords | ForEach-Object { $AllBannedWords.Add($_) } + $AllBannedWords = $AllBannedWords | Select-Object -Unique -First 1000 | Where-Object { $_ -ne $null } + + $Body = @{ + values = @( + @{ + name = 'EnableBannedPasswordCheck' + value = 'True' + } + @{ + name = 'BannedPasswordList' + value = $AllBannedWords -join ([char]9) + } + @{ + name = 'LockoutDurationInSeconds' + value = ($ExistingSettings.values | Where-Object { $_.name -eq 'LockoutDurationInSeconds' }).value + } + @{ + name = 'LockoutThreshold' + value = ($ExistingSettings.values | Where-Object { $_.name -eq 'LockoutThreshold' }).value + } + @{ + name = 'EnableBannedPasswordCheckOnPremises' + value = ($ExistingSettings.values | Where-Object { $_.name -eq 'EnableBannedPasswordCheckOnPremises' }).value + } + @{ + name = 'BannedPasswordCheckOnPremisesMode' + value = ($ExistingSettings.values | Where-Object { $_.name -eq 'BannedPasswordCheckOnPremisesMode' }).value + } + ) + } + + $JsonBody = ConvertTo-Json -Depth 10 -InputObject $Body -Compress + $null = New-GraphPostRequest -tenantid $Tenant -Uri "https://graph.microsoft.com/beta/settings/$($ExistingSettings.id)" -Type PATCH -Body $JsonBody + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Custom Banned Password List updated with $($NewBannedWords.Count) new words." -sev Info + } + + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to update Custom Banned Password List: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage + } + } + } + + if ($Settings.alert -eq $true) { + if ($null -eq $ExistingSettings) { + Write-StandardsAlert -message 'Custom Banned Password List is not configured' -object @{Status = 'Not Configured'; WordCount = 0 } -tenant $tenant -standardName 'CustomBannedPasswordList' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Custom Banned Password List is not configured' -sev Info + } else { + $BannedPasswordCheckEnabled = $ExistingSettings.values | Where-Object { $_.name -eq 'EnableBannedPasswordCheck' } + $CurrentBannedWords = $ExistingSettings.values | Where-Object { $_.name -eq 'BannedPasswordList' } + $CurrentBannedWords = if ($CurrentBannedWords.value) { ($CurrentBannedWords.value -split ([char]9)) } else { @() } + + # Find missing words from input + $MissingInputWords = $BannedWordsList | Where-Object { $CurrentBannedWords -notcontains $_ } + + if ($MissingInputWords.Count -gt 0) { + Write-StandardsAlert -message "Custom Banned Password List is missing $($MissingInputWords.Count) input words: $($MissingInputWords -join ', ')" -object @{Status = 'Configured but Missing Input Words'; MissingWords = $MissingInputWords; Enabled = $BannedPasswordCheckEnabled.value } -tenant $tenant -standardName 'CustomBannedPasswordList' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $tenant -message "Custom Banned Password List is missing $($MissingInputWords.Count) input words: $($MissingInputWords -join ', ')" -sev Info + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Custom Banned Password List contains all input words ($($BannedWordsList.Count))." -sev Info + } + } + } + + if ($Settings.report -eq $true) { + if ($null -eq $ExistingSettings) { + $BannedPasswordState = @{ + Status = 'Not Configured' + Enabled = $false + WordCount = 0 + Compliant = $false + MissingInputWords = $BannedWordsList + } + } else { + $BannedPasswordCheckEnabled = $ExistingSettings.values | Where-Object { $_.name -eq 'EnableBannedPasswordCheck' } + $CurrentBannedWords = $ExistingSettings.values | Where-Object { $_.name -eq 'BannedPasswordList' } + $CurrentBannedWords = if ($CurrentBannedWords.value) { ($CurrentBannedWords.value -split ([char]9)) } else { @() } + $CurrentWordCount = $CurrentBannedWords.Count + + # Find missing words from input + $MissingInputWords = $BannedWordsList | Where-Object { $CurrentBannedWords -notcontains $_ } + + $BannedPasswordState = @{ + Status = 'Configured' + Enabled = $BannedPasswordCheckEnabled.value -eq 'True' + WordCount = $CurrentWordCount + Compliant = ($BannedPasswordCheckEnabled.value -eq 'True' -and $MissingInputWords.Count -eq 0) + MissingInputWords = $MissingInputWords + } + } + + Add-CIPPBPAField -FieldName 'CustomBannedPasswordList' -FieldValue $BannedPasswordState -StoreAs json -Tenant $tenant + Set-CIPPStandardsCompareField -FieldName 'standards.CustomBannedPasswordList' -FieldValue $BannedPasswordState.Com -Tenant $tenant + } + + +} From af4771683e25d64ba38a398e8eae8cae41c29a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 30 Jun 2025 13:49:29 +0200 Subject: [PATCH 035/125] whoops --- .../Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 index c4be7cc3fd11..5191bf2cc04c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCustomBannedPasswordList.ps1 @@ -231,7 +231,7 @@ function Invoke-CIPPStandardCustomBannedPasswordList { } Add-CIPPBPAField -FieldName 'CustomBannedPasswordList' -FieldValue $BannedPasswordState -StoreAs json -Tenant $tenant - Set-CIPPStandardsCompareField -FieldName 'standards.CustomBannedPasswordList' -FieldValue $BannedPasswordState.Com -Tenant $tenant + Set-CIPPStandardsCompareField -FieldName 'standards.CustomBannedPasswordList' -FieldValue $BannedPasswordState.Compliant -Tenant $tenant } From a67c910be3b6e5e4c2f4f0324f1583469a8c128b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 30 Jun 2025 18:59:57 +0200 Subject: [PATCH 036/125] Add defender exclusions page --- .../MEM/Invoke-AddDefenderDeployment.ps1 | 103 +++++++++++++++++- 1 file changed, 99 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 index f7e20a67660a..d0ca782f9754 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 @@ -18,6 +18,7 @@ Function Invoke-AddDefenderDeployment { if ('AllTenants' -in $Tenants) { $Tenants = (Get-Tenants -IncludeErrors).defaultDomainName } $Compliance = $Request.Body.Compliance $PolicySettings = $Request.Body.Policy + $DefenderExclusions = $Request.Body.Exclusion $ASR = $Request.Body.ASR $EDR = $Request.Body.EDR $results = foreach ($tenant in $Tenants) { @@ -117,7 +118,7 @@ Function Invoke-AddDefenderDeployment { if ($PolicySettings.AssignTo -ne 'None') { $AssignBody = if ($PolicySettings.AssignTo -ne 'AllDevicesAndUsers') { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($PolicySettings.AssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' } $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($PolicyRequest.id)')/assign" -tenantid $tenant -type POST -body $AssignBody - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($tenant) -message "Assigned policy $($DisplayName) to $($PolicySettings.AssignTo)" -Sev 'Info' + Write-LogMessage -headers $Headers -API $APINAME -tenant $($tenant) -message "Assigned policy $($DisplayName) to $($PolicySettings.AssignTo)" -Sev 'Info' } "$($tenant): Successfully set Default AV Policy settings" } @@ -175,7 +176,7 @@ Function Invoke-AddDefenderDeployment { if ($ASR.AssignTo -and $ASR.AssignTo -ne 'none') { $AssignBody = if ($ASR.AssignTo -ne 'AllDevicesAndUsers') { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($asr.AssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' } $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($ASRRequest.id)')/assign" -tenantid $tenant -type POST -body $AssignBody - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($tenant) -message "Assigned policy $($DisplayName) to $($ASR.AssignTo)" -Sev 'Info' + Write-LogMessage -headers $Headers -API $APINAME -tenant $($tenant) -message "Assigned policy $($DisplayName) to $($ASR.AssignTo)" -Sev 'Info' } "$($tenant): Successfully added ASR Settings" } @@ -252,15 +253,109 @@ Function Invoke-AddDefenderDeployment { if ($ASR -and $ASR.AssignTo -ne 'none') { $AssignBody = if ($ASR.AssignTo -ne 'AllDevicesAndUsers') { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($asr.AssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' } $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($EDRRequest.id)')/assign" -tenantid $tenant -type POST -body $AssignBody - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($tenant) -message "Assigned EDR policy $($DisplayName) to $($ASR.AssignTo)" -Sev 'Info' + Write-LogMessage -headers $Headers -API $APINAME -tenant $($tenant) -message "Assigned EDR policy $($DisplayName) to $($ASR.AssignTo)" -Sev 'Info' } "$($tenant): Successfully added EDR Settings" } } } + # Exclusion Policy Section + $ExclusionToggle = $Request.Body.showExclusionPolicy + $ExcludedExtensions = $Request.Body.Exclusion.excludedExtensions + $ExcludedPaths = $Request.Body.Exclusion.excludedPaths + $ExcludedProcesses = $Request.Body.Exclusion.excludedProcesses + $ExclusionAssignTo = $Request.Body.Exclusion.AssignTo + if ($ExclusionToggle -and $DefenderExclusions) { + function Escape-ExclusionValue($val) { + $escaped = $val -replace '\\', '\\\\' # Escape backslashes + if ($escaped -match ' ' -and -not ($escaped -match '^".*"$')) { + $escaped = '"' + $escaped + '"' + } + return $escaped + } + $extArr = @() + if ($ExcludedExtensions) { + $extArr = $ExcludedExtensions | Where-Object { $_ -and $_.Trim() } | ForEach-Object { + @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = (Escape-ExclusionValue $_) } + } + } + $pathArr = @() + if ($ExcludedPaths) { + $pathArr = $ExcludedPaths | Where-Object { $_ -and $_.Trim() } | ForEach-Object { + @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = (Escape-ExclusionValue $_) } + } + } + $procArr = @() + if ($ExcludedProcesses) { + $procArr = $ExcludedProcesses | Where-Object { $_ -and $_.Trim() } | ForEach-Object { + @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = (Escape-ExclusionValue $_) } + } + } + $ExclusionSettings = @() + if ($extArr.Count -gt 0) { + $ExclusionSettings = $ExclusionSettings + @(@{ + id = '2' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' + settingDefinitionId = 'device_vendor_msft_policy_config_defender_excludedextensions' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'c203725b-17dc-427b-9470-673a2ce9cd5e' } + simpleSettingCollectionValue = @($extArr) + } + }) + } + if ($pathArr.Count -gt 0) { + $ExclusionSettings = $ExclusionSettings + @(@{ + id = '1' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' + settingDefinitionId = 'device_vendor_msft_policy_config_defender_excludedpaths' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'aaf04adc-c639-464f-b4a7-152e784092e8' } + simpleSettingCollectionValue = @($pathArr) + } + }) + } + if ($procArr.Count -gt 0) { + $ExclusionSettings = $ExclusionSettings + @(@{ + id = '0' + settingInstance = @{ + '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' + settingDefinitionId = 'device_vendor_msft_policy_config_defender_excludedprocesses' + settingInstanceTemplateReference = @{ settingInstanceTemplateId = '96b046ed-f138-4250-9ae0-b0772a93d16f' } + simpleSettingCollectionValue = @($procArr) + } + }) + } + if ($ExclusionSettings.Count -gt 0) { + $ExclusionBody = ConvertTo-Json -Depth 15 -Compress -InputObject @{ + name = 'Default AV Exclusion Policy' + displayName = 'Default AV Exclusion Policy' + settings = $ExclusionSettings + platforms = 'windows10' + technologies = 'mdm,microsoftSense' + templateReference = @{ + templateId = '45fea5e9-280d-4da1-9792-fb5736da0ca9_1' + templateFamily = 'endpointSecurityAntivirus' + templateDisplayName = 'Microsoft Defender Antivirus exclusions' + templateDisplayVersion = 'Version 1' + } + } + $CheckExistingExclusion = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant + if ('Default AV Exclusion Policy' -in $CheckExistingExclusion.Name) { + "$($tenant): Exclusion Policy already exists. Skipping" + } else { + $ExclusionRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant -type POST -body $ExclusionBody + if ($ExclusionAssignTo -and $ExclusionAssignTo -ne 'none') { + $AssignBody = if ($ExclusionAssignTo -ne 'AllDevicesAndUsers') { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($ExclusionAssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' } + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($ExclusionRequest.id)')/assign" -tenantid $tenant -type POST -body $AssignBody + Write-LogMessage -headers $Headers -API $APINAME -tenant $($tenant) -message "Assigned Exclusion policy to $($ExclusionAssignTo)" -Sev 'Info' + } + "$($tenant): Successfully set Default AV Exclusion Policy settings" + } + } + } } catch { "Failed to add policy for $($tenant): $($_.Exception.Message)" - Write-LogMessage -headers $Request.Headers -API $APINAME -tenant $($tenant) -message "Failed adding policy $($DisplayName). Error: $($_.Exception.Message)" -Sev 'Error' + Write-LogMessage -headers $Headers -API $APINAME -tenant $($tenant) -message "Failed adding policy $($DisplayName). Error: $($_.Exception.Message)" -Sev 'Error' continue } From a8a64f8344e0c08ad7b421646bdc31a89bc7955d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 30 Jun 2025 20:53:24 +0200 Subject: [PATCH 037/125] Update to only run compliance part if the toggle was set in the frontend --- .../MEM/Invoke-AddDefenderDeployment.ps1 | 129 ++++++++---------- 1 file changed, 58 insertions(+), 71 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 index d0ca782f9754..5ccce144b576 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 @@ -21,43 +21,45 @@ Function Invoke-AddDefenderDeployment { $DefenderExclusions = $Request.Body.Exclusion $ASR = $Request.Body.ASR $EDR = $Request.Body.EDR - $results = foreach ($tenant in $Tenants) { + $Results = foreach ($tenant in $Tenants) { try { - $SettingsObject = @{ - id = 'fc780465-2017-40d4-a0c5-307022471b92' - androidEnabled = [bool]$Compliance.ConnectAndroid - iosEnabled = [bool]$Compliance.ConnectIos - windowsEnabled = [bool]$Compliance.Connectwindows - macEnabled = [bool]$Compliance.ConnectMac - partnerUnsupportedOsVersionBlocked = [bool]$Compliance.BlockunsupportedOS - partnerUnresponsivenessThresholdInDays = 7 - allowPartnerToCollectIOSApplicationMetadata = [bool]$Compliance.ConnectIosCompliance - allowPartnerToCollectIOSPersonalApplicationMetadata = [bool]$Compliance.ConnectIosCompliance - androidMobileApplicationManagementEnabled = [bool]$Compliance.ConnectAndroidCompliance - iosMobileApplicationManagementEnabled = [bool]$Compliance.appSync - microsoftDefenderForEndpointAttachEnabled = [bool]$true - } - $SettingsObj = $SettingsObject | ConvertTo-Json -Compress - try { - $ExistingSettings = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/fc780465-2017-40d4-a0c5-307022471b92' -tenantid $tenant + if ($Compliance) { + $SettingsObject = @{ + id = 'fc780465-2017-40d4-a0c5-307022471b92' + androidEnabled = [bool]$Compliance.ConnectAndroid + iosEnabled = [bool]$Compliance.ConnectIos + windowsEnabled = [bool]$Compliance.Connectwindows + macEnabled = [bool]$Compliance.ConnectMac + partnerUnsupportedOsVersionBlocked = [bool]$Compliance.BlockunsupportedOS + partnerUnresponsivenessThresholdInDays = 7 + allowPartnerToCollectIOSApplicationMetadata = [bool]$Compliance.ConnectIosCompliance + allowPartnerToCollectIOSPersonalApplicationMetadata = [bool]$Compliance.ConnectIosCompliance + androidMobileApplicationManagementEnabled = [bool]$Compliance.ConnectAndroidCompliance + iosMobileApplicationManagementEnabled = [bool]$Compliance.appSync + microsoftDefenderForEndpointAttachEnabled = [bool]$true + } + $SettingsObj = $SettingsObject | ConvertTo-Json -Compress + try { + $ExistingSettings = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/fc780465-2017-40d4-a0c5-307022471b92' -tenantid $tenant - # Check if any setting doesn't match - foreach ($key in $SettingsObject.Keys) { - if ($ExistingSettings.$key -ne $SettingsObject[$key]) { - $ExistingSettings = $false - break + # Check if any setting doesn't match + foreach ($key in $SettingsObject.Keys) { + if ($ExistingSettings.$key -ne $SettingsObject[$key]) { + $ExistingSettings = $false + break + } } + } catch { + $ExistingSettings = $false + } + if ($ExistingSettings) { + "Defender Intune Configuration already correct and active for $($tenant). Skipping" + } else { + $null = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/' -tenantid $tenant -type POST -body $SettingsObj -AsApp $true + "$($tenant): Successfully set Defender Compliance and Reporting settings. Please remember to enable the Intune Connector in the Defender portal." } - } catch { - $ExistingSettings = $false } - if ($ExistingSettings) { - "Defender Intune Configuration already correct and active for $($tenant). Skipping" - } else { - $null = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/' -tenantid $tenant -type POST -body $SettingsObj -AsApp $true - "$($tenant): Successfully set Defender Compliance and Reporting settings. Please remember to enable the Intune Connector in the Defender portal." - } if ($PolicySettings) { $Settings = switch ($PolicySettings) { @@ -260,68 +262,54 @@ Function Invoke-AddDefenderDeployment { } } # Exclusion Policy Section - $ExclusionToggle = $Request.Body.showExclusionPolicy - $ExcludedExtensions = $Request.Body.Exclusion.excludedExtensions - $ExcludedPaths = $Request.Body.Exclusion.excludedPaths - $ExcludedProcesses = $Request.Body.Exclusion.excludedProcesses - $ExclusionAssignTo = $Request.Body.Exclusion.AssignTo - if ($ExclusionToggle -and $DefenderExclusions) { - function Escape-ExclusionValue($val) { - $escaped = $val -replace '\\', '\\\\' # Escape backslashes - if ($escaped -match ' ' -and -not ($escaped -match '^".*"$')) { - $escaped = '"' + $escaped + '"' - } - return $escaped - } - $extArr = @() - if ($ExcludedExtensions) { - $extArr = $ExcludedExtensions | Where-Object { $_ -and $_.Trim() } | ForEach-Object { - @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = (Escape-ExclusionValue $_) } + if ($DefenderExclusions) { + $ExclusionAssignTo = $DefenderExclusions.AssignTo + if ($DefenderExclusions.excludedExtensions) { + $ExcludedExtensions = $DefenderExclusions.excludedExtensions | Where-Object { $_ -and $_.Trim() } | ForEach-Object { + @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = $_ } } } - $pathArr = @() - if ($ExcludedPaths) { - $pathArr = $ExcludedPaths | Where-Object { $_ -and $_.Trim() } | ForEach-Object { - @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = (Escape-ExclusionValue $_) } + if ($DefenderExclusions.excludedPaths) { + $ExcludedPaths = $DefenderExclusions.excludedPaths | Where-Object { $_ -and $_.Trim() } | ForEach-Object { + @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = $_ } } } - $procArr = @() - if ($ExcludedProcesses) { - $procArr = $ExcludedProcesses | Where-Object { $_ -and $_.Trim() } | ForEach-Object { - @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = (Escape-ExclusionValue $_) } + if ($DefenderExclusions.excludedProcesses) { + $ExcludedProcesses = $DefenderExclusions.excludedProcesses | Where-Object { $_ -and $_.Trim() } | ForEach-Object { + @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationStringSettingValue'; value = $_ } } } - $ExclusionSettings = @() - if ($extArr.Count -gt 0) { - $ExclusionSettings = $ExclusionSettings + @(@{ + $ExclusionSettings = [System.Collections.Generic.List[System.Object]]::new() + if ($ExcludedExtensions.Count -gt 0) { + $ExclusionSettings.Add(@{ id = '2' settingInstance = @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' settingDefinitionId = 'device_vendor_msft_policy_config_defender_excludedextensions' settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'c203725b-17dc-427b-9470-673a2ce9cd5e' } - simpleSettingCollectionValue = @($extArr) + simpleSettingCollectionValue = @($ExcludedExtensions) } }) } - if ($pathArr.Count -gt 0) { - $ExclusionSettings = $ExclusionSettings + @(@{ + if ($ExcludedPaths.Count -gt 0) { + $ExclusionSettings.Add(@{ id = '1' settingInstance = @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' settingDefinitionId = 'device_vendor_msft_policy_config_defender_excludedpaths' settingInstanceTemplateReference = @{ settingInstanceTemplateId = 'aaf04adc-c639-464f-b4a7-152e784092e8' } - simpleSettingCollectionValue = @($pathArr) + simpleSettingCollectionValue = @($ExcludedPaths) } }) } - if ($procArr.Count -gt 0) { - $ExclusionSettings = $ExclusionSettings + @(@{ + if ($ExcludedProcesses.Count -gt 0) { + $ExclusionSettings.Add(@{ id = '0' settingInstance = @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' settingDefinitionId = 'device_vendor_msft_policy_config_defender_excludedprocesses' settingInstanceTemplateReference = @{ settingInstanceTemplateId = '96b046ed-f138-4250-9ae0-b0772a93d16f' } - simpleSettingCollectionValue = @($procArr) + simpleSettingCollectionValue = @($ExcludedProcesses) } }) } @@ -329,7 +317,7 @@ Function Invoke-AddDefenderDeployment { $ExclusionBody = ConvertTo-Json -Depth 15 -Compress -InputObject @{ name = 'Default AV Exclusion Policy' displayName = 'Default AV Exclusion Policy' - settings = $ExclusionSettings + settings = @($ExclusionSettings) platforms = 'windows10' technologies = 'mdm,microsoftSense' templateReference = @{ @@ -347,7 +335,7 @@ Function Invoke-AddDefenderDeployment { if ($ExclusionAssignTo -and $ExclusionAssignTo -ne 'none') { $AssignBody = if ($ExclusionAssignTo -ne 'AllDevicesAndUsers') { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($ExclusionAssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' } $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($ExclusionRequest.id)')/assign" -tenantid $tenant -type POST -body $AssignBody - Write-LogMessage -headers $Headers -API $APINAME -tenant $($tenant) -message "Assigned Exclusion policy to $($ExclusionAssignTo)" -Sev 'Info' + Write-LogMessage -headers $Headers -API $APIName -tenant $tenant -message "Assigned Exclusion policy to $($ExclusionAssignTo)" -Sev 'Info' } "$($tenant): Successfully set Default AV Exclusion Policy settings" } @@ -355,18 +343,17 @@ Function Invoke-AddDefenderDeployment { } } catch { "Failed to add policy for $($tenant): $($_.Exception.Message)" - Write-LogMessage -headers $Headers -API $APINAME -tenant $($tenant) -message "Failed adding policy $($DisplayName). Error: $($_.Exception.Message)" -Sev 'Error' + Write-LogMessage -headers $Headers -API $APIName -tenant $tenant -message "Failed adding policy $($DisplayName). Error: $($_.Exception.Message)" -Sev 'Error' continue } } - $body = [pscustomobject]@{'Results' = @($results) } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $body + Body = @{'Results' = @($Results) } }) } From 634e27da27d6ddadfd272188c7562a839a48669a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 30 Jun 2025 22:07:33 -0400 Subject: [PATCH 038/125] catch invalid json parsing and provide initial values --- .../Users/Invoke-ListUserSettings.ps1 | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 index 901678b7b6ea..1cfbd218703c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ListUserSettings.ps1 @@ -17,15 +17,31 @@ function Invoke-ListUserSettings { try { $Table = Get-CippTable -tablename 'UserSettings' - $UserSettings = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'allUsers'" - if (!$UserSettings) { $UserSettings = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$Username'" } - $UserSettings = $UserSettings.JSON | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue + $UserSettings = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'UserSettings' and RowKey eq 'allUsers'" + if (!$UserSettings) { $UserSettings = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'UserSettings' and RowKey eq '$Username'" } + + try { + $UserSettings = $UserSettings.JSON | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue + } catch { + Write-Warning "Failed to convert UserSettings JSON: $($_.Exception.Message)" + $UserSettings = [pscustomobject]@{ + direction = 'ltr' + paletteMode = 'light' + currentTheme = @{ value = 'light'; label = 'light' } + pinNav = $true + showDevtools = $false + customBranding = @{ + colour = '#F77F00' + logo = $null + } + } + } #Get branding settings if ($UserSettings) { $brandingTable = Get-CippTable -tablename 'Config' - $BrandingSettings = Get-CIPPAzDataTableEntity @brandingTable -Filter "RowKey eq 'BrandingSettings'" + $BrandingSettings = Get-CIPPAzDataTableEntity @brandingTable -Filter "PartitionKey eq 'BrandingSettings' and RowKey eq 'BrandingSettings'" if ($BrandingSettings) { - $UserSettings | Add-Member -MemberType NoteProperty -Name 'BrandingSettings' -Value $BrandingSettings -Force | Out-Null + $UserSettings | Add-Member -MemberType NoteProperty -Name 'customBranding' -Value $BrandingSettings -Force | Out-Null } } $StatusCode = [HttpStatusCode]::OK From 2968fd31ae5e5f65421f1832584b500cba58856c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 1 Jul 2025 10:49:38 -0400 Subject: [PATCH 039/125] new permission output for menu limits cleanup test-cippaccess return a list of allowed permissions for assigned user roles --- .../Get-CippAllowedPermissions.ps1 | 182 ++++++++++++++++++ .../Public/Authentication/Test-CIPPAccess.ps1 | 41 ++-- .../CIPP/Core/Invoke-ExecGraphRequest.ps1 | 112 ----------- 3 files changed, 204 insertions(+), 131 deletions(-) create mode 100644 Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 delete mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecGraphRequest.ps1 diff --git a/Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 b/Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 new file mode 100644 index 000000000000..53ae48fb772a --- /dev/null +++ b/Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 @@ -0,0 +1,182 @@ +function Get-CippAllowedPermissions { + <# + .SYNOPSIS + Retrieves the allowed permissions for the current user. + + .DESCRIPTION + This function retrieves the allowed permissions for the current user based on their role and the configured permissions in the CIPP system. + For admin/superadmin users, permissions are computed from base role include/exclude rules. + For editor/readonly users, permissions start from base role and are restricted by custom roles. + + .PARAMETER UserRoles + Array of user roles to compute permissions for. + + .OUTPUTS + Returns a list of allowed permissions for the current user. + #> + + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string[]]$UserRoles + ) + + # Get all available permissions and base roles configuration + + $CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase + $CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent + $Version = (Get-Content -Path $CIPPRoot\version_latest.txt).trim() + $BaseRoles = Get-Content -Path $CIPPRoot\Config\cipp-roles.json | ConvertFrom-Json + $DefaultRoles = @('superadmin', 'admin', 'editor', 'readonly', 'anonymous', 'authenticated') + + $AllPermissionCacheTable = Get-CIPPTable -tablename 'cachehttppermissions' + $AllPermissionsRow = Get-CIPPAzDataTableEntity @AllPermissionCacheTable -Filter "PartitionKey eq 'HttpFunctions' and RowKey eq 'HttpFunctions' and Version eq '$($Version)'" + + if (-not $AllPermissionsRow) { + $AllPermissions = Get-CIPPHttpFunctions -ByRole | Select-Object -ExpandProperty Permission + $Entity = @{ + PartitionKey = 'HttpFunctions' + RowKey = 'HttpFunctions' + Version = [string]$Version + Permissions = [string]($AllPermissions | ConvertTo-Json -Compress) + } + Add-CIPPAzDataTableEntity @AllPermissionCacheTable -Entity $Entity -Force + } else { + $AllPermissions = $AllPermissionsRow.Permissions | ConvertFrom-Json + } + + $AllowedPermissions = [System.Collections.Generic.List[string]]::new() + + # Determine user's primary base role (highest priority first) + $BaseRole = $null + $PrimaryRole = $null + + if ($UserRoles -contains 'superadmin') { + $PrimaryRole = 'superadmin' + } elseif ($UserRoles -contains 'admin') { + $PrimaryRole = 'admin' + } elseif ($UserRoles -contains 'editor') { + $PrimaryRole = 'editor' + } elseif ($UserRoles -contains 'readonly') { + $PrimaryRole = 'readonly' + } + + if ($PrimaryRole) { + $BaseRole = $BaseRoles.PSObject.Properties | Where-Object { $_.Name -eq $PrimaryRole } | Select-Object -First 1 + } + + # Get custom roles (non-default roles) + $CustomRoles = $UserRoles | Where-Object { $DefaultRoles -notcontains $_ } + + # For admin and superadmin: Compute permissions from base role include/exclude rules + if ($PrimaryRole -in @('admin', 'superadmin')) { + Write-Information "Computing permissions for $PrimaryRole using base role rules" + + if ($BaseRole) { + # Start with all permissions and apply include/exclude rules + $BasePermissions = [System.Collections.Generic.List[string]]::new() + + # Apply include rules + foreach ($Include in $BaseRole.Value.include) { + $MatchingPermissions = $AllPermissions | Where-Object { $_ -like $Include } + foreach ($Permission in $MatchingPermissions) { + if ($BasePermissions -notcontains $Permission) { + $BasePermissions.Add($Permission) + } + } + } + + # Apply exclude rules + foreach ($Exclude in $BaseRole.Value.exclude) { + $ExcludedPermissions = $BasePermissions | Where-Object { $_ -like $Exclude } + foreach ($Permission in $ExcludedPermissions) { + $BasePermissions.Remove($Permission) + } + } + + foreach ($Permission in $BasePermissions) { + $AllowedPermissions.Add($Permission) + } + } + } + # For editor and readonly: Start with base role permissions and restrict with custom roles + elseif ($PrimaryRole -in @('editor', 'readonly')) { + Write-Information "Computing permissions for $PrimaryRole with custom role restrictions" + + if ($BaseRole) { + # Get base role permissions first + $BasePermissions = [System.Collections.Generic.List[string]]::new() + + # Apply include rules from base role + foreach ($Include in $BaseRole.Value.include) { + $MatchingPermissions = $AllPermissions | Where-Object { $_ -like $Include } + foreach ($Permission in $MatchingPermissions) { + if ($BasePermissions -notcontains $Permission) { + $BasePermissions.Add($Permission) + } + } + } + + # Apply exclude rules from base role + foreach ($Exclude in $BaseRole.Value.exclude) { + $ExcludedPermissions = $BasePermissions | Where-Object { $_ -like $Exclude } + foreach ($Permission in $ExcludedPermissions) { + $BasePermissions.Remove($Permission) | Out-Null + } + } + + # If custom roles exist, intersect with custom role permissions (restriction) + if ($CustomRoles.Count -gt 0) { + $CustomRolePermissions = [System.Collections.Generic.List[string]]::new() + + foreach ($CustomRole in $CustomRoles) { + try { + $RolePermissions = Get-CIPPRolePermissions -RoleName $CustomRole + foreach ($Permission in $RolePermissions.Permissions) { + if ($null -ne $Permission -and $Permission -is [string] -and $CustomRolePermissions -notcontains $Permission) { + $CustomRolePermissions.Add($Permission) + } + } + } catch { + Write-Warning "Failed to get permissions for custom role '$CustomRole': $($_.Exception.Message)" + } + } + + # Restrict base permissions to only those allowed by custom roles + $RestrictedPermissions = $BasePermissions | Where-Object { $CustomRolePermissions -contains $_ } + foreach ($Permission in $RestrictedPermissions) { + if ($null -ne $Permission -and $Permission -is [string]) { + $AllowedPermissions.Add($Permission) + } + } + } else { + # No custom roles, use base role permissions + foreach ($Permission in $BasePermissions) { + if ($null -ne $Permission -and $Permission -is [string]) { + $AllowedPermissions.Add($Permission) + } + } + } + } + } + # Handle users with only custom roles (no base role) + elseif ($CustomRoles.Count -gt 0) { + Write-Information 'Computing permissions for custom roles only' + + foreach ($CustomRole in $CustomRoles) { + try { + $RolePermissions = Get-CIPPRolePermissions -RoleName $CustomRole + foreach ($Permission in $RolePermissions.Permissions) { + if ($null -ne $Permission -and $Permission -is [string] -and $AllowedPermissions -notcontains $Permission) { + $AllowedPermissions.Add($Permission) + } + } + } catch { + Write-Warning "Failed to get permissions for custom role '$CustomRole': $($_.Exception.Message)" + } + } + } + + # Return sorted unique permissions + return ($AllowedPermissions | Sort-Object -Unique) +} diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index af8a7ff8dc2f..85652645ad8f 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -15,19 +15,6 @@ function Test-CIPPAccess { # Check help for role $APIRole = $Help.Role - if ($APIRole -eq 'Public') { - return $true - } - - # Get default roles from config - $CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase - $CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent - $BaseRoles = Get-Content -Path $CIPPRoot\Config\cipp-roles.json | ConvertFrom-Json - - if ($APIRole -eq 'Public') { - return $true - } - # Get default roles from config $CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase $CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent @@ -38,11 +25,6 @@ function Test-CIPPAccess { return $true } - # Get default roles from config - $CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase - $CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent - $BaseRoles = Get-Content -Path $CIPPRoot\Config\cipp-roles.json | ConvertFrom-Json - if ($Request.Headers.'x-ms-client-principal-idp' -eq 'aad' -and $Request.Headers.'x-ms-client-principal-name' -match '^[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}$') { $Type = 'APIClient' # Direct API Access @@ -91,6 +73,22 @@ function Test-CIPPAccess { $CustomRoles = @('cipp-api') Write-Information "API Access: AppId=$($Request.Headers.'x-ms-client-principal-name'), IP=$IPAddress" } + if ($Request.Params.CIPPEndpoint -eq 'me') { + $Permissions = Get-CippAllowedPermissions -UserRoles $CustomRoles + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = ( + @{ + 'clientPrincipal' = @{ + appId = $Request.Headers.'x-ms-client-principal-name' + appRole = $CustomRoles + } + 'permissions' = $Permissions + } | ConvertTo-Json -Depth 5) + }) + return + } + } else { $Type = 'User' $User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json @@ -103,9 +101,14 @@ function Test-CIPPAccess { #Write-Information ($User | ConvertTo-Json -Depth 5) # Return user permissions if ($Request.Params.CIPPEndpoint -eq 'me') { + $Permissions = Get-CippAllowedPermissions -UserRoles $User.userRoles Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = (@{ 'clientPrincipal' = $User } | ConvertTo-Json -Depth 5) + Body = ( + @{ + 'clientPrincipal' = $User + 'permissions' = $Permissions + } | ConvertTo-Json -Depth 5) }) return } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecGraphRequest.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecGraphRequest.ps1 deleted file mode 100644 index d06367943d64..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecGraphRequest.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -using namespace System.Net - -Function Invoke-ExecGraphRequest { - <# - .FUNCTIONALITY - Entrypoint - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $APIName = $Request.Params.CIPPEndpoint - $Headers = $Request.Headers - Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - Function ConvertTo-FlatObject { - # https://evotec.xyz/powershell-converting-advanced-object-to-flat-object/ - MIT License - [CmdletBinding()] - Param ( - [Parameter(ValueFromPipeLine)][Object[]]$Objects, - [String]$Separator = '.', - [ValidateSet('', 0, 1)]$Base = 1, - [int]$Depth = 5, - [Parameter(DontShow)][String[]]$Path, - [Parameter(DontShow)][System.Collections.IDictionary] $OutputObject - ) - Begin { - $InputObjects = [System.Collections.Generic.List[Object]]::new() - } - Process { - foreach ($O in $Objects) { - $InputObjects.Add($O) - } - } - End { - If ($PSBoundParameters.ContainsKey('OutputObject')) { - $Object = $InputObjects[0] - $Iterate = [ordered] @{} - if ($null -eq $Object) { - #Write-Verbose -Message "ConvertTo-FlatObject - Object is null" - } elseif ($Object.GetType().Name -in 'String', 'DateTime', 'TimeSpan', 'Version', 'Enum') { - $Object = $Object.ToString() - } elseif ($Depth) { - $Depth-- - If ($Object -is [System.Collections.IDictionary]) { - $Iterate = $Object - } elseif ($Object -is [Array] -or $Object -is [System.Collections.IEnumerable]) { - $i = $Base - foreach ($Item in $Object.GetEnumerator()) { - $Iterate["$i"] = $Item - $i += 1 - } - } else { - foreach ($Prop in $Object.PSObject.Properties) { - if ($Prop.IsGettable) { - $Iterate["$($Prop.Name)"] = $Object.$($Prop.Name) - } - } - } - } - If ($Iterate.Keys.Count) { - foreach ($Key in $Iterate.Keys) { - ConvertTo-FlatObject -Objects @(, $Iterate["$Key"]) -Separator $Separator -Base $Base -Depth $Depth -Path ($Path + $Key) -OutputObject $OutputObject - } - } else { - $Property = $Path -Join $Separator - $OutputObject[$Property] = $Object - } - } elseif ($InputObjects.Count -gt 0) { - foreach ($ItemObject in $InputObjects) { - $OutputObject = [ordered]@{} - ConvertTo-FlatObject -Objects @(, $ItemObject) -Separator $Separator -Base $Base -Depth $Depth -Path $Path -OutputObject $OutputObject - [PSCustomObject] $OutputObject - } - } - } - } - $TenantFilter = $Request.Query.TenantFilter - try { - if ($TenantFilter -ne 'AllTenants') { - $RawGraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/$($Request.Query.Endpoint)" -tenantid $TenantFilter -NoPagination [boolean]$Request.query.DisablePagination -ComplexFilter - } else { - $RawGraphRequest = Get-Tenants | ForEach-Object -Parallel { - Import-Module '.\Modules\AzBobbyTables' - Import-Module '.\Modules\CIPPCore' - try { - $DefaultDomainName = $_.defaultDomainName - $TenantName = $_.displayName - New-GraphGetRequest -uri "https://graph.microsoft.com/beta/$($using:Request.Query.Endpoint)" -tenantid $DefaultDomainName -NoPagination [boolean]$using:Request.query.DisablePagination -ComplexFilter | Select-Object @{ - label = 'Tenant' - expression = { $TenantName } - }, * - } catch { - continue - } - } - - } - $GraphRequest = $RawGraphRequest | Where-Object -Property '@odata.context' -EQ $null | ConvertTo-FlatObject - $StatusCode = [HttpStatusCode]::OK - } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - $StatusCode = [HttpStatusCode]::Forbidden - $GraphRequest = $ErrorMessage - } - - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = $StatusCode - Body = @($GraphRequest) - }) - -} From 643dfcb0a8b6d8c3e3b49b2f9a15f14d531a6362 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 1 Jul 2025 12:02:40 -0400 Subject: [PATCH 040/125] Update Get-CippAllowedPermissions.ps1 --- .../Public/Authentication/Get-CippAllowedPermissions.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 b/Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 index 53ae48fb772a..0f11f47d8684 100644 --- a/Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Get-CippAllowedPermissions.ps1 @@ -90,7 +90,7 @@ function Get-CippAllowedPermissions { foreach ($Exclude in $BaseRole.Value.exclude) { $ExcludedPermissions = $BasePermissions | Where-Object { $_ -like $Exclude } foreach ($Permission in $ExcludedPermissions) { - $BasePermissions.Remove($Permission) + $BasePermissions.Remove($Permission) | Out-Null } } From 5da7856bd657716e8bfd8c383faf781ea3e979a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 1 Jul 2025 19:55:08 +0200 Subject: [PATCH 041/125] Update role in Invoke-ExecCSPLicense to Tenant.Directory.ReadWrite --- Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 index fc6ce44569e4..8c2c46712b11 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 @@ -5,7 +5,7 @@ Function Invoke-ExecCSPLicense { .FUNCTIONALITY Entrypoint .ROLE - Tenant.Directory.Read + Tenant.Directory.ReadWrite #> [CmdletBinding()] param($Request, $TriggerMetadata) From c7374827d3378736cc98b45c1adc396fd96d37e4 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 1 Jul 2025 17:33:10 -0400 Subject: [PATCH 042/125] fix graph presets --- .../Tools/Invoke-ExecGraphExplorerPreset.ps1 | 21 ++++++++++++------- .../Invoke-ListGraphExplorerPresets.ps1 | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 index 4b068585ffc6..9828d33ba5ef 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 @@ -13,10 +13,9 @@ function Invoke-ExecGraphExplorerPreset { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - #UNDOREPLACE $Username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Headers.'x-ms-client-principal')) | ConvertFrom-Json).userDetails - $Action = $Request.Body.Action ?? '' + $Action = $Request.Body.action ?? '' Write-Information ($Request.Body | ConvertTo-Json -Depth 10) @@ -48,8 +47,10 @@ function Invoke-ExecGraphExplorerPreset { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode Body = @{ - Results = $Message - Success = $false + Results = @{ + resultText = $Message + state = 'error' + } } }) return @@ -61,8 +62,10 @@ function Invoke-ExecGraphExplorerPreset { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode Body = @{ - Results = $Message - Success = $false + Results = @{ + resultText = $Message + state = 'error' + } } }) return @@ -111,8 +114,10 @@ function Invoke-ExecGraphExplorerPreset { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode Body = @{ - Results = $Message - Success = $Success + Results = @{ + resultText = $Message + state = if ($Success) { 'success' } else { 'error' } + } } }) } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphExplorerPresets.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphExplorerPresets.ps1 index 7f372f03a1e7..60801c2e3afb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphExplorerPresets.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphExplorerPresets.ps1 @@ -14,8 +14,7 @@ function Invoke-ListGraphExplorerPresets { $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - # Interact with query parameters or the body of the request. - $Username = $Request.Headers['x-ms-client-principal-name'] + $Username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Headers.'x-ms-client-principal')) | ConvertFrom-Json).userDetails try { $Table = Get-CIPPTable -TableName 'GraphPresets' @@ -26,6 +25,7 @@ function Invoke-ListGraphExplorerPresets { name = $Preset.name IsShared = $Preset.IsShared IsMyPreset = $Preset.Owner -eq $Username + Owner = $Preset.Owner params = (ConvertFrom-Json -InputObject $Preset.Params) } } From a998f5543be6c9d82a8136d6e49d5ec979261930 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:52:03 +0200 Subject: [PATCH 043/125] update execedittemplate --- .../HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 index b1367d6c8076..a7e6f3f176df 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-ExecEditTemplate { +function Invoke-ExecEditTemplate { <# .FUNCTIONALITY Entrypoint,AnyTenant @@ -18,7 +18,7 @@ Function Invoke-ExecEditTemplate { $Table = Get-CippTable -tablename 'templates' $guid = $request.body.guid $JSON = ConvertTo-Json -Compress -Depth 100 -InputObject ($request.body | Select-Object * -ExcludeProperty GUID) - $Type = $request.Body.Type + $Type = $request.query.Type if ($Type -eq 'IntuneTemplate') { Write-Host 'Intune Template' @@ -28,7 +28,6 @@ Function Invoke-ExecEditTemplate { Set-CIPPIntuneTemplate -RawJSON $RawJSON -GUID $GUID -DisplayName $Request.body.displayName -Description $Request.body.description -templateType $OriginalTemplate.Type -Headers $Request.Headers } else { $Table.Force = $true - Add-CIPPAzDataTableEntity @Table -Entity @{ JSON = "$JSON" RowKey = "$GUID" From 0e1cd7993dd9a6a4be3c291599017278e90a1c6c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:55:53 +0200 Subject: [PATCH 044/125] add openapi specs --- Tools/cipp-openapispec.json | 1756 +++++++++++++++++++++ Tools/endpoint-openapispec.json | 2263 ++++++++++++++++++++++++++++ Tools/identity-openapispec.json | 1774 ++++++++++++++++++++++ Tools/security-openapispec.json | 891 +++++++++++ Tools/teams-share-openapispec.json | 749 +++++++++ Tools/tenant-openapispec.json | 1622 ++++++++++++++++++++ Tools/tools-openapispec.json | 665 ++++++++ 7 files changed, 9720 insertions(+) create mode 100644 Tools/cipp-openapispec.json create mode 100644 Tools/endpoint-openapispec.json create mode 100644 Tools/identity-openapispec.json create mode 100644 Tools/security-openapispec.json create mode 100644 Tools/teams-share-openapispec.json create mode 100644 Tools/tenant-openapispec.json create mode 100644 Tools/tools-openapispec.json diff --git a/Tools/cipp-openapispec.json b/Tools/cipp-openapispec.json new file mode 100644 index 000000000000..0efd9e314077 --- /dev/null +++ b/Tools/cipp-openapispec.json @@ -0,0 +1,1756 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CIPP API", + "description": "API for Cloud Identity and Policy Platform (CIPP)", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/api", + "description": "CIPP API Server" + } + ], + "components": { + "schemas": { + "StandardResponse": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "description": "The results of the operation" + } + } + } + } + }, + "paths": { + "/ListScheduledItems": { + "get": { + "summary": "List Scheduled Items", + "description": "Retrieves a list of scheduled items", + "tags": [ + "Scheduler" + ], + "parameters": [ + { + "name": "ShowHidden", + "in": "query", + "description": "Whether to show hidden system jobs", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "Name", + "in": "query", + "description": "Filter by job name", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Scheduled items retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ExecutedTime": { + "type": "string", + "description": "When the job was last executed" + }, + "TaskState": { + "type": "string", + "description": "Current state of the task", + "enum": ["Running", "Planned", "Failed", "Completed"] + }, + "Tenant": { + "type": "string", + "description": "The tenant the job is for" + }, + "Name": { + "type": "string", + "description": "Name of the scheduled job" + }, + "ScheduledTime": { + "type": "integer", + "description": "When the job is scheduled to run (Unix timestamp)" + }, + "Command": { + "type": "string", + "description": "The command to execute" + }, + "Parameters": { + "type": "object", + "description": "Parameters for the command" + }, + "PostExecution": { + "type": "string", + "description": "Actions to take after execution" + }, + "Recurrence": { + "type": "string", + "description": "How often the job recurs" + }, + "Results": { + "type": "string", + "description": "Results of the job execution" + }, + "RowKey": { + "type": "string", + "description": "Unique identifier for the job" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemoveScheduledItem": { + "post": { + "summary": "Remove Scheduled Item", + "description": "Removes a scheduled item", + "tags": [ + "Scheduler" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "description": "The RowKey of the scheduled item to remove" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled item removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddScheduledItem": { + "post": { + "summary": "Add Scheduled Item", + "description": "Adds a new scheduled item", + "tags": [ + "Scheduler" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "tenantFilter", + "Name", + "command", + "ScheduledTime", + "Recurrence" + ], + "properties": { + "tenantFilter": { + "type": "object", + "description": "The tenant to run the job for", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID or domain" + }, + "label": { + "type": "string", + "description": "Tenant display name" + } + } + }, + "Name": { + "type": "string", + "description": "Name of the scheduled job" + }, + "command": { + "type": "object", + "description": "The command to execute", + "properties": { + "label": { + "type": "string", + "description": "Command display name" + }, + "value": { + "type": "string", + "description": "Command value" + } + } + }, + "ScheduledTime": { + "type": "integer", + "description": "When the job is scheduled to run (Unix timestamp)" + }, + "Recurrence": { + "type": "string", + "description": "How often the job recurs", + "enum": ["0", "1d", "7d", "30d", "365d"] + }, + "parameters": { + "type": "object", + "description": "Parameters for the command" + }, + "RawJsonParameters": { + "type": "string", + "description": "JSON string of parameters for advanced configuration" + }, + "postExecution": { + "type": "array", + "description": "Actions to take after execution", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string", + "enum": ["Webhook", "Email", "PSA"] + } + } + } + }, + "RowKey": { + "type": "string", + "description": "Unique identifier for the job (for editing existing jobs)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled item added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListFunctionParameters": { + "get": { + "summary": "List Function Parameters", + "description": "Retrieves a list of available functions and their parameters", + "tags": [ + "Scheduler" + ], + "parameters": [ + { + "name": "Module", + "in": "query", + "description": "The module to list functions for", + "required": true, + "schema": { + "type": "string", + "enum": ["CIPPCore"] + } + } + ], + "responses": { + "200": { + "description": "Function parameters retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Function": { + "type": "string", + "description": "Function name" + }, + "Synopsis": { + "type": "string", + "description": "Function description" + }, + "Parameters": { + "type": "array", + "description": "Function parameters", + "items": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Parameter name" + }, + "Type": { + "type": "string", + "description": "Parameter type" + }, + "Required": { + "type": "boolean", + "description": "Whether the parameter is required" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListTenants": { + "get": { + "summary": "List Tenants", + "description": "Retrieves a list of tenants", + "tags": [ + "Tenants" + ], + "parameters": [ + { + "name": "AllTenantSelector", + "in": "query", + "description": "Whether to include the 'All Tenants' option", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Tenants retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "customerId": { + "type": "string", + "description": "Tenant ID" + }, + "defaultDomainName": { + "type": "string", + "description": "Default domain name" + }, + "displayName": { + "type": "string", + "description": "Display name" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + }, + "post": { + "summary": "Clear Tenant Cache", + "description": "Clears the tenant cache", + "tags": [ + "Tenants" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ClearCache" + ], + "properties": { + "ClearCache": { + "type": "boolean", + "description": "Whether to clear the cache" + }, + "TenantsOnly": { + "type": "boolean", + "description": "Whether to only clear the tenant cache" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Cache cleared successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecPasswordConfig": { + "get": { + "summary": "Get Password Configuration", + "description": "Retrieves the current password configuration", + "tags": [ + "Settings" + ], + "parameters": [ + { + "name": "list", + "in": "query", + "description": "Whether to list the password configuration", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Password configuration retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "properties": { + "passwordType": { + "type": "string", + "description": "The type of password generation", + "enum": ["Classic", "Correct-Battery-Horse"] + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + }, + "post": { + "summary": "Update Password Configuration", + "description": "Updates the password configuration", + "tags": [ + "Settings" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "passwordType" + ], + "properties": { + "passwordType": { + "type": "string", + "description": "The type of password generation", + "enum": ["Classic", "Correct-Battery-Horse"] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Password configuration updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecListBackup": { + "get": { + "summary": "List Backups", + "description": "Retrieves a list of backups", + "tags": [ + "Backup" + ], + "parameters": [ + { + "name": "NameOnly", + "in": "query", + "description": "Whether to only return backup names", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "BackupName", + "in": "query", + "description": "The name of a specific backup to retrieve", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Backups retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "BackupName": { + "type": "string", + "description": "Name of the backup" + }, + "Timestamp": { + "type": "string", + "description": "When the backup was created" + }, + "Backup": { + "type": "string", + "description": "The backup data (JSON string)" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecRunBackup": { + "post": { + "summary": "Run Backup", + "description": "Creates a new backup", + "tags": [ + "Backup" + ], + "responses": { + "200": { + "description": "Backup created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecSetCIPPAutoBackup": { + "post": { + "summary": "Set Automatic Backup", + "description": "Enables or disables automatic backups", + "tags": [ + "Backup" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "Enabled" + ], + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether to enable automatic backups" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Automatic backup setting updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecRestoreBackup": { + "post": { + "summary": "Restore Backup", + "description": "Restores a backup", + "tags": [ + "Backup" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "required": [ + "BackupName" + ], + "properties": { + "BackupName": { + "type": "string", + "description": "The name of the backup to restore" + } + } + }, + { + "type": "object", + "description": "The backup data to restore" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Backup restored successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/Listlogs": { + "get": { + "summary": "List Logs", + "description": "Retrieves a list of logs", + "tags": [ + "Logs" + ], + "parameters": [ + { + "name": "DateFilter", + "in": "query", + "description": "Filter logs by date (YYYYMMDD format)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Filter", + "in": "query", + "description": "Whether to apply filtering", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Logs retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "DateTime": { + "type": "string", + "description": "When the log entry was created" + }, + "Tenant": { + "type": "string", + "description": "The tenant the log entry is for" + }, + "TenantID": { + "type": "string", + "description": "The tenant ID" + }, + "User": { + "type": "string", + "description": "The user who performed the action" + }, + "Message": { + "type": "string", + "description": "The log message" + }, + "API": { + "type": "string", + "description": "The API that was called" + }, + "Severity": { + "type": "string", + "description": "The severity of the log entry" + }, + "AppId": { + "type": "string", + "description": "The application ID" + }, + "IP": { + "type": "string", + "description": "The IP address of the user" + }, + "LogData": { + "type": "string", + "description": "Additional log data" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListExtensionsConfig": { + "get": { + "summary": "List Extensions Configuration", + "description": "Retrieves the configuration for extensions", + "tags": [ + "Integrations" + ], + "responses": { + "200": { + "description": "Extensions configuration retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether the extension is enabled" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecExtensionsConfig": { + "post": { + "summary": "Update Extensions Configuration", + "description": "Updates the configuration for extensions", + "tags": [ + "Integrations" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether the extension is enabled" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Extensions configuration updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecExtensionTest": { + "get": { + "summary": "Test Extension", + "description": "Tests an extension", + "tags": [ + "Integrations" + ], + "parameters": [ + { + "name": "extensionName", + "in": "query", + "description": "The name of the extension to test", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Extension test completed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecExtensionSync": { + "get": { + "summary": "Sync Extension", + "description": "Syncs an extension", + "tags": [ + "Integrations" + ], + "parameters": [ + { + "name": "Extension", + "in": "query", + "description": "The name of the extension to sync", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Extension sync completed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecExtensionMapping": { + "get": { + "summary": "Get Extension Mapping", + "description": "Retrieves the mapping for an extension", + "tags": [ + "Integrations" + ], + "parameters": [ + { + "name": "List", + "in": "query", + "description": "The name of the extension to get mapping for", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Extension mapping retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Mappings": { + "type": "array", + "description": "The mappings for the extension", + "items": { + "type": "object", + "properties": { + "TenantId": { + "type": "string", + "description": "The tenant ID" + }, + "Tenant": { + "type": "string", + "description": "The tenant name" + }, + "IntegrationName": { + "type": "string", + "description": "The integration name" + }, + "IntegrationId": { + "type": "string", + "description": "The integration ID" + }, + "TenantDomain": { + "type": "string", + "description": "The tenant domain" + } + } + } + }, + "Companies": { + "type": "array", + "description": "The companies for the extension", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The company name" + }, + "value": { + "type": "string", + "description": "The company ID" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + }, + "post": { + "summary": "Update Extension Mapping", + "description": "Updates the mapping for an extension", + "tags": [ + "Integrations" + ], + "parameters": [ + { + "name": "AddMapping", + "in": "query", + "description": "The name of the extension to update mapping for", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "AutoMapping", + "in": "query", + "description": "The name of the extension to auto-map", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TenantId": { + "type": "string", + "description": "The tenant ID" + }, + "Tenant": { + "type": "string", + "description": "The tenant name" + }, + "IntegrationName": { + "type": "string", + "description": "The integration name" + }, + "IntegrationId": { + "type": "string", + "description": "The integration ID" + }, + "TenantDomain": { + "type": "string", + "description": "The tenant domain" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Extension mapping updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListExtensionSync": { + "get": { + "summary": "List Extension Sync Jobs", + "description": "Retrieves a list of extension sync jobs", + "tags": [ + "Integrations" + ], + "responses": { + "200": { + "description": "Extension sync jobs retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Tenant": { + "type": "string", + "description": "The tenant the job is for" + }, + "SyncType": { + "type": "string", + "description": "The type of sync" + }, + "Task": { + "type": "string", + "description": "The task being performed" + }, + "ScheduledTime": { + "type": "string", + "description": "When the job is scheduled to run" + }, + "ExecutedTime": { + "type": "string", + "description": "When the job was last executed" + }, + "LastRun": { + "type": "string", + "description": "When the job was last run" + }, + "RepeatsEvery": { + "type": "string", + "description": "How often the job repeats" + }, + "Results": { + "type": "string", + "description": "Results of the job execution" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListExoRequest": { + "post": { + "summary": "List Exchange Cmdlets", + "description": "Retrieves a list of Exchange cmdlets", + "tags": [ + "Exchange" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "required": [ + "availableCmdlets", + "tenantFilter" + ], + "properties": { + "availableCmdlets": { + "type": "boolean", + "description": "Whether to list available cmdlets" + }, + "tenantFilter": { + "type": "string", + "description": "The tenant to list cmdlets for" + }, + "compliance": { + "type": "boolean", + "description": "Whether to use compliance endpoints" + }, + "asApp": { + "type": "boolean", + "description": "Whether to execute as app" + } + } + }, + { + "type": "object", + "required": [ + "cmdlet", + "cmdParams", + "tenantFilter" + ], + "properties": { + "cmdlet": { + "type": "string", + "description": "The cmdlet to execute" + }, + "cmdParams": { + "type": "object", + "description": "The parameters for the cmdlet" + }, + "compliance": { + "type": "boolean", + "description": "Whether to use compliance endpoints" + }, + "tenantFilter": { + "type": "string", + "description": "The tenant to execute the cmdlet for" + } + } + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Exchange cmdlets retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Cmdlet": { + "type": "string", + "description": "The cmdlet name" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecAzBobbyTables": { + "post": { + "summary": "Execute Azure Table Operations", + "description": "Executes operations on Azure tables", + "tags": [ + "Azure Tables" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "FunctionName" + ], + "properties": { + "FunctionName": { + "type": "string", + "description": "The function to execute", + "enum": [ + "Get-AzDataTable", + "Get-AzDataTableEntity", + "Remove-AzDataTableEntity", + "New-AzDataTable", + "Remove-AzDataTable", + "Add-AzDataTableEntity" + ] + }, + "TableName": { + "type": "string", + "description": "The name of the table" + }, + "Parameters": { + "type": "object", + "description": "The parameters for the function" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Azure table operation executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecCippFunction": { + "post": { + "summary": "Execute CIPP Function", + "description": "Executes a CIPP function", + "tags": [ + "CIPP Functions" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "FunctionName" + ], + "properties": { + "FunctionName": { + "type": "string", + "description": "The function to execute", + "enum": [ + "Get-CIPPTimerFunctions", + "!Get-CIPPTimerFunctions" + ] + }, + "Parameters": { + "type": "object", + "description": "The parameters for the function" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "CIPP function executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecApiClient": { + "get": { + "summary": "Get API Client Information", + "description": "Retrieves information about API clients", + "tags": [ + "API Clients" + ], + "parameters": [ + { + "name": "Action", + "in": "query", + "description": "The action to perform", + "required": true, + "schema": { + "type": "string", + "enum": [ + "GetAzureConfiguration", + "List", + "ListAvailable" + ] + } + } + ], + "responses": { + "200": { + "description": "API client information retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "oneOf": [ + { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether Microsoft Authentication is enabled" + }, + "ApiUrl": { + "type": "string", + "description": "The API URL" + }, + "TenantID": { + "type": "string", + "description": "The tenant ID" + }, + "ClientIDs": { + "type": "array", + "description": "The client IDs", + "items": { + "type": "string" + } + } + } + }, + { + "type": "array", + "items": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether the client is enabled" + }, + "AppName": { + "type": "string", + "description": "The application name" + }, + "ClientId": { + "type": "string", + "description": "The client ID" + }, + "Role": { + "type": "string", + "description": "The client role" + }, + "IPRange": { + "type": "array", + "description": "The allowed IP ranges", + "items": { + "type": "string" + } + } + } + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + }, + "post": { + "summary": "Update API Client", + "description": "Updates an API client", + "tags": [ + "API Clients" + ], + "parameters": [ + { + "name": "action", + "in": "query", + "description": "The action to perform", + "required": false, + "schema": { + "type": "string", + "enum": [ + "SaveToAzure" + ] + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "Action" + ], + "properties": { + "Action": { + "type": "string", + "description": "The action to perform", + "enum": [ + "AddUpdate", + "ResetSecret", + "Delete" + ] + }, + "ClientId": { + "type": "string", + "description": "The client ID" + }, + "AppName": { + "type": "string", + "description": "The application name" + }, + "Role": { + "type": "string", + "description": "The client role" + }, + "IPRange": { + "type": "array", + "description": "The allowed IP ranges", + "items": { + "type": "string" + } + }, + "Enabled": { + "type": "boolean", + "description": "Whether the client is enabled" + }, + "RemoveAppReg": { + "type": "boolean", + "description": "Whether to remove the app registration" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "API client updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListCustomRole": { + "get": { + "summary": "List Custom Roles", + "description": "Retrieves a list of custom roles", + "tags": [ + "API Clients" + ], + "responses": { + "200": { + "description": "Custom roles retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "RowKey": { + "type": "string", + "description": "The role name" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + } + } +} \ No newline at end of file diff --git a/Tools/endpoint-openapispec.json b/Tools/endpoint-openapispec.json new file mode 100644 index 000000000000..7fb45816d2ce --- /dev/null +++ b/Tools/endpoint-openapispec.json @@ -0,0 +1,2263 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CIPP Endpoint API", + "description": "API endpoints for the Endpoint section of Cloud Identity and Policy Platform (CIPP)", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/api", + "description": "CIPP API Server" + } + ], + "components": { + "schemas": { + "StandardResponse": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "description": "The results of the operation" + } + } + } + } + }, + "paths": { + "/ListApps": { + "get": { + "summary": "List Applications", + "description": "Retrieves a list of applications", + "tags": [ + "Applications" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Applications retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The application ID" + }, + "displayName": { + "type": "string", + "description": "The display name of the application" + }, + "publishingState": { + "type": "string", + "description": "The publishing state of the application" + }, + "installCommandLine": { + "type": "string", + "description": "The install command line" + }, + "uninstallCommandLine": { + "type": "string", + "description": "The uninstall command line" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecAssignApp": { + "post": { + "summary": "Assign Application", + "description": "Assigns an application to users or devices", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "AssignTo", + "ID" + ], + "properties": { + "AssignTo": { + "type": "string", + "description": "Who to assign the application to", + "enum": ["AllUsers", "AllDevices", "Both", "customGroup"] + }, + "ID": { + "type": "string", + "description": "The application ID" + }, + "customGroup": { + "type": "string", + "description": "Custom group names (if AssignTo is customGroup)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Application assigned successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemoveApp": { + "post": { + "summary": "Remove Application", + "description": "Removes an application", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "string", + "description": "The application ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Application removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListApplicationQueue": { + "get": { + "summary": "List Application Queue", + "description": "Retrieves a list of applications in the queue", + "tags": [ + "Applications" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Application queue retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The queue item ID" + }, + "applicationName": { + "type": "string", + "description": "The application name" + }, + "status": { + "type": "string", + "description": "The status of the queue item" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemoveQueuedApp": { + "post": { + "summary": "Remove Queued Application", + "description": "Removes an application from the queue", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "string", + "description": "The queue item ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Queued application removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecAppUpload": { + "post": { + "summary": "Execute Application Upload", + "description": "Executes the application upload process", + "tags": [ + "Applications" + ], + "responses": { + "200": { + "description": "Application upload executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddMSPApp": { + "post": { + "summary": "Add MSP Application", + "description": "Adds an MSP vendor application", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "selectedTenants", + "rmmname", + "displayName" + ], + "properties": { + "selectedTenants": { + "type": "array", + "description": "The tenants to add the application to", + "items": { + "type": "object", + "properties": { + "defaultDomainName": { + "type": "string", + "description": "The tenant domain name" + }, + "customerId": { + "type": "string", + "description": "The tenant ID" + } + } + } + }, + "rmmname": { + "type": "object", + "description": "The RMM tool", + "properties": { + "value": { + "type": "string", + "enum": ["datto", "syncro", "immy", "huntress", "automate", "cwcommand"] + } + } + }, + "displayName": { + "type": "string", + "description": "The display name of the application" + }, + "params": { + "type": "object", + "description": "Parameters specific to the RMM tool" + }, + "AssignTo": { + "type": "string", + "description": "Who to assign the application to", + "enum": ["On", "allLicensedUsers", "AllDevices", "AllDevicesAndUsers", "customGroup"] + }, + "customGroup": { + "type": "string", + "description": "Custom group names (if AssignTo is customGroup)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "MSP application added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddStoreApp": { + "post": { + "summary": "Add Store Application", + "description": "Adds a store application", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "selectedTenants", + "packagename", + "applicationName" + ], + "properties": { + "selectedTenants": { + "type": "array", + "description": "The tenants to add the application to", + "items": { + "type": "object", + "properties": { + "defaultDomainName": { + "type": "string", + "description": "The tenant domain name" + }, + "customerId": { + "type": "string", + "description": "The tenant ID" + } + } + } + }, + "packagename": { + "type": "string", + "description": "The package identifier" + }, + "applicationName": { + "type": "string", + "description": "The application name" + }, + "description": { + "type": "string", + "description": "The application description" + }, + "InstallationIntent": { + "type": "boolean", + "description": "Whether to mark for uninstallation" + }, + "AssignTo": { + "type": "string", + "description": "Who to assign the application to", + "enum": ["On", "allLicensedUsers", "AllDevices", "AllDevicesAndUsers", "customGroup"] + }, + "customGroup": { + "type": "string", + "description": "Custom group names (if AssignTo is customGroup)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Store application added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddwinGetApp": { + "post": { + "summary": "Add WinGet Application", + "description": "Adds a WinGet application", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "selectedTenants", + "packagename", + "applicationName" + ], + "properties": { + "selectedTenants": { + "type": "array", + "description": "The tenants to add the application to", + "items": { + "type": "object", + "properties": { + "defaultDomainName": { + "type": "string", + "description": "The tenant domain name" + }, + "customerId": { + "type": "string", + "description": "The tenant ID" + } + } + } + }, + "packagename": { + "type": "string", + "description": "The package identifier" + }, + "applicationName": { + "type": "string", + "description": "The application name" + }, + "description": { + "type": "string", + "description": "The application description" + }, + "InstallationIntent": { + "type": "boolean", + "description": "Whether to mark for uninstallation" + }, + "AssignTo": { + "type": "string", + "description": "Who to assign the application to", + "enum": ["On", "allLicensedUsers", "AllDevices", "AllDevicesAndUsers", "customGroup"] + }, + "customGroup": { + "type": "string", + "description": "Custom group names (if AssignTo is customGroup)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "WinGet application added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddChocoApp": { + "post": { + "summary": "Add Chocolatey Application", + "description": "Adds a Chocolatey application", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "selectedTenants", + "packagename", + "applicationName" + ], + "properties": { + "selectedTenants": { + "type": "array", + "description": "The tenants to add the application to", + "items": { + "type": "object", + "properties": { + "defaultDomainName": { + "type": "string", + "description": "The tenant domain name" + }, + "customerId": { + "type": "string", + "description": "The tenant ID" + } + } + } + }, + "packagename": { + "type": "string", + "description": "The package name" + }, + "applicationName": { + "type": "string", + "description": "The application name" + }, + "description": { + "type": "string", + "description": "The application description" + }, + "customRepo": { + "type": "string", + "description": "Custom repository URL" + }, + "InstallAsSystem": { + "type": "boolean", + "description": "Whether to install as system" + }, + "DisableRestart": { + "type": "boolean", + "description": "Whether to disable restart" + }, + "InstallationIntent": { + "type": "boolean", + "description": "Whether to mark for uninstallation" + }, + "AssignTo": { + "type": "string", + "description": "Who to assign the application to", + "enum": ["On", "allLicensedUsers", "AllDevices", "AllDevicesAndUsers", "customGroup"] + }, + "customGroup": { + "type": "string", + "description": "Custom group names (if AssignTo is customGroup)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Chocolatey application added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddOfficeApp": { + "post": { + "summary": "Add Office Application", + "description": "Adds a Microsoft Office application", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "selectedTenants", + "updateChannel", + "languages" + ], + "properties": { + "selectedTenants": { + "type": "array", + "description": "The tenants to add the application to", + "items": { + "type": "object", + "properties": { + "defaultDomainName": { + "type": "string", + "description": "The tenant domain name" + }, + "customerId": { + "type": "string", + "description": "The tenant ID" + } + } + } + }, + "excludedApps": { + "type": "array", + "description": "Apps to exclude from the installation", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "enum": ["access", "excel", "oneNote", "outlook", "powerPoint", "teams", "word", "lync", "bing"] + } + } + } + }, + "updateChannel": { + "type": "object", + "description": "The update channel", + "properties": { + "value": { + "type": "string", + "enum": ["current", "firstReleaseCurrent", "monthlyEnterprise", "deferred", "firstReleaseDeferred"] + } + } + }, + "languages": { + "type": "array", + "description": "Languages to install", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Language tag" + } + } + } + }, + "SharedComputerActivation": { + "type": "boolean", + "description": "Whether to use shared computer activation" + }, + "arch": { + "type": "boolean", + "description": "Whether to use 64-bit" + }, + "RemoveVersions": { + "type": "boolean", + "description": "Whether to remove other versions" + }, + "AcceptLicense": { + "type": "boolean", + "description": "Whether to accept the license" + }, + "AssignTo": { + "type": "string", + "description": "Who to assign the application to", + "enum": ["On", "allLicensedUsers", "AllDevices", "AllDevicesAndUsers"] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Office application added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListAPDevices": { + "get": { + "summary": "List Autopilot Devices", + "description": "Retrieves a list of Autopilot devices", + "tags": [ + "Autopilot" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Autopilot devices retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The device ID" + }, + "displayName": { + "type": "string", + "description": "The display name of the device" + }, + "serialNumber": { + "type": "string", + "description": "The serial number of the device" + }, + "model": { + "type": "string", + "description": "The model of the device" + }, + "manufacturer": { + "type": "string", + "description": "The manufacturer of the device" + }, + "groupTag": { + "type": "string", + "description": "The group tag of the device" + }, + "enrollmentState": { + "type": "string", + "description": "The enrollment state of the device" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecAssignAPDevice": { + "post": { + "summary": "Assign Autopilot Device", + "description": "Assigns an Autopilot device to a user", + "tags": [ + "Autopilot" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "device", + "serialNumber", + "user" + ], + "properties": { + "device": { + "type": "string", + "description": "The device ID" + }, + "serialNumber": { + "type": "string", + "description": "The serial number of the device" + }, + "user": { + "type": "string", + "description": "The user principal name" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Autopilot device assigned successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemoveAPDevice": { + "post": { + "summary": "Remove Autopilot Device", + "description": "Removes an Autopilot device", + "tags": [ + "Autopilot" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "string", + "description": "The device ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Autopilot device removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecSyncAPDevices": { + "post": { + "summary": "Sync Autopilot Devices", + "description": "Syncs Autopilot devices", + "tags": [ + "Autopilot" + ], + "responses": { + "200": { + "description": "Autopilot devices synced successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListAutopilotConfig": { + "get": { + "summary": "List Autopilot Configurations", + "description": "Retrieves a list of Autopilot configurations", + "tags": [ + "Autopilot" + ], + "parameters": [ + { + "name": "type", + "in": "query", + "description": "The type of configuration to retrieve", + "required": false, + "schema": { + "type": "string", + "enum": ["ESP", "ApProfile"] + } + }, + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Autopilot configurations retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The configuration ID" + }, + "displayName": { + "type": "string", + "description": "The display name of the configuration" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListDevices": { + "get": { + "summary": "List Devices", + "description": "Retrieves a list of devices", + "tags": [ + "Devices" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Devices retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The device ID" + }, + "displayName": { + "type": "string", + "description": "The display name of the device" + }, + "manufacturer": { + "type": "string", + "description": "The manufacturer of the device" + }, + "model": { + "type": "string", + "description": "The model of the device" + }, + "osVersion": { + "type": "string", + "description": "The OS version of the device" + }, + "lastSyncDateTime": { + "type": "string", + "description": "The last sync date and time" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecDeviceAction": { + "post": { + "summary": "Execute Device Action", + "description": "Executes an action on a device", + "tags": [ + "Devices" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "Action", + "DeviceID" + ], + "properties": { + "Action": { + "type": "string", + "description": "The action to perform", + "enum": ["Reboot", "Rename", "Autopilot", "Wipe", "RemoveFromAutopilot", "RemoveFromIntune", "RemoveFromDefender", "RemoveFromAzure", "RemoveFromEverywhere", "SyncDevice", "FreshStart", "QuickScan", "FullScan", "WindowsUpdateScan", "WindowsUpdateReboot", "WindowsUpdateDrivers", "WindowsUpdateFeatures", "WindowsUpdateOther", "WindowsUpdateAll"] + }, + "DeviceID": { + "type": "string", + "description": "The device ID" + }, + "NewDeviceName": { + "type": "string", + "description": "The new device name (for Rename action)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Device action executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecGetLocalAdminPassword": { + "post": { + "summary": "Get Local Admin Password", + "description": "Retrieves the local admin password for a device", + "tags": [ + "Devices" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "DeviceID" + ], + "properties": { + "DeviceID": { + "type": "string", + "description": "The device ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Local admin password retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecGetRecoveryKey": { + "post": { + "summary": "Get Recovery Key", + "description": "Retrieves the recovery key for a device", + "tags": [ + "Devices" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "DeviceID" + ], + "properties": { + "DeviceID": { + "type": "string", + "description": "The device ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Recovery key retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListIntunePolicy": { + "get": { + "summary": "List Intune Policies", + "description": "Retrieves a list of Intune policies", + "tags": [ + "Intune" + ], + "parameters": [ + { + "name": "type", + "in": "query", + "description": "The type of policy to retrieve", + "required": false, + "schema": { + "type": "string", + "enum": ["ESP"] + } + }, + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Intune policies retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The policy ID" + }, + "displayName": { + "type": "string", + "description": "The display name of the policy" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddPolicy": { + "post": { + "summary": "Add Policy", + "description": "Adds a policy", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "selectedTenants": { + "type": "array", + "description": "The tenants to add the policy to", + "items": { + "type": "object", + "properties": { + "customerId": { + "type": "string", + "description": "The tenant ID" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Policy added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddIntuneTemplate": { + "post": { + "summary": "Add Intune Template", + "description": "Adds an Intune template", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Type": { + "type": "string", + "description": "The type of template" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Intune template added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecAssignPolicy": { + "post": { + "summary": "Assign Policy", + "description": "Assigns a policy", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "AssignTo", + "ID" + ], + "properties": { + "AssignTo": { + "type": "string", + "description": "Who to assign the policy to", + "enum": ["AllUsers", "AllDevices", "Both"] + }, + "ID": { + "type": "string", + "description": "The policy ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Policy assigned successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemovePolicy": { + "post": { + "summary": "Remove Policy", + "description": "Removes a policy", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "string", + "description": "The policy ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Policy removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListIntuneTemplates": { + "get": { + "summary": "List Intune Templates", + "description": "Retrieves a list of Intune templates", + "tags": [ + "Intune" + ], + "parameters": [ + { + "name": "View", + "in": "query", + "description": "Whether to view the templates", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Intune templates retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "GUID": { + "type": "string", + "description": "The template ID" + }, + "displayName": { + "type": "string", + "description": "The display name of the template" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecEditTemplate": { + "post": { + "summary": "Edit Template", + "description": "Edits a template", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string", + "description": "The template ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Template edited successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecCommunityRepo": { + "post": { + "summary": "Execute Community Repository", + "description": "Executes a community repository", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RepoURL": { + "type": "string", + "description": "The repository URL" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Community repository executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemoveIntuneTemplate": { + "post": { + "summary": "Remove Intune Template", + "description": "Removes an Intune template", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "string", + "description": "The template ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Intune template removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListIntuneScript": { + "get": { + "summary": "List Intune Scripts", + "description": "Retrieves a list of Intune scripts", + "tags": [ + "Intune" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Intune scripts retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The script ID" + }, + "displayName": { + "type": "string", + "description": "The display name of the script" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemoveIntuneScript": { + "post": { + "summary": "Remove Intune Script", + "description": "Removes an Intune script", + "tags": [ + "Intune" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "string", + "description": "The script ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Intune script removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListGraphRequest": { + "get": { + "summary": "List Graph Request", + "description": "Retrieves data from a Graph API request", + "tags": [ + "Graph" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "endpoint", + "in": "query", + "description": "The Graph API endpoint", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Graph request data retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/listUsers": { + "get": { + "summary": "List Users", + "description": "Retrieves a list of users", + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Users retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "userPrincipalName": { + "type": "string", + "description": "The user principal name" + }, + "displayName": { + "type": "string", + "description": "The display name of the user" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListAppsRepository": { + "post": { + "summary": "List Apps Repository", + "description": "Searches the apps repository", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "search" + ], + "properties": { + "search": { + "type": "string", + "description": "The search query" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Apps repository search results retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "packagename": { + "type": "string", + "description": "The package name" + }, + "applicationName": { + "type": "string", + "description": "The application name" + }, + "description": { + "type": "string", + "description": "The application description" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListPotentialApps": { + "post": { + "summary": "List Potential Apps", + "description": "Searches for potential apps", + "tags": [ + "Applications" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "searchString", + "type" + ], + "properties": { + "searchString": { + "type": "string", + "description": "The search query" + }, + "type": { + "type": "string", + "description": "The type of apps to search for", + "enum": ["WinGet"] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Potential apps search results retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "packagename": { + "type": "string", + "description": "The package name" + }, + "applicationName": { + "type": "string", + "description": "The application name" + }, + "description": { + "type": "string", + "description": "The application description" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListExtensionsConfig": { + "get": { + "summary": "List Extensions Configuration", + "description": "Retrieves the configuration for extensions", + "tags": [ + "Extensions" + ], + "responses": { + "200": { + "description": "Extensions configuration retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether the extension is enabled" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListCommunityRepos": { + "get": { + "summary": "List Community Repositories", + "description": "Retrieves a list of community repositories", + "tags": [ + "Intune" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Community repositories retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "RepoURL": { + "type": "string", + "description": "The repository URL" + }, + "RepoName": { + "type": "string", + "description": "The repository name" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + } + } +} \ No newline at end of file diff --git a/Tools/identity-openapispec.json b/Tools/identity-openapispec.json new file mode 100644 index 000000000000..72e8871a8913 --- /dev/null +++ b/Tools/identity-openapispec.json @@ -0,0 +1,1774 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CIPP Identity API", + "description": "API endpoints for identity management in CIPP", + "version": "1.0.0" + }, + "paths": { + "/api/ListGraphRequest": { + "get": { + "summary": "List Graph Request", + "description": "Generic endpoint for making Graph API requests with different parameters", + "parameters": [ + { + "name": "Endpoint", + "in": "query", + "description": "The Graph API endpoint to call", + "required": true, + "schema": { + "type": "string" + }, + "examples": { + "users": { + "value": "users" + }, + "devices": { + "value": "devices" + }, + "identityProtection/riskDetections": { + "value": "identityProtection/riskDetections" + }, + "identityProtection/riskyUsers": { + "value": "identityProtection/riskyUsers" + } + } + }, + { + "name": "manualPagination", + "in": "query", + "description": "Whether to use manual pagination", + "schema": { + "type": "boolean" + } + }, + { + "name": "$select", + "in": "query", + "description": "Fields to select", + "schema": { + "type": "string" + } + }, + { + "name": "$count", + "in": "query", + "description": "Whether to include count", + "schema": { + "type": "boolean" + } + }, + { + "name": "$orderby", + "in": "query", + "description": "Field to order by", + "schema": { + "type": "string" + } + }, + { + "name": "$top", + "in": "query", + "description": "Number of records to return", + "schema": { + "type": "integer" + } + }, + { + "name": "$format", + "in": "query", + "description": "Response format", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + }, + "Metadata": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "/api/ExecCreateTAP": { + "post": { + "summary": "Create Temporary Access Password", + "description": "Creates a temporary access password for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User Principal Name" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecResetMFA": { + "post": { + "summary": "Reset MFA", + "description": "Re-requires MFA registration for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User Principal Name" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecSendPush": { + "post": { + "summary": "Send MFA Push", + "description": "Sends an MFA push notification to a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "UserEmail": { + "type": "string", + "description": "User Principal Name" + } + }, + "required": ["UserEmail"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecPerUserMFA": { + "post": { + "summary": "Set Per-User MFA", + "description": "Sets per-user MFA for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "description": "User Principal Name" + }, + "State": { + "type": "string", + "enum": ["Enforced", "Enabled", "Disabled"], + "description": "MFA State" + } + }, + "required": ["userId", "State"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecConvertMailbox": { + "post": { + "summary": "Convert Mailbox", + "description": "Converts a mailbox between shared and regular types", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User Principal Name" + }, + "MailboxType": { + "type": "string", + "enum": ["!Shared", "!Regular"], + "description": "Mailbox Type" + } + }, + "required": ["ID", "MailboxType"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecEnableArchive": { + "post": { + "summary": "Enable Online Archive", + "description": "Enables online archive for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User Principal Name" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecSetOoO": { + "post": { + "summary": "Set Out of Office", + "description": "Sets or disables out of office for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "description": "User Principal Name" + }, + "AutoReplyState": { + "type": "string", + "enum": ["Enabled", "Disabled"], + "description": "Auto Reply State" + }, + "input": { + "type": "string", + "description": "Out of Office Message" + } + }, + "required": ["userId", "AutoReplyState"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/EditGroup": { + "post": { + "summary": "Edit Group", + "description": "Edits a group, including adding/removing members and owners", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantId": { + "type": "string", + "description": "Tenant ID" + }, + "groupId": { + "type": "string", + "description": "Group ID" + }, + "AddMember": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Members to add" + }, + "AddOwner": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Owners to add" + }, + "RemoveMember": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Members to remove" + }, + "RemoveOwner": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Owners to remove" + }, + "addMember": { + "type": "string", + "description": "Single member to add" + }, + "groupId": { + "type": "string", + "description": "Group ID to add member to" + } + }, + "required": ["tenantId", "groupId"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListGroups": { + "get": { + "summary": "List Groups", + "description": "Lists groups in the tenant", + "parameters": [ + { + "name": "groupID", + "in": "query", + "description": "Group ID to get details for", + "schema": { + "type": "string" + } + }, + { + "name": "tenantFilter", + "in": "query", + "description": "Tenant to filter by", + "schema": { + "type": "string" + } + }, + { + "name": "members", + "in": "query", + "description": "Whether to include members", + "schema": { + "type": "boolean" + } + }, + { + "name": "owners", + "in": "query", + "description": "Whether to include owners", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "groupInfo": { + "type": "object" + }, + "members": { + "type": "array", + "items": { + "type": "object" + } + }, + "owners": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + } + }, + "/api/ExecEmailForward": { + "post": { + "summary": "Email Forwarding", + "description": "Manages email forwarding for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "User Principal Name" + }, + "userid": { + "type": "string", + "description": "User Principal Name" + }, + "ForwardOption": { + "type": "string", + "description": "Forward Option" + } + }, + "required": ["username", "userid", "ForwardOption"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecOneDriveProvision": { + "post": { + "summary": "Provision OneDrive", + "description": "Pre-provisions OneDrive for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "UserPrincipalName": { + "type": "string", + "description": "User Principal Name" + } + }, + "required": ["UserPrincipalName"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecOneDriveShortCut": { + "post": { + "summary": "Add OneDrive Shortcut", + "description": "Adds a shortcut to a SharePoint site in a user's OneDrive", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "User Principal Name" + }, + "userid": { + "type": "string", + "description": "User ID" + }, + "siteUrl": { + "type": "string", + "description": "SharePoint Site URL" + } + }, + "required": ["username", "userid", "siteUrl"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListSites": { + "get": { + "summary": "List Sites", + "description": "Lists SharePoint sites", + "parameters": [ + { + "name": "type", + "in": "query", + "description": "Type of sites to list", + "schema": { + "type": "string" + } + }, + { + "name": "URLOnly", + "in": "query", + "description": "Whether to return only URLs", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "webUrl": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/api/ExecDisableUser": { + "post": { + "summary": "Disable/Enable User", + "description": "Blocks or unblocks sign-in for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User ID" + }, + "Enable": { + "type": "boolean", + "description": "Whether to enable the user" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecResetPass": { + "post": { + "summary": "Reset Password", + "description": "Resets a user's password", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "MustChange": { + "type": "boolean", + "description": "Whether the user must change password at next logon" + }, + "ID": { + "type": "string", + "description": "User Principal Name" + }, + "displayName": { + "type": "string", + "description": "User Display Name" + } + }, + "required": ["MustChange", "ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecClrImmId": { + "post": { + "summary": "Clear Immutable ID", + "description": "Clears the immutable ID for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User ID" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecRevokeSessions": { + "post": { + "summary": "Revoke Sessions", + "description": "Revokes all sessions for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User ID" + }, + "Username": { + "type": "string", + "description": "User Principal Name" + } + }, + "required": ["ID", "Username"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/RemoveUser": { + "post": { + "summary": "Remove User", + "description": "Deletes a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "User ID" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/AddUser": { + "post": { + "summary": "Add User", + "description": "Creates a new user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string", + "description": "Tenant ID" + }, + "givenName": { + "type": "string", + "description": "First Name" + }, + "surname": { + "type": "string", + "description": "Last Name" + }, + "mailNickname": { + "type": "string", + "description": "Mail Nickname" + }, + "primDomain": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "description": "Primary Domain" + }, + "usageLocation": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "description": "Usage Location" + } + }, + "required": ["tenantFilter", "givenName", "surname", "mailNickname", "primDomain"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListMFAUsers": { + "get": { + "summary": "List MFA Users", + "description": "Lists users with MFA information", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "UPN": { + "type": "string" + }, + "AccountEnabled": { + "type": "string" + }, + "isLicensed": { + "type": "string" + }, + "MFARegistration": { + "type": "string" + }, + "PerUser": { + "type": "string" + }, + "CoveredBySD": { + "type": "string" + }, + "CoveredByCA": { + "type": "string" + }, + "MFAMethods": { + "type": "array", + "items": { + "type": "string" + } + }, + "CAPolicies": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "/api/ListSignIns": { + "get": { + "summary": "List Sign Ins", + "description": "Lists sign-in events", + "parameters": [ + { + "name": "Days", + "in": "query", + "description": "Number of days to look back", + "schema": { + "type": "integer" + } + }, + { + "name": "filter", + "in": "query", + "description": "Custom filter", + "schema": { + "type": "string" + } + }, + { + "name": "failedLogonsOnly", + "in": "query", + "description": "Whether to show only failed logons", + "schema": { + "type": "boolean" + } + }, + { + "name": "FailureThreshold", + "in": "query", + "description": "Failure threshold", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "createdDateTime": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + }, + "clientAppUsed": { + "type": "string" + }, + "authenticationRequirement": { + "type": "string" + }, + "errorCode": { + "type": "string" + }, + "additionalDetails": { + "type": "string" + }, + "ipAddress": { + "type": "string" + }, + "locationcipp": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/api/ListInactiveAccounts": { + "get": { + "summary": "List Inactive Accounts", + "description": "Lists inactive user accounts (6 months)", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tenantDisplayName": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "lastSignInDateTime": { + "type": "string" + }, + "lastNonInteractiveSignInDateTime": { + "type": "string" + }, + "numberOfAssignedLicenses": { + "type": "integer" + }, + "lastRefreshedDateTime": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/api/ListAzureADConnectStatus": { + "get": { + "summary": "List Azure AD Connect Status", + "description": "Lists Azure AD Connect status", + "parameters": [ + { + "name": "DataToReturn", + "in": "query", + "description": "Type of data to return", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "ObjectType": { + "type": "string" + }, + "createdDateTime": { + "type": "string" + }, + "onPremisesProvisioningErrors": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + }, + "/api/ExecGroupsHideFromGAL": { + "post": { + "summary": "Hide/Unhide Group from GAL", + "description": "Hides or unhides a group from the Global Address List", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "Group Email" + }, + "GroupType": { + "type": "string", + "description": "Group Type" + }, + "HidefromGAL": { + "type": "boolean", + "description": "Whether to hide from GAL" + } + }, + "required": ["ID", "GroupType", "HidefromGAL"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecGroupsDeliveryManagement": { + "post": { + "summary": "Manage Group Delivery Settings", + "description": "Manages delivery settings for a group", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "Group Email" + }, + "GroupType": { + "type": "string", + "description": "Group Type" + }, + "OnlyAllowInternal": { + "type": "boolean", + "description": "Whether to only allow internal messages" + } + }, + "required": ["ID", "GroupType", "OnlyAllowInternal"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecGroupsDelete": { + "post": { + "summary": "Delete Group", + "description": "Deletes a group", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "Group ID" + }, + "GroupType": { + "type": "string", + "description": "Group Type" + }, + "DisplayName": { + "type": "string", + "description": "Group Display Name" + } + }, + "required": ["ID", "GroupType", "DisplayName"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/AddGroup": { + "post": { + "summary": "Add Group", + "description": "Creates a new group", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string", + "description": "Tenant ID" + }, + "groupName": { + "type": "string", + "description": "Group Name" + }, + "groupType": { + "type": "string", + "description": "Group Type" + } + }, + "required": ["tenantFilter", "groupName", "groupType"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListRoles": { + "get": { + "summary": "List Roles", + "description": "Lists roles in the tenant", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "DisplayName": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "Members": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "/api/ExecDismissRiskyUser": { + "post": { + "summary": "Dismiss Risky User", + "description": "Dismisses the risk for a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "userId": { + "type": "string", + "description": "User ID" + }, + "userDisplayName": { + "type": "string", + "description": "User Display Name" + } + }, + "required": ["userId", "userDisplayName"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecJITAdmin": { + "post": { + "summary": "Execute JIT Admin", + "description": "Creates or updates a JIT admin", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string", + "description": "Tenant ID" + }, + "userAction": { + "type": "string", + "enum": ["create", "select"], + "description": "User Action" + }, + "firstName": { + "type": "string", + "description": "First Name" + }, + "lastName": { + "type": "string", + "description": "Last Name" + }, + "userName": { + "type": "string", + "description": "Username" + }, + "domain": { + "type": "string", + "description": "Domain Name" + }, + "existingUser": { + "type": "string", + "description": "Existing User" + }, + "startDate": { + "type": "string", + "format": "date", + "description": "Start Date" + }, + "endDate": { + "type": "string", + "format": "date", + "description": "End Date" + }, + "adminRoles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Admin Roles" + }, + "UseTAP": { + "type": "boolean", + "description": "Generate TAP" + }, + "expireAction": { + "type": "string", + "enum": ["DeleteUser", "DisableUser", "RemoveRoles"], + "description": "Expiration Action" + }, + "postExecution": { + "type": "array", + "items": { + "type": "string", + "enum": ["Webhook", "email", "PSA"] + }, + "description": "Notification Action" + } + }, + "required": ["tenantFilter", "userAction", "startDate", "endDate", "adminRoles"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + }, + "get": { + "summary": "List JIT Admins", + "description": "Lists JIT admins", + "parameters": [ + { + "name": "Action", + "in": "query", + "description": "Action to perform", + "schema": { + "type": "string", + "enum": ["List"] + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + } + }, + "/api/ExecDeviceDelete": { + "post": { + "summary": "Manage Device", + "description": "Enables, disables, or deletes a device", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "Device ID" + }, + "action": { + "type": "string", + "enum": ["!Enable", "!Disable", "!Delete"], + "description": "Action to perform" + } + }, + "required": ["ID", "action"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecGetRecoveryKey": { + "post": { + "summary": "Get Recovery Key", + "description": "Retrieves BitLocker recovery keys for a device", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GUID": { + "type": "string", + "description": "Device ID" + } + }, + "required": ["GUID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListDeletedItems": { + "get": { + "summary": "List Deleted Items", + "description": "Lists deleted items", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "displayName": { + "type": "string" + }, + "TargetType": { + "type": "string" + }, + "userPrincipalName": { + "type": "string" + }, + "deletedDateTime": { + "type": "string" + }, + "onPremisesSyncEnabled": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + }, + "/api/ExecRestoreDeleted": { + "post": { + "summary": "Restore Deleted Object", + "description": "Restores a deleted object", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "Object ID" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ExecOffboardUser": { + "post": { + "summary": "Offboard User", + "description": "Offboards a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string", + "description": "Tenant ID" + }, + "user": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Users to offboard" + }, + "Scheduled": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "description": "Scheduled offboarding" + } + }, + "required": ["tenantFilter", "user"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListExtensionsConfig": { + "get": { + "summary": "List Extensions Configuration", + "description": "Lists extensions configuration", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GitHub": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + } + }, + "/api/ExecCommunityRepo": { + "post": { + "summary": "Execute Community Repository Action", + "description": "Executes an action on a community repository", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string", + "enum": ["UploadTemplate"], + "description": "Action to perform" + }, + "GUID": { + "type": "string", + "description": "Template GUID" + }, + "FullName": { + "type": "string", + "description": "Repository Full Name" + }, + "Message": { + "type": "string", + "description": "Commit Message" + } + }, + "required": ["Action", "GUID", "FullName", "Message"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListCommunityRepos": { + "get": { + "summary": "List Community Repositories", + "description": "Lists community repositories", + "parameters": [ + { + "name": "WriteAccess", + "in": "query", + "description": "Whether to only show repositories with write access", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FullName": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "/api/RemoveGroupTemplate": { + "post": { + "summary": "Remove Group Template", + "description": "Removes a group template", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ID": { + "type": "string", + "description": "Template GUID" + } + }, + "required": ["ID"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + }, + "/api/ListGroupTemplates": { + "get": { + "summary": "List Group Templates", + "description": "Lists group templates", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Displayname": { + "type": "string" + }, + "Description": { + "type": "string" + }, + "groupType": { + "type": "string" + }, + "GUID": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/api/AddGroupTemplate": { + "post": { + "summary": "Add Group Template", + "description": "Creates a new group template", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "string", + "description": "Tenant ID" + }, + "Displayname": { + "type": "string", + "description": "Template Display Name" + }, + "Description": { + "type": "string", + "description": "Template Description" + }, + "groupType": { + "type": "string", + "description": "Group Type" + } + }, + "required": ["Displayname", "Description", "groupType"] + } + } + } + }, + "responses": { + "200": { + "description": "Successful response" + } + } + } + } + } +} \ No newline at end of file diff --git a/Tools/security-openapispec.json b/Tools/security-openapispec.json new file mode 100644 index 000000000000..d7b388cc3a3e --- /dev/null +++ b/Tools/security-openapispec.json @@ -0,0 +1,891 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CIPP Security API", + "description": "API endpoints for the Security section of Cloud Identity and Policy Platform (CIPP)", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/api", + "description": "CIPP API Server" + } + ], + "components": { + "schemas": { + "StandardResponse": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "description": "The results of the operation" + } + } + } + } + }, + "paths": { + "/ListDefenderState": { + "get": { + "summary": "List Defender State", + "description": "Retrieves the status of Microsoft Defender across devices", + "tags": [ + "Defender" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Defender state retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "deviceName": { + "type": "string", + "description": "The name of the device" + }, + "windowsProtectionState": { + "type": "object", + "properties": { + "malwareProtectionEnabled": { + "type": "boolean", + "description": "Whether malware protection is enabled" + }, + "realTimeProtectionEnabled": { + "type": "boolean", + "description": "Whether real-time protection is enabled" + }, + "networkInspectionSystemEnabled": { + "type": "boolean", + "description": "Whether network inspection is enabled" + }, + "deviceState": { + "type": "string", + "description": "The state of the device" + }, + "quickScanOverdue": { + "type": "boolean", + "description": "Whether a quick scan is overdue" + }, + "fullScanOverdue": { + "type": "boolean", + "description": "Whether a full scan is overdue" + }, + "signatureUpdateOverdue": { + "type": "boolean", + "description": "Whether a signature update is overdue" + }, + "rebootRequired": { + "type": "boolean", + "description": "Whether a reboot is required" + }, + "lastReportedDateTime": { + "type": "string", + "format": "date-time", + "description": "The date and time of the last report" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListDefenderTVM": { + "get": { + "summary": "List Defender Threat & Vulnerability Management", + "description": "Retrieves software vulnerabilities detected by Microsoft Defender", + "tags": [ + "Defender" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Defender TVM data retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "affectedDevicesCount": { + "type": "integer", + "description": "The number of affected devices" + }, + "affectedDevices": { + "type": "array", + "description": "The list of affected devices", + "items": { + "type": "string" + } + }, + "osPlatform": { + "type": "string", + "description": "The OS platform" + }, + "softwareVendor": { + "type": "string", + "description": "The software vendor" + }, + "softwareName": { + "type": "string", + "description": "The software name" + }, + "vulnerabilitySeverityLevel": { + "type": "string", + "description": "The severity level of the vulnerability" + }, + "cvssScore": { + "type": "number", + "description": "The CVSS score" + }, + "securityUpdateAvailable": { + "type": "boolean", + "description": "Whether a security update is available" + }, + "exploitabilityLevel": { + "type": "string", + "description": "The exploitability level" + }, + "cveId": { + "type": "string", + "description": "The CVE ID" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddDefenderDeployment": { + "post": { + "summary": "Add Defender Deployment", + "description": "Deploys Microsoft Defender policies to selected tenants", + "tags": [ + "Defender" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "selectedTenants" + ], + "properties": { + "selectedTenants": { + "type": "array", + "description": "The tenants to deploy to", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID" + }, + "label": { + "type": "string", + "description": "Tenant display name" + } + } + } + }, + "Compliance": { + "type": "object", + "description": "Compliance settings", + "properties": { + "AllowMEMEnforceCompliance": { + "type": "boolean", + "description": "Allow Microsoft Defender for Endpoint to enforce Endpoint Security Configurations" + }, + "ConnectIosCompliance": { + "type": "boolean", + "description": "Connect iOS/iPadOS devices version 13.0 and above to Microsoft Defender for Endpoint" + }, + "ConnectAndroidCompliance": { + "type": "boolean", + "description": "Connect Android devices version 6.0.0 and above to Microsoft Defender for Endpoint" + }, + "ConnectWindows": { + "type": "boolean", + "description": "Connect Windows devices version 10.0.15063 and above to Microsoft Defender for Endpoint" + }, + "AppSync": { + "type": "boolean", + "description": "Enable App Sync for iOS/iPadOS devices" + }, + "BlockunsupportedOS": { + "type": "boolean", + "description": "Block unsupported OS versions" + }, + "ConnectAndroid": { + "type": "boolean", + "description": "Connect Android devices to Microsoft Defender for Endpoint" + }, + "ConnectIos": { + "type": "boolean", + "description": "Connect iOS/iPadOS devices to Microsoft Defender for Endpoint" + } + } + }, + "EDR": { + "type": "object", + "description": "EDR settings", + "properties": { + "Telemetry": { + "type": "boolean", + "description": "Expedite Telemetry Reporting Frequency" + }, + "Config": { + "type": "boolean", + "description": "Connect Defender Configuration Package automatically from Connector" + }, + "SampleSharing": { + "type": "boolean", + "description": "Enable Sample Sharing" + } + } + }, + "Policy": { + "type": "object", + "description": "Policy settings", + "properties": { + "ScanArchives": { + "type": "boolean", + "description": "Allow Archive Scanning" + }, + "AllowBehavior": { + "type": "boolean", + "description": "Allow behavior monitoring" + }, + "AllowCloudProtection": { + "type": "boolean", + "description": "Allow Cloud Protection" + }, + "AllowEmailScanning": { + "type": "boolean", + "description": "Allow e-mail scanning" + }, + "AllowFullScanNetwork": { + "type": "boolean", + "description": "Allow Full Scan on Network Drives" + }, + "AllowFullScanRemovable": { + "type": "boolean", + "description": "Allow Full Scan on Removable Drives" + }, + "AllowScriptScan": { + "type": "boolean", + "description": "Allow Script Scanning" + }, + "AllowIPS": { + "type": "boolean", + "description": "Allow Intrusion Prevention System" + }, + "LowCPU": { + "type": "boolean", + "description": "Enable Low CPU priority" + }, + "AllowDownloadable": { + "type": "boolean", + "description": "Allow scanning of downloaded files" + }, + "AllowRealTime": { + "type": "boolean", + "description": "Allow Realtime monitoring" + }, + "AllowNetwork": { + "type": "boolean", + "description": "Allow scanning of mapped drives" + }, + "AllowUI": { + "type": "boolean", + "description": "Allow users to access UI" + }, + "NetworkProtectionBlock": { + "type": "boolean", + "description": "Enable Network Protection in Block Mode" + }, + "NetworkProtectionAudit": { + "type": "boolean", + "description": "Enable Network Protection in Audit Mode" + }, + "CheckSigs": { + "type": "boolean", + "description": "Check Signatures before scan" + }, + "DisableCatchupFullScan": { + "type": "boolean", + "description": "Disable Catchup Full Scan" + }, + "DisableCatchupQuickScan": { + "type": "boolean", + "description": "Disable Catchup Quick Scan" + }, + "AssignTo": { + "type": "string", + "description": "Assignment target", + "enum": ["none", "allLicensedUsers", "AllDevices", "AllDevicesAndUsers"] + } + } + }, + "ASR": { + "type": "object", + "description": "Attack Surface Reduction settings", + "properties": { + "BlockAdobeChild": { + "type": "boolean", + "description": "Block Adobe Reader from creating child processes" + }, + "BlockWin32Macro": { + "type": "boolean", + "description": "Block Win32 API calls from Office macros" + }, + "BlockCredentialStealing": { + "type": "boolean", + "description": "Block credential stealing from the Windows local security authority subsystem" + }, + "BlockPSExec": { + "type": "boolean", + "description": "Block process creations originating from PSExec and WMI commands" + }, + "WMIPersistence": { + "type": "boolean", + "description": "Block persistence through WMI event subscription" + }, + "BlockOfficeExes": { + "type": "boolean", + "description": "Block Office applications from creating executable content" + }, + "BlockOfficeApps": { + "type": "boolean", + "description": "Block Office applications from injecting code into other processes" + }, + "BlockYoungExe": { + "type": "boolean", + "description": "Block executable files from running unless they meet a prevalence, age, or trusted list criterion" + }, + "blockJSVB": { + "type": "boolean", + "description": "Block JavaScript or VBScript from launching downloaded executable content" + }, + "blockOfficeComChild": { + "type": "boolean", + "description": "Block Office communication application from creating child processes" + }, + "blockOfficeChild": { + "type": "boolean", + "description": "Block all Office applications from creating child processes" + }, + "BlockUntrustedUSB": { + "type": "boolean", + "description": "Block untrusted and unsigned processes that run from USB" + }, + "EnableRansomwareVac": { + "type": "boolean", + "description": "Use advanced protection against ransomware" + }, + "BlockExesMail": { + "type": "boolean", + "description": "Block executable content from email client and webmail" + }, + "BlockUnsignedDrivers": { + "type": "boolean", + "description": "Block abuse of exploited vulnerable signed drivers" + }, + "AssignTo": { + "type": "string", + "description": "Assignment target", + "enum": ["none", "allLicensedUsers", "AllDevices", "AllDevicesAndUsers"] + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Defender deployment added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecAlertsList": { + "get": { + "summary": "List Security Alerts", + "description": "Retrieves a list of security alerts", + "tags": [ + "Incidents" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Security alerts retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "properties": { + "MSResults": { + "type": "array", + "items": { + "type": "object", + "properties": { + "EventDateTime": { + "type": "string", + "format": "date-time", + "description": "The date and time of the event" + }, + "Status": { + "type": "string", + "description": "The status of the alert" + }, + "Title": { + "type": "string", + "description": "The title of the alert" + }, + "Severity": { + "type": "string", + "description": "The severity of the alert" + }, + "Category": { + "type": "string", + "description": "The category of the alert" + }, + "Tenant": { + "type": "string", + "description": "The tenant name" + }, + "InvolvedUsers": { + "type": "array", + "description": "The users involved in the alert", + "items": { + "type": "string" + } + }, + "Id": { + "type": "string", + "description": "The ID of the alert" + }, + "RawResult": { + "type": "object", + "description": "The raw result data", + "properties": { + "vendorInformation": { + "type": "object", + "properties": { + "vendor": { + "type": "string", + "description": "The vendor name" + }, + "provider": { + "type": "string", + "description": "The provider name" + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecSetSecurityAlert": { + "post": { + "summary": "Set Security Alert Status", + "description": "Updates the status of a security alert", + "tags": [ + "Incidents" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "GUID", + "Status", + "Vendor", + "Provider" + ], + "properties": { + "GUID": { + "type": "string", + "description": "The ID of the alert" + }, + "Status": { + "type": "string", + "description": "The new status of the alert", + "enum": ["!inProgress", "!resolved"] + }, + "Vendor": { + "type": "string", + "description": "The vendor name" + }, + "Provider": { + "type": "string", + "description": "The provider name" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Security alert status updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecIncidentsList": { + "get": { + "summary": "List Security Incidents", + "description": "Retrieves a list of security incidents", + "tags": [ + "Incidents" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Security incidents retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Created": { + "type": "string", + "format": "date-time", + "description": "The creation date and time" + }, + "Updated": { + "type": "string", + "format": "date-time", + "description": "The last update date and time" + }, + "Tenant": { + "type": "string", + "description": "The tenant name" + }, + "Id": { + "type": "string", + "description": "The ID of the incident" + }, + "RedirectId": { + "type": "string", + "description": "The redirect ID" + }, + "DisplayName": { + "type": "string", + "description": "The display name of the incident" + }, + "Status": { + "type": "string", + "description": "The status of the incident" + }, + "Severity": { + "type": "string", + "description": "The severity of the incident" + }, + "AssignedTo": { + "type": "string", + "description": "The user assigned to the incident" + }, + "Classification": { + "type": "string", + "description": "The classification of the incident" + }, + "Determination": { + "type": "string", + "description": "The determination of the incident" + }, + "IncidentUrl": { + "type": "string", + "description": "The URL to the incident" + }, + "Tags": { + "type": "array", + "description": "The tags associated with the incident", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecSetSecurityIncident": { + "post": { + "summary": "Set Security Incident Status", + "description": "Updates the status of a security incident or assigns it to a user", + "tags": [ + "Incidents" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "GUID" + ], + "properties": { + "GUID": { + "type": "string", + "description": "The ID of the incident" + }, + "Status": { + "type": "string", + "description": "The new status of the incident", + "enum": ["!active", "!inProgress", "!resolved"] + }, + "Assigned": { + "type": "string", + "description": "The user assigned to the incident" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Security incident updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListGraphRequest": { + "get": { + "summary": "List Graph Request", + "description": "Retrieves data from a Graph API request", + "tags": [ + "Graph" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "The tenant to filter by", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "endpoint", + "in": "query", + "description": "The Graph API endpoint", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "$top", + "in": "query", + "description": "Number of records to return", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Graph request data retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + } + } +} \ No newline at end of file diff --git a/Tools/teams-share-openapispec.json b/Tools/teams-share-openapispec.json new file mode 100644 index 000000000000..4c327deadebd --- /dev/null +++ b/Tools/teams-share-openapispec.json @@ -0,0 +1,749 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CIPP Teams & Share API", + "description": "API endpoints for Teams, SharePoint, and OneDrive management in CIPP", + "version": "1.0.0" + }, + "paths": { + "/api/ListSites": { + "get": { + "summary": "List sites", + "description": "List SharePoint sites or OneDrive accounts based on the type parameter", + "parameters": [ + { + "name": "type", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": ["SharePointSiteUsage", "OneDriveUsageAccount"] + }, + "description": "Type of sites to list" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Site" + } + } + } + } + } + } + } + }, + "/api/ExecSetSharePointMember": { + "post": { + "summary": "Add or remove SharePoint site member", + "description": "Add or remove a user as a member of a SharePoint site", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["groupId", "add", "URL", "SharePointType", "user"], + "properties": { + "groupId": { + "type": "string", + "description": "Owner principal name" + }, + "add": { + "type": "boolean", + "description": "True to add, false to remove" + }, + "URL": { + "type": "string", + "description": "Web URL of the site" + }, + "SharePointType": { + "type": "string", + "description": "Root web template" + }, + "user": { + "type": "string", + "description": "User principal name to add or remove" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Operation successful" + } + } + } + }, + "/api/ExecSharePointPerms": { + "post": { + "summary": "Manage SharePoint or OneDrive permissions", + "description": "Add or remove a user as a site admin for SharePoint or add/remove permissions for OneDrive", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["UPN", "RemovePermission", "URL"], + "properties": { + "UPN": { + "type": "string", + "description": "Owner principal name" + }, + "RemovePermission": { + "type": "boolean", + "description": "True to remove permission, false to add" + }, + "URL": { + "type": "string", + "description": "Web URL of the site or OneDrive" + }, + "user": { + "type": "string", + "description": "User principal name to add or remove permissions for" + }, + "onedriveAccessUser": { + "type": "string", + "description": "User principal name to add or remove OneDrive access for" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Operation successful" + } + } + } + }, + "/api/AddSite": { + "post": { + "summary": "Add a new SharePoint site", + "description": "Create a new SharePoint site", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["siteName", "siteDescription", "SiteOwner", "TemplateName", "siteDesign"], + "properties": { + "tenantFilter": { + "type": "string", + "description": "Tenant ID" + }, + "siteName": { + "type": "string", + "description": "Name of the site" + }, + "siteDescription": { + "type": "string", + "description": "Description of the site" + }, + "SiteOwner": { + "type": "string", + "description": "User principal name of the site owner" + }, + "TemplateName": { + "type": "string", + "enum": ["team", "communication"], + "description": "Template name" + }, + "siteDesign": { + "type": "string", + "enum": ["blank", "Showcase", "Topic"], + "description": "Site design template" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Site created successfully" + } + } + } + }, + "/api/AddSiteBulk": { + "post": { + "summary": "Add multiple SharePoint sites", + "description": "Create multiple SharePoint sites in bulk", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["tenantFilter", "bulkSites"], + "properties": { + "tenantFilter": { + "type": "string", + "description": "Tenant ID" + }, + "bulkSites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "SiteName": { + "type": "string", + "description": "Name of the site" + }, + "siteDescription": { + "type": "string", + "description": "Description of the site" + }, + "siteOwner": { + "type": "string", + "description": "User principal name of the site owner" + }, + "TemplateName": { + "type": "string", + "description": "Template name" + }, + "siteDesign": { + "type": "string", + "description": "Site design template" + }, + "sensitivityLabel": { + "type": "string", + "description": "Sensitivity label" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Sites created successfully" + } + } + } + }, + "/api/ListTeams": { + "get": { + "summary": "List Teams", + "description": "List Microsoft Teams", + "parameters": [ + { + "name": "type", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": ["list"] + }, + "description": "Type of Teams list" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Team" + } + } + } + } + } + } + } + }, + "/api/AddTeam": { + "post": { + "summary": "Add a new Team", + "description": "Create a new Microsoft Team", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["tenantID", "displayName", "owner", "visibility"], + "properties": { + "tenantID": { + "type": "string", + "description": "Tenant ID" + }, + "displayName": { + "type": "string", + "description": "Display name of the team" + }, + "description": { + "type": "string", + "description": "Description of the team" + }, + "owner": { + "type": "string", + "description": "User principal name of the team owner" + }, + "visibility": { + "type": "string", + "enum": ["public", "private"], + "description": "Team visibility" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Team created successfully" + } + } + } + }, + "/api/ListTeamsActivity": { + "get": { + "summary": "List Teams activity", + "description": "List Microsoft Teams user activity", + "parameters": [ + { + "name": "type", + "in": "query", + "required": true, + "schema": { + "type": "string", + "enum": ["TeamsUserActivityUser"] + }, + "description": "Type of Teams activity" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamsActivity" + } + } + } + } + } + } + } + }, + "/api/ListTeamsVoice": { + "get": { + "summary": "List Teams voice phone numbers", + "description": "List Microsoft Teams voice phone numbers", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamsVoice" + } + } + } + } + } + } + } + }, + "/api/ExecTeamsVoicePhoneNumberAssignment": { + "post": { + "summary": "Assign Teams voice phone number", + "description": "Assign a phone number to a user or set emergency location", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["PhoneNumber"], + "properties": { + "PhoneNumber": { + "type": "string", + "description": "Phone number to assign" + }, + "PhoneNumberType": { + "type": "string", + "description": "Type of phone number" + }, + "locationOnly": { + "type": "boolean", + "description": "True if only setting emergency location" + }, + "input": { + "type": "string", + "description": "User principal name or location ID" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Operation successful" + } + } + } + }, + "/api/ExecRemoveTeamsVoicePhoneNumberAssignment": { + "post": { + "summary": "Unassign Teams voice phone number", + "description": "Unassign a phone number from a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["PhoneNumber", "AssignedTo", "PhoneNumberType"], + "properties": { + "PhoneNumber": { + "type": "string", + "description": "Phone number to unassign" + }, + "AssignedTo": { + "type": "string", + "description": "User the phone number is assigned to" + }, + "PhoneNumberType": { + "type": "string", + "description": "Type of phone number" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Operation successful" + } + } + } + }, + "/api/ListTeamsLisLocation": { + "get": { + "summary": "List Teams emergency locations", + "description": "List Microsoft Teams emergency locations", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeamsLocation" + } + } + } + } + } + } + } + }, + "/api/listUsers": { + "get": { + "summary": "List users", + "description": "List users for selection in forms", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + } + }, + "/api/ListGraphRequest": { + "get": { + "summary": "List Graph API resources", + "description": "List resources from Microsoft Graph API", + "parameters": [ + { + "name": "Endpoint", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "Graph API endpoint" + }, + { + "name": "$filter", + "in": "query", + "schema": { + "type": "string" + }, + "description": "OData filter" + }, + { + "name": "$top", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Maximum number of results" + }, + { + "name": "$count", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "Include count" + }, + { + "name": "$orderby", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Order by field" + }, + { + "name": "$select", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Fields to select" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Site": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "description": "Display name of the site" + }, + "createdDateTime": { + "type": "string", + "format": "date-time", + "description": "Date and time when the site was created" + }, + "ownerPrincipalName": { + "type": "string", + "description": "Principal name of the site owner" + }, + "lastActivityDate": { + "type": "string", + "format": "date-time", + "description": "Date of last activity" + }, + "fileCount": { + "type": "integer", + "description": "Number of files" + }, + "storageUsedInGigabytes": { + "type": "number", + "format": "float", + "description": "Storage used in gigabytes" + }, + "storageAllocatedInGigabytes": { + "type": "number", + "format": "float", + "description": "Storage allocated in gigabytes" + }, + "reportRefreshDate": { + "type": "string", + "format": "date-time", + "description": "Date when the report was refreshed" + }, + "webUrl": { + "type": "string", + "description": "URL of the site" + }, + "description": { + "type": "string", + "description": "Description of the site" + } + } + }, + "Team": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "description": "Display name of the team" + }, + "description": { + "type": "string", + "description": "Description of the team" + }, + "visibility": { + "type": "string", + "description": "Visibility of the team" + }, + "mailNickname": { + "type": "string", + "description": "Mail nickname of the team" + }, + "id": { + "type": "string", + "description": "ID of the team" + } + } + }, + "TeamsActivity": { + "type": "object", + "properties": { + "UPN": { + "type": "string", + "description": "User principal name" + }, + "LastActive": { + "type": "string", + "format": "date-time", + "description": "Date and time of last activity" + }, + "MeetingCount": { + "type": "integer", + "description": "Number of meetings" + }, + "CallCount": { + "type": "integer", + "description": "Number of calls" + }, + "TeamsChat": { + "type": "integer", + "description": "Number of Teams chats" + } + } + }, + "TeamsVoice": { + "type": "object", + "properties": { + "AssignedTo": { + "type": "string", + "description": "User the phone number is assigned to" + }, + "TelephoneNumber": { + "type": "string", + "description": "Phone number" + }, + "AssignmentStatus": { + "type": "string", + "description": "Assignment status" + }, + "NumberType": { + "type": "string", + "description": "Type of phone number" + }, + "AcquiredCapabilities": { + "type": "string", + "description": "Capabilities of the phone number" + }, + "IsoCountryCode": { + "type": "string", + "description": "ISO country code" + }, + "PlaceName": { + "type": "string", + "description": "Place name" + }, + "ActivationState": { + "type": "string", + "description": "Activation state" + }, + "IsOperatorConnect": { + "type": "boolean", + "description": "Whether the number is operator connect" + }, + "AcquisitionDate": { + "type": "string", + "format": "date-time", + "description": "Date when the number was acquired" + } + } + }, + "TeamsLocation": { + "type": "object", + "properties": { + "Description": { + "type": "string", + "description": "Description of the location" + }, + "LocationId": { + "type": "string", + "description": "ID of the location" + } + } + }, + "User": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "description": "Display name of the user" + }, + "userPrincipalName": { + "type": "string", + "description": "User principal name" + }, + "id": { + "type": "string", + "description": "ID of the user" + } + } + } + } + } +} \ No newline at end of file diff --git a/Tools/tenant-openapispec.json b/Tools/tenant-openapispec.json new file mode 100644 index 000000000000..80ba16246e40 --- /dev/null +++ b/Tools/tenant-openapispec.json @@ -0,0 +1,1622 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CIPP Tenant API", + "description": "API for Tenant Management in Cloud Identity and Policy Platform (CIPP)", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/api", + "description": "CIPP API Server" + } + ], + "components": { + "schemas": { + "StandardResponse": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "description": "The results of the operation" + } + } + } + } + }, + "paths": { + "/ListAlertsQueue": { + "get": { + "summary": "List Alerts Queue", + "description": "Retrieves a list of alerts in the queue", + "tags": [ + "Tenant Administration" + ], + "responses": { + "200": { + "description": "Alerts retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "RowKey": { + "type": "string", + "description": "Unique identifier for the alert" + }, + "PartitionKey": { + "type": "string", + "description": "Partition key for the alert" + }, + "LogType": { + "type": "string", + "description": "Type of log" + }, + "RawAlert": { + "type": "object", + "description": "Raw alert data" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddAlert": { + "post": { + "summary": "Add Alert", + "description": "Adds a new alert", + "tags": [ + "Tenant Administration" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "array", + "description": "List of tenants to apply the alert to", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID" + }, + "label": { + "type": "string", + "description": "Tenant name" + } + } + } + }, + "excludedTenants": { + "type": "array", + "description": "List of tenants to exclude from the alert", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID" + }, + "label": { + "type": "string", + "description": "Tenant name" + } + } + } + }, + "logbook": { + "type": "object", + "description": "Log source", + "properties": { + "value": { + "type": "string", + "description": "Log source value" + }, + "label": { + "type": "string", + "description": "Log source label" + } + } + }, + "conditions": { + "type": "array", + "description": "Alert conditions", + "items": { + "type": "object", + "properties": { + "Property": { + "type": "object", + "description": "Property to check", + "properties": { + "value": { + "type": "string", + "description": "Property value" + }, + "label": { + "type": "string", + "description": "Property label" + } + } + }, + "Operator": { + "type": "object", + "description": "Comparison operator", + "properties": { + "value": { + "type": "string", + "description": "Operator value" + }, + "label": { + "type": "string", + "description": "Operator label" + } + } + }, + "Input": { + "type": "object", + "description": "Input value", + "properties": { + "value": { + "type": "string", + "description": "Input value" + } + } + } + } + } + }, + "Actions": { + "type": "array", + "description": "Actions to take", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Action value" + }, + "label": { + "type": "string", + "description": "Action label" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Alert added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddScheduledItem": { + "post": { + "summary": "Add Scheduled Item", + "description": "Adds a new scheduled item", + "tags": [ + "Tenant Administration" + ], + "parameters": [ + { + "name": "hidden", + "in": "query", + "description": "Whether to hide the scheduled item", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "DisallowDuplicateName", + "in": "query", + "description": "Whether to disallow duplicate names", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "RowKey": { + "type": "string", + "description": "Unique identifier for the scheduled item" + }, + "tenantFilter": { + "type": "string", + "description": "Tenant to apply the scheduled item to" + }, + "excludedTenants": { + "type": "array", + "description": "List of tenants to exclude from the scheduled item", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID" + }, + "label": { + "type": "string", + "description": "Tenant name" + } + } + } + }, + "Name": { + "type": "string", + "description": "Name of the scheduled item" + }, + "Command": { + "type": "object", + "description": "Command to execute", + "properties": { + "value": { + "type": "string", + "description": "Command value" + } + } + }, + "Parameters": { + "type": "object", + "description": "Parameters for the command" + }, + "ScheduledTime": { + "type": "integer", + "description": "When the job is scheduled to run (Unix timestamp)" + }, + "Recurrence": { + "type": "string", + "description": "How often the job recurs" + }, + "PostExecution": { + "type": "array", + "description": "Actions to take after execution", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Action value" + }, + "label": { + "type": "string", + "description": "Action label" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled item added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/listCSPLicenses": { + "get": { + "summary": "List CSP Licenses", + "description": "Retrieves a list of CSP licenses", + "tags": [ + "Tenant Reports" + ], + "responses": { + "200": { + "description": "CSP licenses retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Tenant": { + "type": "string", + "description": "Tenant name" + }, + "TenantId": { + "type": "string", + "description": "Tenant ID" + }, + "SubscriptionId": { + "type": "string", + "description": "Subscription ID" + }, + "SkuId": { + "type": "string", + "description": "SKU ID" + }, + "SkuName": { + "type": "string", + "description": "SKU name" + }, + "Status": { + "type": "string", + "description": "Subscription status" + }, + "Quantity": { + "type": "integer", + "description": "License quantity" + }, + "BillingCycle": { + "type": "string", + "description": "Billing cycle" + }, + "RenewalDate": { + "type": "string", + "description": "Renewal date" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecCSPLicense": { + "post": { + "summary": "Execute CSP License Operation", + "description": "Executes an operation on CSP licenses", + "tags": [ + "Tenant Reports" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Action": { + "type": "string", + "description": "Action to perform", + "enum": ["!Add", "!Remove", "!Cancel"] + }, + "sku": { + "type": "string", + "description": "SKU ID" + }, + "add": { + "type": "integer", + "description": "Number of licenses to add" + }, + "Remove": { + "type": "integer", + "description": "Number of licenses to remove" + }, + "SubscriptionIds": { + "type": "string", + "description": "Subscription IDs to cancel" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "CSP license operation executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListLicenses": { + "get": { + "summary": "List Licenses", + "description": "Retrieves a list of licenses", + "tags": [ + "Tenant Reports" + ], + "responses": { + "200": { + "description": "Licenses retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Tenant": { + "type": "string", + "description": "Tenant name" + }, + "License": { + "type": "string", + "description": "License name" + }, + "CountUsed": { + "type": "integer", + "description": "Number of licenses used" + }, + "CountAvailable": { + "type": "integer", + "description": "Number of licenses available" + }, + "TotalLicenses": { + "type": "integer", + "description": "Total number of licenses" + }, + "TermInfo": { + "type": "object", + "description": "Term information" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListStandards": { + "get": { + "summary": "List Standards", + "description": "Retrieves a list of standards", + "tags": [ + "Tenant Standards" + ], + "responses": { + "200": { + "description": "Standards retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "RowKey": { + "type": "string", + "description": "Unique identifier for the standard" + }, + "Standard": { + "type": "string", + "description": "Standard name" + }, + "Tenant": { + "type": "string", + "description": "Tenant name" + }, + "Enabled": { + "type": "boolean", + "description": "Whether the standard is enabled" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/listStandardTemplates": { + "get": { + "summary": "List Standard Templates", + "description": "Retrieves a list of standard templates", + "tags": [ + "Tenant Standards" + ], + "responses": { + "200": { + "description": "Standard templates retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "GUID": { + "type": "string", + "description": "Unique identifier for the template" + }, + "templateName": { + "type": "string", + "description": "Template name" + }, + "tenantFilter": { + "type": "array", + "description": "List of tenants to apply the template to", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID" + }, + "label": { + "type": "string", + "description": "Tenant name" + } + } + } + }, + "excludedTenants": { + "type": "array", + "description": "List of tenants to exclude from the template", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID" + }, + "label": { + "type": "string", + "description": "Tenant name" + } + } + } + }, + "updatedAt": { + "type": "string", + "description": "When the template was last updated" + }, + "updatedBy": { + "type": "string", + "description": "Who last updated the template" + }, + "runManually": { + "type": "boolean", + "description": "Whether the template is run manually" + }, + "standards": { + "type": "array", + "description": "List of standards in the template", + "items": { + "type": "object" + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecStandardsRun": { + "get": { + "summary": "Execute Standards Run", + "description": "Executes a standards run", + "tags": [ + "Tenant Standards" + ], + "parameters": [ + { + "name": "TemplateId", + "in": "query", + "description": "Template ID to run", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "tenantFilter", + "in": "query", + "description": "Tenant filter", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Standards run executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/RemoveStandardTemplate": { + "post": { + "summary": "Remove Standard Template", + "description": "Removes a standard template", + "tags": [ + "Tenant Standards" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ID" + ], + "properties": { + "ID": { + "type": "string", + "description": "Template ID to remove" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Standard template removed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/execStandardConvert": { + "post": { + "summary": "Convert Standards", + "description": "Converts legacy standards to the new format", + "tags": [ + "Tenant Standards" + ], + "responses": { + "200": { + "description": "Standards converted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListNamedLocations": { + "get": { + "summary": "List Named Locations", + "description": "Retrieves a list of named locations", + "tags": [ + "Tenant Conditional Access" + ], + "responses": { + "200": { + "description": "Named locations retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the named location" + }, + "displayName": { + "type": "string", + "description": "Display name" + }, + "@odata.type": { + "type": "string", + "description": "Type of named location" + }, + "includeUnknownCountriesAndRegions": { + "type": "boolean", + "description": "Whether to include unknown countries and regions" + }, + "isTrusted": { + "type": "boolean", + "description": "Whether the location is trusted" + }, + "rangeOrLocation": { + "type": "string", + "description": "Range or location" + }, + "modifiedDateTime": { + "type": "string", + "description": "When the named location was last modified" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecNamedLocation": { + "post": { + "summary": "Execute Named Location Operation", + "description": "Executes an operation on a named location", + "tags": [ + "Tenant Conditional Access" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "namedLocationId", + "change", + "input" + ], + "properties": { + "namedLocationId": { + "type": "string", + "description": "Named location ID" + }, + "change": { + "type": "string", + "description": "Change to make", + "enum": ["addLocation", "removeLocation", "addIp", "removeIp"] + }, + "input": { + "type": "string", + "description": "Input value (country code or IP)" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Named location operation executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddNamedLocation": { + "post": { + "summary": "Add Named Location", + "description": "Adds a new named location", + "tags": [ + "Tenant Conditional Access" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "description": "Display name" + }, + "locationType": { + "type": "string", + "description": "Type of location", + "enum": ["country", "ip"] + }, + "countriesAndRegions": { + "type": "array", + "description": "List of countries and regions", + "items": { + "type": "string" + } + }, + "includeUnknownCountriesAndRegions": { + "type": "boolean", + "description": "Whether to include unknown countries and regions" + }, + "ipRanges": { + "type": "array", + "description": "List of IP ranges", + "items": { + "type": "object", + "properties": { + "cidrAddress": { + "type": "string", + "description": "CIDR address" + } + } + } + }, + "isTrusted": { + "type": "boolean", + "description": "Whether the location is trusted" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Named location added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListConditionalAccessPolicies": { + "get": { + "summary": "List Conditional Access Policies", + "description": "Retrieves a list of conditional access policies", + "tags": [ + "Tenant Conditional Access" + ], + "responses": { + "200": { + "description": "Conditional access policies retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the policy" + }, + "displayName": { + "type": "string", + "description": "Display name" + }, + "state": { + "type": "string", + "description": "State of the policy" + }, + "conditions": { + "type": "object", + "description": "Conditions for the policy" + }, + "grantControls": { + "type": "object", + "description": "Grant controls for the policy" + }, + "sessionControls": { + "type": "object", + "description": "Session controls for the policy" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecCAExclusion": { + "post": { + "summary": "Execute Conditional Access Exclusion", + "description": "Executes a conditional access exclusion", + "tags": [ + "Tenant Conditional Access" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenantFilter": { + "type": "object", + "description": "Tenant filter", + "properties": { + "value": { + "type": "string", + "description": "Tenant ID" + }, + "label": { + "type": "string", + "description": "Tenant name" + } + } + }, + "user": { + "type": "object", + "description": "User to exclude", + "properties": { + "value": { + "type": "string", + "description": "User ID" + }, + "label": { + "type": "string", + "description": "User name" + } + } + }, + "policy": { + "type": "object", + "description": "Policy to exclude from", + "properties": { + "value": { + "type": "string", + "description": "Policy ID" + }, + "label": { + "type": "string", + "description": "Policy name" + } + } + }, + "startDate": { + "type": "string", + "description": "Start date for the exclusion" + }, + "endDate": { + "type": "string", + "description": "End date for the exclusion" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Conditional access exclusion executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListGraphRequest": { + "get": { + "summary": "List Graph Request", + "description": "Retrieves data from Microsoft Graph API", + "tags": [ + "Tenant Administration" + ], + "parameters": [ + { + "name": "Endpoint", + "in": "query", + "description": "Graph API endpoint", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "$select", + "in": "query", + "description": "Fields to select", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "$count", + "in": "query", + "description": "Whether to include count", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "$top", + "in": "query", + "description": "Number of results to return", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Graph request executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListAuditLogs": { + "get": { + "summary": "List Audit Logs", + "description": "Retrieves a list of audit logs", + "tags": [ + "Tenant Administration" + ], + "parameters": [ + { + "name": "RelativeTime", + "in": "query", + "description": "Relative time filter", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "StartDate", + "in": "query", + "description": "Start date filter", + "required": false, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "EndDate", + "in": "query", + "description": "End date filter", + "required": false, + "schema": { + "type": "string", + "format": "date" + } + } + ], + "responses": { + "200": { + "description": "Audit logs retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Timestamp": { + "type": "string", + "description": "When the log entry was created" + }, + "Tenant": { + "type": "string", + "description": "Tenant name" + }, + "Title": { + "type": "string", + "description": "Log title" + }, + "LogId": { + "type": "string", + "description": "Unique identifier for the log" + }, + "Actions": { + "type": "string", + "description": "Actions taken" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecListBackup": { + "get": { + "summary": "List Backups", + "description": "Retrieves a list of backups", + "tags": [ + "Tenant Backup" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "Tenant filter", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Backups retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "BackupName": { + "type": "string", + "description": "Name of the backup" + }, + "Timestamp": { + "type": "string", + "description": "When the backup was created" + }, + "Backup": { + "type": "string", + "description": "The backup data (JSON string)" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListCSPsku": { + "get": { + "summary": "List CSP SKUs", + "description": "Retrieves a list of CSP SKUs", + "tags": [ + "Tenant Administration" + ], + "responses": { + "200": { + "description": "CSP SKUs retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "SKU ID" + }, + "name": { + "type": "array", + "description": "SKU names", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "SKU name" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListCommunityRepos": { + "get": { + "summary": "List Community Repositories", + "description": "Retrieves a list of community repositories", + "tags": [ + "Tenant Standards" + ], + "parameters": [ + { + "name": "WriteAccess", + "in": "query", + "description": "Whether to only include repositories with write access", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Community repositories retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FullName": { + "type": "string", + "description": "Full name of the repository" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecCommunityRepo": { + "post": { + "summary": "Execute Community Repository Operation", + "description": "Executes an operation on a community repository", + "tags": [ + "Tenant Standards" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "Action", + "GUID", + "FullName", + "Message" + ], + "properties": { + "Action": { + "type": "string", + "description": "Action to perform", + "enum": ["UploadTemplate"] + }, + "GUID": { + "type": "string", + "description": "Template GUID" + }, + "FullName": { + "type": "string", + "description": "Repository full name" + }, + "Message": { + "type": "string", + "description": "Commit message" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Community repository operation executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListExtensionsConfig": { + "get": { + "summary": "List Extensions Configuration", + "description": "Retrieves the configuration for extensions", + "tags": [ + "Tenant Administration" + ], + "responses": { + "200": { + "description": "Extensions configuration retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether the extension is enabled" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + } + } +} \ No newline at end of file diff --git a/Tools/tools-openapispec.json b/Tools/tools-openapispec.json new file mode 100644 index 000000000000..7d85f7a40e4a --- /dev/null +++ b/Tools/tools-openapispec.json @@ -0,0 +1,665 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CIPP Tools API", + "description": "API for Tools in Cloud Identity and Policy Platform (CIPP)", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/api", + "description": "CIPP API Server" + } + ], + "components": { + "schemas": { + "StandardResponse": { + "type": "object", + "properties": { + "Results": { + "type": "object", + "description": "The results of the operation" + } + } + } + } + }, + "paths": { + "/ListBreachesAccount": { + "get": { + "summary": "List Breaches for Account", + "description": "Retrieves a list of breaches for a specific account (email or domain)", + "tags": [ + "Breach Lookup" + ], + "parameters": [ + { + "name": "account", + "in": "query", + "description": "Email address or domain name to check for breaches", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Breaches retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Title": { + "type": "string", + "description": "Title of the breach" + }, + "LogoPath": { + "type": "string", + "description": "Path to the breach logo" + }, + "password": { + "type": "string", + "description": "Partial password if available" + }, + "Description": { + "type": "string", + "description": "Description of the breach" + }, + "Domain": { + "type": "string", + "description": "Domain associated with the breach" + }, + "DataClasses": { + "type": "array", + "description": "Types of data leaked in the breach", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListBreachesTenant": { + "get": { + "summary": "List Breaches for Tenant", + "description": "Retrieves a list of breaches for a specific tenant", + "tags": [ + "Breach Lookup" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "Tenant ID to filter breaches", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Tenant breaches retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "Email address associated with the breach" + }, + "password": { + "type": "string", + "description": "Partial password if available" + }, + "sources": { + "type": "string", + "description": "Sources of the breach data" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecBreachSearch": { + "get": { + "summary": "Execute Breach Search", + "description": "Executes a breach search for a tenant", + "tags": [ + "Breach Lookup" + ], + "parameters": [ + { + "name": "tenantFilter", + "in": "query", + "description": "Tenant ID to search for breaches", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Breach search executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListExtensionsConfig": { + "get": { + "summary": "List Extensions Configuration", + "description": "Retrieves the configuration for extensions", + "tags": [ + "Community Repositories" + ], + "responses": { + "200": { + "description": "Extensions configuration retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "GitHub": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether GitHub integration is enabled" + } + } + } + }, + "additionalProperties": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean", + "description": "Whether the extension is enabled" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecGitHubAction": { + "get": { + "summary": "Execute GitHub Action (GET)", + "description": "Executes a GitHub action with GET method", + "tags": [ + "Community Repositories" + ], + "parameters": [ + { + "name": "Action", + "in": "query", + "description": "Action to perform", + "required": true, + "schema": { + "type": "string", + "enum": ["GetBranches", "GetFileTree", "GetFileContents", "GetOrgs"] + } + }, + { + "name": "FullName", + "in": "query", + "description": "Full name of the repository (owner/repo)", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Branch", + "in": "query", + "description": "Branch name", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "Path", + "in": "query", + "description": "File path", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "GitHub action executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + }, + "post": { + "summary": "Execute GitHub Action (POST)", + "description": "Executes a GitHub action with POST method", + "tags": [ + "Community Repositories" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "Action" + ], + "properties": { + "Action": { + "type": "string", + "description": "Action to perform", + "enum": ["CreateRepo", "Search", "GetFileContents"] + }, + "Type": { + "type": "string", + "description": "Type of repository (user or org)", + "enum": ["user", "org", "repositories"] + }, + "Name": { + "type": "string", + "description": "Repository name" + }, + "Org": { + "type": "string", + "description": "Organization name" + }, + "Description": { + "type": "string", + "description": "Repository description" + }, + "Private": { + "type": "boolean", + "description": "Whether the repository is private" + }, + "Repository": { + "type": "string", + "description": "Repository name for search" + }, + "User": { + "type": "string", + "description": "User name for search" + }, + "SearchTerm": { + "type": "array", + "description": "Search terms", + "items": { + "type": "string" + } + }, + "FullName": { + "type": "string", + "description": "Full name of the repository (owner/repo)" + }, + "Path": { + "type": "string", + "description": "File path" + }, + "Branch": { + "type": "string", + "description": "Branch name" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "GitHub action executed successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ListCommunityRepos": { + "get": { + "summary": "List Community Repositories", + "description": "Retrieves a list of community repositories", + "tags": [ + "Community Repositories" + ], + "responses": { + "200": { + "description": "Community repositories retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "Results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Repository name" + }, + "Owner": { + "type": "string", + "description": "Repository owner" + }, + "URL": { + "type": "string", + "description": "Repository URL" + }, + "Visibility": { + "type": "string", + "description": "Repository visibility (public/private)" + }, + "WriteAccess": { + "type": "boolean", + "description": "Whether the user has write access to the repository" + }, + "UploadBranch": { + "type": "string", + "description": "Branch used for uploads" + }, + "FullName": { + "type": "string", + "description": "Full name of the repository (owner/repo)" + }, + "DefaultBranch": { + "type": "string", + "description": "Default branch of the repository" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/ExecCommunityRepo": { + "post": { + "summary": "Execute Community Repository Operation", + "description": "Executes an operation on a community repository", + "tags": [ + "Community Repositories" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "Action" + ], + "properties": { + "Action": { + "type": "string", + "description": "Action to perform", + "enum": ["Add", "Delete", "SetBranch", "ImportTemplate"] + }, + "Id": { + "type": "string", + "description": "Repository ID" + }, + "Branch": { + "type": "string", + "description": "Branch name" + }, + "FullName": { + "type": "string", + "description": "Full name of the repository (owner/repo)" + }, + "Path": { + "type": "string", + "description": "File path for template import" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Community repository operation executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/AddScheduledItem": { + "post": { + "summary": "Add Scheduled Item", + "description": "Adds a new scheduled item", + "tags": [ + "Template Library" + ], + "parameters": [ + { + "name": "DisallowDuplicateName", + "in": "query", + "description": "Whether to disallow duplicate names", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "TenantFilter": { + "type": "string", + "description": "Tenant to apply the scheduled item to" + }, + "Name": { + "type": "string", + "description": "Name of the scheduled item" + }, + "Command": { + "type": "object", + "description": "Command to execute", + "properties": { + "value": { + "type": "string", + "description": "Command value" + } + } + }, + "Parameters": { + "type": "object", + "description": "Parameters for the command", + "properties": { + "TemplateSettings": { + "type": "object", + "description": "Template settings" + } + } + }, + "ScheduledTime": { + "type": "integer", + "description": "When the job is scheduled to run (Unix timestamp)" + }, + "Recurrence": { + "type": "object", + "description": "How often the job recurs", + "properties": { + "value": { + "type": "string", + "description": "Recurrence value" + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scheduled item added successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StandardResponse" + } + } + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + } + } + } + } +} \ No newline at end of file From 4b7aa1e911107e31609e17734a3669f18a4be4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 2 Jul 2025 22:58:23 +0200 Subject: [PATCH 045/125] Casing --- Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 index 933f6fbdb99e..1284db3c752d 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroups.ps1 @@ -2,50 +2,50 @@ function Remove-CIPPGroups { [CmdletBinding()] param( $Username, - $tenantFilter, + $TenantFilter, $APIName = 'Remove From Groups', $Headers, - $userid + $UserID ) if (-not $userid) { - $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Username)" -tenantid $Tenantfilter).id + $UserID = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Username)" -tenantid $TenantFilter).id } - $AllGroups = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/?`$select=displayName,mailEnabled,id,groupTypes,assignedLicenses&`$top=999" -tenantid $tenantFilter) + $AllGroups = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/?`$select=displayName,mailEnabled,id,groupTypes,assignedLicenses&`$top=999" -tenantid $TenantFilter) - $Returnval = (New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userid)/GetMemberGroups" -tenantid $tenantFilter -type POST -body '{"securityEnabledOnly": false}').value | ForEach-Object -Parallel { + $Returnval = (New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserID)/GetMemberGroups" -tenantid $TenantFilter -type POST -body '{"securityEnabledOnly": false}').value | ForEach-Object -Parallel { Import-Module '.\Modules\AzBobbyTables' Import-Module '.\Modules\CIPPCore' - $group = $_ + $Group = $_ try { - $Groupname = ($using:AllGroups | Where-Object -Property id -EQ $group).displayName - $IsMailEnabled = ($using:AllGroups | Where-Object -Property id -EQ $group).mailEnabled - $IsM365Group = $null -ne ($using:AllGroups | Where-Object { $_.id -eq $group -and $_.groupTypes -contains 'Unified' }) - $IsLicensed = ($using:AllGroups | Where-Object -Property id -EQ $group).assignedLicenses.Count -gt 0 + $GroupName = ($using:AllGroups | Where-Object -Property id -EQ $Group).displayName + $IsMailEnabled = ($using:AllGroups | Where-Object -Property id -EQ $Group).mailEnabled + $IsM365Group = $null -ne ($using:AllGroups | Where-Object { $_.id -eq $Group -and $_.groupTypes -contains 'Unified' }) + $IsLicensed = ($using:AllGroups | Where-Object -Property id -EQ $Group).assignedLicenses.Count -gt 0 if ($IsLicensed) { - "Could not remove $($using:Username) from $Groupname. This is because the group has licenses assigned to it." + "Could not remove $($using:Username) from $GroupName. This is because the group has licenses assigned to it." } else { if ($IsM365Group) { - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:userid)/`$ref" -tenantid $using:tenantFilter -type DELETE -body '' -Verbose + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:UserID)/`$ref" -tenantid $using:TenantFilter -type DELETE -body '' -Verbose } elseif (-not $IsMailEnabled) { - $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:userid)/`$ref" -tenantid $using:tenantFilter -type DELETE -body '' -Verbose + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$_/members/$($using:UserID)/`$ref" -tenantid $using:TenantFilter -type DELETE -body '' -Verbose } elseif ($IsMailEnabled) { - $Params = @{ Identity = $Groupname; Member = $using:userid ; BypassSecurityGroupManagerCheck = $true } + $Params = @{ Identity = $GroupName; Member = $using:UserID ; BypassSecurityGroupManagerCheck = $true } New-ExoRequest -tenantid $using:tenantFilter -cmdlet 'Remove-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true } - Write-LogMessage -headers $using:Headers -API $($using:APIName) -message "Removed $($using:Username) from $groupname" -Sev 'Info' -tenant $using:TenantFilter - "Successfully removed $($using:Username) from group $Groupname" + Write-LogMessage -headers $using:Headers -API $($using:APIName) -message "Removed $($using:Username) from $GroupName" -Sev 'Info' -tenant $using:TenantFilter + "Successfully removed $($using:Username) from group $GroupName" } } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -headers $using:Headers -API $($using:APIName) -message "Could not remove $($using:Username) from group $groupname : $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $using:TenantFilter -LogData $ErrorMessage - "Could not remove $($using:Username) from group $($Groupname): $($ErrorMessage.NormalizedError). This is likely because its a Dynamic Group or synched with active directory" + Write-LogMessage -headers $using:Headers -API $($using:APIName) -message "Could not remove $($using:Username) from group $GroupName : $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $using:TenantFilter -LogData $ErrorMessage + "Could not remove $($using:Username) from group $($GroupName): $($ErrorMessage.NormalizedError). This is likely because its a Dynamic Group or synched with active directory" } } - if (!$Returnval) { + if (-not $Returnval) { $Returnval = "$($Username) is not a member of any groups." Write-LogMessage -headers $Headers -API $APIName -message "$($Username) is not a member of any groups" -Sev 'Info' -tenant $TenantFilter } From 3286d8349d8e6878535d1859436286ad20c01f98 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 2 Jul 2025 23:08:57 +0200 Subject: [PATCH 046/125] update template editor --- .../HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 index a7e6f3f176df..96bb985840b9 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecEditTemplate.ps1 @@ -16,7 +16,7 @@ function Invoke-ExecEditTemplate { try { $Table = Get-CippTable -tablename 'templates' - $guid = $request.body.guid + $guid = $request.body.id $JSON = ConvertTo-Json -Compress -Depth 100 -InputObject ($request.body | Select-Object * -ExcludeProperty GUID) $Type = $request.query.Type @@ -24,7 +24,7 @@ function Invoke-ExecEditTemplate { Write-Host 'Intune Template' $OriginalTemplate = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'IntuneTemplate' and RowKey eq '$GUID'" $OriginalTemplate = ($OriginalTemplate.JSON | ConvertFrom-Json -Depth 100) - $RawJSON = $OriginalTemplate.RAWJson + $RawJSON = ConvertTo-Json -Compress -Depth 100 -InputObject $Request.body.parsedRAWJson Set-CIPPIntuneTemplate -RawJSON $RawJSON -GUID $GUID -DisplayName $Request.body.displayName -Description $Request.body.description -templateType $OriginalTemplate.Type -Headers $Request.Headers } else { $Table.Force = $true From 65f45970ab329fdab44f4d91c9fcac896fd17eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 3 Jul 2025 00:46:40 +0200 Subject: [PATCH 047/125] Refactor Invoke-EditUser to streamline result handling and improve logging consistency --- .../Administration/Users/Invoke-EditUser.ps1 | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 index 5b985cf0817e..f5fd50105a6d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-EditUser.ps1 @@ -148,19 +148,19 @@ function Invoke-EditUser { } $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)" -tenantid $UserObj.tenantFilter -type 'patch' -body "{`"mail`": `"$UserPrincipalName`"}" -Verbose Write-LogMessage -API $APIName -tenant ($UserObj.tenantFilter) -headers $Headers -message "Added Aliases to $($UserObj.DisplayName)" -Sev Info - $null = $Results.Add( 'Success. Added aliases to user.') + $Results.Add( 'Success. Added aliases to user.') } } catch { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to add aliases to user $($UserObj.DisplayName). Error: $($ErrorMessage.NormalizedError)" Write-LogMessage -API $APIName -tenant ($UserObj.tenantFilter) -headers $Headers -message $Message -Sev Error -LogData $ErrorMessage - $null = $Results.Add($Message) + $Results.Add($Message) } if ($Request.Body.CopyFrom.value) { $CopyFrom = Set-CIPPCopyGroupMembers -Headers $Headers -CopyFromId $Request.Body.CopyFrom.value -UserID $UserPrincipalName -TenantFilter $UserObj.tenantFilter - $null = $Results.AddRange(@($CopyFrom)) + $Results.AddRange(@($CopyFrom)) } if ($AddToGroups) { @@ -185,12 +185,12 @@ function Invoke-EditUser { $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$GroupID/members/`$ref" -tenantid $UserObj.tenantFilter -type POST -body $UserBodyJSON -Verbose } Write-LogMessage -headers $Headers -API $APIName -tenant $UserObj.tenantFilter -message "Added $($UserObj.DisplayName) to $GroupName group" -Sev Info - $null = $Results.Add("Success. $($UserObj.DisplayName) has been added to $GroupName") + $Results.Add("Success. $($UserObj.DisplayName) has been added to $GroupName") } catch { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to add member $($UserObj.DisplayName) to $GroupName. Error: $($ErrorMessage.NormalizedError)" Write-LogMessage -headers $Headers -API $APIName -tenant $UserObj.tenantFilter -message $Message -Sev Error -LogData $ErrorMessage - $null = $Results.Add($Message) + $Results.Add($Message) } } } @@ -213,12 +213,12 @@ function Invoke-EditUser { $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$GroupID/members/$($UserObj.id)/`$ref" -tenantid $UserObj.tenantFilter -type DELETE } Write-LogMessage -headers $Headers -API $APIName -tenant $UserObj.tenantFilter -message "Removed $($UserObj.DisplayName) from $GroupName group" -Sev Info - $null = $Results.Add("Success. $($UserObj.DisplayName) has been removed from $GroupName") + $Results.Add("Success. $($UserObj.DisplayName) has been removed from $GroupName") } catch { $ErrorMessage = Get-CippException -Exception $_ $Message = "Failed to remove member $($UserObj.DisplayName) from $GroupName. Error: $($ErrorMessage.NormalizedError)" Write-LogMessage -headers $Headers -API $APIName -tenant $UserObj.tenantFilter -message $Message -Sev Error -LogData $ErrorMessage - $null = $Results.Add($Message) + $Results.Add($Message) } } } @@ -228,7 +228,7 @@ function Invoke-EditUser { $ManagerBodyJSON = ConvertTo-Json -Compress -Depth 10 -InputObject $ManagerBody $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)/manager/`$ref" -tenantid $UserObj.tenantFilter -type PUT -body $ManagerBodyJSON -Verbose Write-LogMessage -headers $Headers -API $APIName -tenant $UserObj.tenantFilter -message "Set $($UserObj.DisplayName)'s manager to $($Request.body.setManager.label)" -Sev Info - $null = $Results.Add("Success. Set $($UserObj.DisplayName)'s manager to $($Request.body.setManager.label)") + $Results.Add("Success. Set $($UserObj.DisplayName)'s manager to $($Request.body.setManager.label)") } if ($Request.body.setSponsor.value) { @@ -236,14 +236,13 @@ function Invoke-EditUser { $SponsorBodyJSON = ConvertTo-Json -Compress -Depth 10 -InputObject $SponsorBody $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($UserObj.id)/sponsors/`$ref" -tenantid $UserObj.tenantFilter -type POST -body $SponsorBodyJSON -Verbose Write-LogMessage -headers $Headers -API $APIName -tenant $UserObj.tenantFilter -message "Set $($UserObj.DisplayName)'s sponsor to $($Request.body.setSponsor.label)" -Sev Info - $null = $Results.Add("Success. Set $($UserObj.DisplayName)'s sponsor to $($Request.body.setSponsor.label)") + $Results.Add("Success. Set $($UserObj.DisplayName)'s sponsor to $($Request.body.setSponsor.label)") } - $body = @{'Results' = @($results) } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $Body + Body = @{'Results' = @($Results) } }) } From 9cc2fbef467b70d3e94080b5da22f8eaad5dbe5c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 16:24:32 -0400 Subject: [PATCH 048/125] fix tenant access check --- Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 index bae5f70793b0..561568426189 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessTenant.ps1 @@ -106,7 +106,7 @@ function Test-CIPPAccessTenant { try { $null = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-OrganizationConfig' -ErrorAction Stop - $OrgManagementRoles = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-ManagementRoleAssignment' -cmdParams @{ RoleAssignee = 'Organization Management'; Delegating = $false } | Select-Object -Property Role, Guid + $OrgManagementRoles = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-ManagementRoleAssignment' -cmdParams @{ Delegating = $false } | Where-Object { $_.RoleAssigneeName -eq 'Organization Management' } | Select-Object -Property Role, Guid Write-Information "Found $($OrgManagementRoles.Count) Organization Management roles in Exchange" $Results.OrgManagementRoles = $OrgManagementRoles From fb00fcd70f73b70037e06bcf5cb522a38067f710 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 16:39:52 -0400 Subject: [PATCH 049/125] be more explicit with principalId --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index 76cea5d8a031..3e35fbb36e23 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -28,6 +28,8 @@ function Invoke-ExecExchangeRoleRepair { Write-Information "Found $($AvailableRoles.Count) available Organization Management roles in Exchange" $MissingOrgMgmtRoles = $AvailableRoles | Where-Object { $OrgManagementRoles.Role -notcontains $_.displayName } + $Group = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-Group' | Where-Object { $_.Identity -eq 'Organization Management' } | Select-Object -First 1 + if ($MissingOrgMgmtRoles.Count -gt 0) { $Requests = foreach ($Role in $MissingOrgMgmtRoles) { [PSCustomObject]@{ @@ -35,7 +37,7 @@ function Invoke-ExecExchangeRoleRepair { method = 'POST' url = 'roleManagement/exchange/roleAssignments' body = @{ - principalId = '/RoleGroups/Organization Management' + principalId = $Group.Guid roleDefinitionId = $Role.id directoryScopeId = '/' appScopeId = $null From 3b25067695e711e6f7b6f6068708d8bf817f812e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 16:47:41 -0400 Subject: [PATCH 050/125] Update Invoke-ExecExchangeRoleRepair.ps1 --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index 3e35fbb36e23..f0214effdef6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -15,7 +15,7 @@ function Invoke-ExecExchangeRoleRepair { try { Write-Information "Starting Exchange Organization Management role repair for tenant: $($Tenant.defaultDomainName)" - $OrgManagementRoles = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-ManagementRoleAssignment' -cmdParams @{ RoleAssignee = 'Organization Management'; Delegating = $false } | Select-Object -Property Role, Guid + $OrgManagementRoles = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-ManagementRoleAssignment' -cmdParams @{ Delegating = $false } | Where-Object { $_.RoleAssigneeName -eq 'Organization Management' } | Select-Object -Property Role, Guid Write-Information "Found $($OrgManagementRoles.Count) Organization Management roles in Exchange" $RoleDefinitions = New-GraphGetRequest -tenantid $Tenant.customerId -uri 'https://graph.microsoft.com/beta/roleManagement/exchange/roleDefinitions' @@ -37,7 +37,7 @@ function Invoke-ExecExchangeRoleRepair { method = 'POST' url = 'roleManagement/exchange/roleAssignments' body = @{ - principalId = $Group.Guid + principalId = '/RoleGroups/Organization Management' roleDefinitionId = $Role.id directoryScopeId = '/' appScopeId = $null From 84a5e46e1d2595aeeb6e68618aace5f3a6b360fd Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 16:47:51 -0400 Subject: [PATCH 051/125] Update Invoke-ExecExchangeRoleRepair.ps1 --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index f0214effdef6..7baf15a19341 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -28,8 +28,6 @@ function Invoke-ExecExchangeRoleRepair { Write-Information "Found $($AvailableRoles.Count) available Organization Management roles in Exchange" $MissingOrgMgmtRoles = $AvailableRoles | Where-Object { $OrgManagementRoles.Role -notcontains $_.displayName } - $Group = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-Group' | Where-Object { $_.Identity -eq 'Organization Management' } | Select-Object -First 1 - if ($MissingOrgMgmtRoles.Count -gt 0) { $Requests = foreach ($Role in $MissingOrgMgmtRoles) { [PSCustomObject]@{ From 1fd7eee3c8a81e557eddf8ba2c4718f1f6c536ed Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 17:11:33 -0400 Subject: [PATCH 052/125] Update Invoke-ExecPermissionRepair.ps1 --- .../HTTP Functions/CIPP/Settings/Invoke-ExecPermissionRepair.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPermissionRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPermissionRepair.ps1 index 499d31a60a22..c332ff12cd8b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPermissionRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecPermissionRepair.ps1 @@ -26,6 +26,7 @@ function Invoke-ExecPermissionRepair { $NewPermissions = @{} foreach ($AppId in $AppIds) { + if (!$AppId) { continue } $ApplicationPermissions = [system.collections.generic.list[object]]::new() $DelegatedPermissions = [system.collections.generic.list[object]]::new() From e1b56f3745c27a70b7da37a74a74146e4abd9792 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 17:18:23 -0400 Subject: [PATCH 053/125] update logging --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index 7baf15a19341..df6a681cbcdf 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -53,7 +53,7 @@ function Invoke-ExecExchangeRoleRepair { state = 'success' resultText = "Successfully repaired the missing Organization Management roles: $($MissingOrgMgmtRoles.displayName -join ', ')" } - Write-LogMessage -headers $Headers -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -Message "Successfully repaired the missing Organization Management roles: $($MissingOrgMgmtRoles.displayName -join ', '). Run another Tenant Access check after waiting a bit for replication." -sev 'Info' + Write-LogMessage -API 'ExecExchangeRoleRepair' -headers $Headers -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -Message "Successfully repaired the missing Organization Management roles: $($MissingOrgMgmtRoles.displayName -join ', '). Run another Tenant Access check after waiting a bit for replication." -sev 'Info' } else { # Get roles that failed to repair $FailedRoles = $RepairResults | Where-Object { $_.status -ne 201 } | ForEach-Object { @@ -64,12 +64,14 @@ function Invoke-ExecExchangeRoleRepair { $PermissionError = $false if ($RepairResults.status -in (401, 403, 500)) { $PermissionError = $true + $LogData = $RepairResults | Where-Object { $_.status -in (401, 403, 500) } | Select-Object -Property id, status, error } $Results = @{ state = 'error' resultText = "Failed to repair the missing Organization Management roles: $($FailedRoles -join ', ').$(if ($PermissionError) { " This may be due to insufficient permissions. The required Graph Permission is 'Application - RoleManagement.ReadWrite.Exchange'" })" } - Write-LogMessage -headers $Headers -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -Message "Failed to repair the missing Organization Management roles: $($FailedRoles -join ', ')" -sev 'Error' + Write-LogMessage -API 'ExecExchangeRoleRepair' -headers $Headers -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -Message "Failed to repair the missing Organization Management roles: $($FailedRoles -join ', ')" -sev 'Error' -LogData $LogData + Write-Warning 'Exchange role repair failed' } } else { $Results = @{ @@ -80,7 +82,7 @@ function Invoke-ExecExchangeRoleRepair { } catch { $ErrorMessage = Get-CippException -Exception $_ Write-Warning "Exception during Exchange Organization Management role repair: $($ErrorMessage.NormalizedError)" - Write-LogMessage -headers $Headers -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -Message "Exchange Organization Management role repair failed: $($ErrorMessage.NormalizedError)" -sev 'Error' -LogData $ErrorMessage + Write-LogMessage -API 'ExecExchangeRoleRepair' -headers $Headers -tenant $Tenant.defaultDomainName -tenantid $Tenant.customerId -Message "Exchange Organization Management role repair failed: $($ErrorMessage.NormalizedError)" -sev 'Error' -LogData $ErrorMessage $Results = @{ state = 'error' resultText = "Exchange Organization Management role repair failed: $($ErrorMessage.NormalizedError)" From 2ad9663e2d7ab8b5bc4f095a46441d06e9c870ba Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 17:23:25 -0400 Subject: [PATCH 054/125] Update Invoke-ExecExchangeRoleRepair.ps1 --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index df6a681cbcdf..2c892fdb5c3a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -28,6 +28,8 @@ function Invoke-ExecExchangeRoleRepair { Write-Information "Found $($AvailableRoles.Count) available Organization Management roles in Exchange" $MissingOrgMgmtRoles = $AvailableRoles | Where-Object { $OrgManagementRoles.Role -notcontains $_.displayName } + $Group = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-RoleGroup' -cmdParams @{ Identity = 'Organization Management' } + if ($MissingOrgMgmtRoles.Count -gt 0) { $Requests = foreach ($Role in $MissingOrgMgmtRoles) { [PSCustomObject]@{ @@ -35,7 +37,7 @@ function Invoke-ExecExchangeRoleRepair { method = 'POST' url = 'roleManagement/exchange/roleAssignments' body = @{ - principalId = '/RoleGroups/Organization Management' + principalId = $Group.Guid roleDefinitionId = $Role.id directoryScopeId = '/' appScopeId = $null From dda550243ffc866bdb5fb7522574f3454e406bf8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 17:23:48 -0400 Subject: [PATCH 055/125] Update Invoke-ExecExchangeRoleRepair.ps1 --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index 2c892fdb5c3a..8037d661a8aa 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -66,7 +66,7 @@ function Invoke-ExecExchangeRoleRepair { $PermissionError = $false if ($RepairResults.status -in (401, 403, 500)) { $PermissionError = $true - $LogData = $RepairResults | Where-Object { $_.status -in (401, 403, 500) } | Select-Object -Property id, status, error + $LogData = $RepairResults | Where-Object { $_.status -in (401, 403, 500) } | Select-Object -Property id, status, body } $Results = @{ state = 'error' From d40c2bc54543f42c432c122381f901a308f0dde2 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 17:29:53 -0400 Subject: [PATCH 056/125] Update Invoke-ExecExchangeRoleRepair.ps1 --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index 8037d661a8aa..f5bf376aeee7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -37,7 +37,7 @@ function Invoke-ExecExchangeRoleRepair { method = 'POST' url = 'roleManagement/exchange/roleAssignments' body = @{ - principalId = $Group.Guid + principalId = $Group.ExternalDirectoryObjectId roleDefinitionId = $Role.id directoryScopeId = '/' appScopeId = $null @@ -66,8 +66,8 @@ function Invoke-ExecExchangeRoleRepair { $PermissionError = $false if ($RepairResults.status -in (401, 403, 500)) { $PermissionError = $true - $LogData = $RepairResults | Where-Object { $_.status -in (401, 403, 500) } | Select-Object -Property id, status, body } + $LogData = $RepairResults | Where-Object { $_.status -in (401, 403, 500) } | Select-Object -Property id, status, body $Results = @{ state = 'error' resultText = "Failed to repair the missing Organization Management roles: $($FailedRoles -join ', ').$(if ($PermissionError) { " This may be due to insufficient permissions. The required Graph Permission is 'Application - RoleManagement.ReadWrite.Exchange'" })" From 360e058bd402498d3a5f4e93afbea31f10047d62 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 17:34:02 -0400 Subject: [PATCH 057/125] Update Invoke-ExecExchangeRoleRepair.ps1 --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index f5bf376aeee7..1f060aaddbcb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -67,7 +67,7 @@ function Invoke-ExecExchangeRoleRepair { if ($RepairResults.status -in (401, 403, 500)) { $PermissionError = $true } - $LogData = $RepairResults | Where-Object { $_.status -in (401, 403, 500) } | Select-Object -Property id, status, body + $LogData = $RepairResults | Select-Object -Property id, status, body $Results = @{ state = 'error' resultText = "Failed to repair the missing Organization Management roles: $($FailedRoles -join ', ').$(if ($PermissionError) { " This may be due to insufficient permissions. The required Graph Permission is 'Application - RoleManagement.ReadWrite.Exchange'" })" From 5c84a00f4e7c26a35b4358b18c6d097a2ff35383 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 3 Jul 2025 17:38:05 -0400 Subject: [PATCH 058/125] Update Invoke-ExecExchangeRoleRepair.ps1 --- .../CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 index 1f060aaddbcb..4ce8b2264706 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExchangeRoleRepair.ps1 @@ -28,8 +28,6 @@ function Invoke-ExecExchangeRoleRepair { Write-Information "Found $($AvailableRoles.Count) available Organization Management roles in Exchange" $MissingOrgMgmtRoles = $AvailableRoles | Where-Object { $OrgManagementRoles.Role -notcontains $_.displayName } - $Group = New-ExoRequest -tenantid $Tenant.customerId -cmdlet 'Get-RoleGroup' -cmdParams @{ Identity = 'Organization Management' } - if ($MissingOrgMgmtRoles.Count -gt 0) { $Requests = foreach ($Role in $MissingOrgMgmtRoles) { [PSCustomObject]@{ @@ -37,7 +35,7 @@ function Invoke-ExecExchangeRoleRepair { method = 'POST' url = 'roleManagement/exchange/roleAssignments' body = @{ - principalId = $Group.ExternalDirectoryObjectId + principalId = '/RoleGroups/Organization Management' roleDefinitionId = $Role.id directoryScopeId = '/' appScopeId = $null From 3572adf397db1e89ffc6d6d18381744571aeec4d Mon Sep 17 00:00:00 2001 From: Zac Richards <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:32:09 +0800 Subject: [PATCH 059/125] Improve error handling in BEC remediation and PwPush link creation Enhanced the Invoke-ExecBECRemediate workflow with granular error handling and detailed result reporting for each remediation step. Updated Set-CIPPResetPassword and New-PwPushLink to robustly handle failures in PwPush link creation, ensuring fallback to plain password and improved logging. These changes increase reliability and provide clearer feedback during user remediation operations. --- .../Users/Invoke-ExecBECRemediate.ps1 | 190 +++++++++++++++--- .../CIPPCore/Public/Set-CIPPResetPassword.ps1 | 12 +- .../Public/PwPush/New-PwPushLink.ps1 | 40 +++- 3 files changed, 205 insertions(+), 37 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 index bbc1746c1b57..183cf21d34ea 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 @@ -19,54 +19,188 @@ function Invoke-ExecBECRemediate { $Username = $Request.Body.username Write-Host $TenantFilter Write-Host $SuspectUser + $Results = try { + $AllResults = [System.Collections.Generic.List[object]]::new() + + # Step 1: Reset Password $Step = 'Reset Password' - Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers + try { + $PasswordResult = Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers + $AllResults.Add($PasswordResult) + } catch { + $AllResults.Add([pscustomobject]@{ + resultText = "Failed to reset password: $($_.Exception.Message)" + state = 'error' + }) + } + + # Step 2: Disable Account $Step = 'Disable Account' - Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers + try { + $DisableResult = Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers + $AllResults.Add([pscustomobject]@{ + resultText = $DisableResult + state = if ($DisableResult -like "*WARNING*") { 'warning' } else { 'success' } + }) + } catch { + $AllResults.Add([pscustomobject]@{ + resultText = "Failed to disable account: $($_.Exception.Message)" + state = 'error' + }) + } + + # Step 3: Revoke Sessions $Step = 'Revoke Sessions' - Revoke-CIPPSessions -userid $SuspectUser -username $Username -Headers $Headers -APIName $APIName -tenantFilter $TenantFilter + try { + $SessionResult = Revoke-CIPPSessions -userid $SuspectUser -username $Username -Headers $Headers -APIName $APIName -tenantFilter $TenantFilter + $AllResults.Add([pscustomobject]@{ + resultText = $SessionResult + state = if ($SessionResult -like "*Failed*") { 'error' } else { 'success' } + }) + } catch { + $AllResults.Add([pscustomobject]@{ + resultText = "Failed to revoke sessions: $($_.Exception.Message)" + state = 'error' + }) + } + + # Step 4: Remove MFA methods $Step = 'Remove MFA methods' - Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers + try { + $MFAResult = Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers + $AllResults.Add([pscustomobject]@{ + resultText = $MFAResult + state = if ($MFAResult -like "*No MFA methods*") { 'info' } elseif ($MFAResult -like "*Successfully*") { 'success' } else { 'error' } + }) + } catch { + $AllResults.Add([pscustomobject]@{ + resultText = "Failed to remove MFA methods: $($_.Exception.Message)" + state = 'error' + }) + } + + # Step 5: Disable Inbox Rules $Step = 'Disable Inbox Rules' - $Rules = New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $Username; IncludeHidden = $true } - $RuleDisabled = 0 - $RuleFailed = 0 - if (($Rules | Measure-Object).Count -gt 0) { - $Rules | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object { - try { - Set-CIPPMailboxRule -Username $Username -TenantFilter $TenantFilter -RuleId $_.Identity -RuleName $_.Name -Disable -APIName $APIName -Headers $Headers - $RuleDisabled++ - } catch { - $_.Exception.Message - $RuleFailed++ + try { + Write-LogMessage -headers $Headers -API $APIName -message "Starting inbox rules processing for user: $Username" -Sev 'Info' -tenant $TenantFilter + $Rules = New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $Username; IncludeHidden = $true } + Write-LogMessage -headers $Headers -API $APIName -message "Retrieved $(($Rules | Measure-Object).Count) total rules for $Username" -Sev 'Info' -tenant $TenantFilter + $RuleDisabled = 0 + $RuleFailed = 0 + $RuleMessages = [System.Collections.Generic.List[string]]::new() + + if (($Rules | Measure-Object).Count -eq 0) { + # No rules exist at all + $AllResults.Add([pscustomobject]@{ + resultText = "No Inbox Rules found for $Username." + state = 'info' + }) + } else { + # Rules exist, filter and process them + $ProcessableRules = $Rules | Where-Object { + $_.Name -ne 'Junk E-Mail Rule' -and + $_.Name -notlike 'Microsoft.Exchange.OOF.*' -and + -not ( + # Only skip legitimate system delegate rules (exact pattern + legacy identity) + $_.Name -match '^Delegate Rule -\d+$' -and # Exact: "Delegate Rule -[numbers]" + $_.Identity -match '\\' -and # Has backslash (legacy format) + $_.Identity -notmatch '@' # No @ symbol + ) + } + + if (($ProcessableRules | Measure-Object).Count -eq 0) { + # Rules exist but none are processable after filtering + $SystemRulesCount = ($Rules | Measure-Object).Count - $DelegateRulesSkipped + if ($SystemRulesCount -gt 0) { + $AllResults.Add([pscustomobject]@{ + resultText = "Found $(($Rules | Measure-Object).Count) inbox rules for $Username, but none require disabling (only system rules found)." + state = 'info' + }) + } + } else { + # Process the filterable rules + $ProcessableRules | ForEach-Object { + $CurrentRule = $_ + Write-LogMessage -headers $Headers -API $APIName -message "Processing rule: Name='$($CurrentRule.Name)', Identity='$($CurrentRule.Identity)'" -Sev 'Info' -tenant $TenantFilter + + try { + Set-CIPPMailboxRule -Username $Username -TenantFilter $TenantFilter -RuleId $CurrentRule.Identity -RuleName $CurrentRule.Name -Disable -APIName $APIName -Headers $Headers + + Write-LogMessage -headers $Headers -API $APIName -message "Successfully disabled rule: $($CurrentRule.Name)" -Sev 'Info' -tenant $TenantFilter + $RuleDisabled++ + } catch { + $ErrorMsg = "Could not disable rule '$($CurrentRule.Name)': $($_.Exception.Message)" + Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Error' -tenant $TenantFilter + $RuleMessages.Add($ErrorMsg) + $RuleFailed++ + } + } + + # Report results + if ($RuleDisabled -gt 0) { + $AllResults.Add([pscustomobject]@{ + resultText = "Successfully disabled $RuleDisabled inbox rules for $Username" + state = 'success' + }) + } + + if ($RuleFailed -gt 0) { + $AllResults.Add([pscustomobject]@{ + resultText = "Failed to process $RuleFailed inbox rules for $Username" + state = 'warning' + }) + + # Add individual rule failure messages as objects + foreach ($RuleMessage in $RuleMessages) { + $AllResults.Add([pscustomobject]@{ + resultText = $RuleMessage + state = 'error' + }) + } + } } } - } - if ($RuleDisabled -gt 0) { - "Disabled $RuleDisabled Inbox Rules for $Username" - } else { - "No Inbox Rules found for $Username. We have not disabled any rules." - } - if ($RuleFailed -gt 0) { - "Failed to disable $RuleFailed Inbox Rules for $Username" + $TotalProcessed = $RuleDisabled + $RuleFailed + Write-LogMessage -headers $Headers -API $APIName -message "Completed inbox rules processing for $Username. Total rules: $(($Rules | Measure-Object).Count), Processed: $TotalProcessed, Disabled: $RuleDisabled, Failed: $RuleFailed, System delegate rules skipped: $DelegateRulesSkipped" -Sev 'Info' -tenant $TenantFilter + + } catch { + $ErrorMsg = "Failed to process inbox rules: $($_.Exception.Message)" + Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Error' -tenant $TenantFilter + $AllResults.Add([pscustomobject]@{ + resultText = $ErrorMsg + state = 'error' + }) } + $StatusCode = [HttpStatusCode]::OK - Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username" -sev 'Info' -LogData @($Results) + Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username" -sev 'Info' -LogData @($AllResults) + + # Return the results array + $AllResults.ToArray() } catch { $ErrorMessage = Get-CippException -Exception $_ - $Results = [pscustomobject]@{'Results' = "Failed to execute remediation. $($ErrorMessage.NormalizedError)" } + $ErrorList = [System.Collections.Generic.List[object]]::new() + $ErrorList.Add([pscustomobject]@{ + resultText = "Failed to execute remediation at step '$Step'. $($ErrorMessage.NormalizedError)" + state = 'error' + }) Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username failed at the $Step step" -sev 'Error' -LogData $ErrorMessage $StatusCode = [HttpStatusCode]::InternalServerError + + # Return the error array + $ErrorList.ToArray() } - $Results = [pscustomobject]@{'Results' = @($Results) } - # Associate values to output bindings by calling 'Push-OutputBinding'. + # Create the final response structure + $ResponseBody = [pscustomobject]@{'Results' = @($Results) } + + # Associate values to output bindings Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $Results + Body = $ResponseBody }) } diff --git a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 index b206a7f12834..1285bbf1f402 100644 --- a/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPResetPassword.ps1 @@ -22,9 +22,15 @@ function Set-CIPPResetPassword { $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter -type PATCH -body $passwordProfile -verbose #PWPush - $PasswordLink = New-PwPushLink -Payload $password - if ($PasswordLink) { - $password = $PasswordLink + $PasswordLink = $null + try { + $PasswordLink = New-PwPushLink -Payload $password + if ($PasswordLink -and $PasswordLink -ne $false) { + $password = $PasswordLink + } + } + catch { + Write-LogMessage -headers $Headers -API $APIName -message "Failed to create PwPush link, using plain password. Error: $($_.Exception.Message)" -Sev 'Warning' -tenant $TenantFilter } Write-LogMessage -headers $Headers -API $APIName -message "Successfully reset the password for $DisplayName, $($UserID). User must change password is set to $forceChangePasswordNextSignIn" -Sev 'Info' -tenant $TenantFilter diff --git a/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 b/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 index 3d2744355e60..ad58593c9676 100644 --- a/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 +++ b/Modules/CippExtensions/Public/PwPush/New-PwPushLink.ps1 @@ -3,9 +3,35 @@ function New-PwPushLink { Param( $Payload ) - $Table = Get-CIPPTable -TableName Extensionsconfig - $Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json).PWPush - if ($Configuration.Enabled -eq $true) { + + try { + $Table = Get-CIPPTable -TableName Extensionsconfig + $ConfigEntity = Get-CIPPAzDataTableEntity @Table + + # Check if the config entity exists and has a config property + if (-not $ConfigEntity -or [string]::IsNullOrEmpty($ConfigEntity.config)) { + return $false + } + + # Safely parse the JSON configuration + try { + $ParsedConfig = $ConfigEntity.config | ConvertFrom-Json -ErrorAction Stop + $Configuration = $ParsedConfig.PWPush + } catch { + return $false + } + + # Check if PWPush section exists in configuration + if (-not $Configuration) { + return $false + } + + # Check if PwPush is enabled + if ($Configuration.Enabled -ne $true) { + return $false + } + + # Proceed with creating the PwPush link try { Set-PwPushConfig -Configuration $Configuration $PushParams = @{ @@ -25,13 +51,15 @@ function New-PwPushLink { } } catch { $LogData = [PSCustomObject]@{ - 'Response' = $Link + 'Response' = if ($Link) { $Link } else { 'No response' } 'Exception' = Get-CippException -Exception $_ } Write-LogMessage -API PwPush -Message "Failed to create a new PwPush link: $($_.Exception.Message)" -Sev 'Error' -LogData $LogData - throw 'Failed to create a new PwPush link, check the log book for more details' + Write-LogMessage -API PwPush -Message "Continuing without PwPush link due to error" -Sev 'Warning' + return $false } - } else { + } catch { + Write-LogMessage -API PwPush -Message "Unexpected error in PwPush configuration handling: $($_.Exception.Message)" -Sev 'Error' return $false } } From 648ce3641adf5b0ab4c2d67de9ae9a9ee6c68f92 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 17:44:16 -0400 Subject: [PATCH 060/125] teams testing --- .../Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 | 6 ++++-- .../CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 index a35801474473..0e19160420f8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 @@ -16,13 +16,15 @@ function Invoke-ListTeamsVoice { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.tenantFilter - $TenantId = (Get-Tenants | Where-Object -Property defaultDomainName -EQ $TenantFilter).customerId + $TenantId = (Get-Tenants -TenantFilter $TenantFilter).customerId try { $Users = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999&`$select=id,userPrincipalName,displayName" -tenantid $TenantFilter) $Skip = 0 $GraphRequest = do { Write-Host "Getting page $Skip" - $data = (New-TeamsAPIGetRequest -uri "https://api.interfaces.records.teams.microsoft.com/Skype.TelephoneNumberMgmt/Tenants/$($TenantId)/telephone-numbers?skip=$($Skip)&locale=en-US&top=999" -tenantid $TenantFilter).TelephoneNumbers | ForEach-Object { + $Results = New-TeamsAPIGetRequest -uri "https://api.interfaces.records.teams.microsoft.com/Skype.TelephoneNumberMgmt/Tenants/$($TenantId)/telephone-numbers?skip=$($Skip)&locale=en-US&top=999" -tenantid $TenantFilter + Write-Information ($Results | ConvertTo-Json -Depth 10) + $data = $Results.TelephoneNumbers | ForEach-Object { Write-Host 'Reached the loop' $CompleteRequest = $_ | Select-Object *, @{Name = 'AssignedTo'; Expression = { $users | Where-Object -Property id -EQ $_.TargetId } } if ($CompleteRequest.AcquisitionDate) { diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 8d3e99ce8892..4a1bbb2b50a0 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -18,6 +18,7 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 'X-Requested-With' = 'XMLHttpRequest' 'x-ms-tnm-applicationid' = '045268c0-445e-4ac1-9157-d58f67b167d9' 'Accept' = 'application/json' + 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' } $Data if ($noPagination) { $nextURL = $null } else { $nextURL = $data.NextLink } From 4d8e670d7503213a19eef58e71fded673221d3b0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 17:49:24 -0400 Subject: [PATCH 061/125] Update New-TeamsAPIGetRequest.ps1 --- Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 4a1bbb2b50a0..22c8f80656e1 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -18,6 +18,7 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 'X-Requested-With' = 'XMLHttpRequest' 'x-ms-tnm-applicationid' = '045268c0-445e-4ac1-9157-d58f67b167d9' 'Accept' = 'application/json' + 'Accept-Encoding' = 'identity' 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' } $Data From 801b1068945cf7f6cd427fefb3ed194d41c46506 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 17:56:54 -0400 Subject: [PATCH 062/125] Update New-TeamsAPIGetRequest.ps1 --- .../GraphHelper/New-TeamsAPIGetRequest.ps1 | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 22c8f80656e1..9a6a627f99ca 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -10,7 +10,8 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 $NextURL = $Uri $ReturnedData = do { try { - $Data = Invoke-RestMethod -ContentType "$ContentType;charset=UTF-8" -Uri $NextURL -Method $Method -Headers @{ + # Use Invoke-WebRequest first to get full response control + $Response = Invoke-WebRequest -ContentType "$ContentType;charset=UTF-8" -Uri $NextURL -Method $Method -Headers @{ Authorization = $token.Authorization 'x-ms-client-request-id' = [guid]::NewGuid().ToString() 'x-ms-client-session-id' = [guid]::NewGuid().ToString() @@ -21,6 +22,23 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 'Accept-Encoding' = 'identity' 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' } + + # Ensure we get the content as string and parse as JSON + $ContentString = $Response.Content + if ($Response.Headers['Content-Encoding'] -contains 'gzip') { + # If still gzipped despite our header, decompress manually + $bytes = [System.Text.Encoding]::UTF8.GetBytes($ContentString) + $memoryStream = New-Object System.IO.MemoryStream(, $bytes) + $gzipStream = New-Object System.IO.Compression.GzipStream($memoryStream, [System.IO.Compression.CompressionMode]::Decompress) + $reader = New-Object System.IO.StreamReader($gzipStream) + $ContentString = $reader.ReadToEnd() + $reader.Close() + $gzipStream.Close() + $memoryStream.Close() + } + + # Parse the content as JSON + $Data = $ContentString | ConvertFrom-Json $Data if ($noPagination) { $nextURL = $null } else { $nextURL = $data.NextLink } } catch { From f9d4d0284d3718a1104877b09256b3b584fde848 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 18:01:58 -0400 Subject: [PATCH 063/125] Update New-TeamsAPIGetRequest.ps1 --- Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 9a6a627f99ca..4ffd0667aa07 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -25,6 +25,7 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 # Ensure we get the content as string and parse as JSON $ContentString = $Response.Content + Write-Information "Response Headers: $($Response.Headers | ConvertTo-Json -Depth 10)" if ($Response.Headers['Content-Encoding'] -contains 'gzip') { # If still gzipped despite our header, decompress manually $bytes = [System.Text.Encoding]::UTF8.GetBytes($ContentString) From f239854da0f258842eb61b82b395111c90009faf Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 18:07:10 -0400 Subject: [PATCH 064/125] Update New-TeamsAPIGetRequest.ps1 --- .../GraphHelper/New-TeamsAPIGetRequest.ps1 | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 4ffd0667aa07..d7081b101d6b 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -23,19 +23,28 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' } - # Ensure we get the content as string and parse as JSON - $ContentString = $Response.Content + # Handle response content - check for gzip encoding first Write-Information "Response Headers: $($Response.Headers | ConvertTo-Json -Depth 10)" + if ($Response.Headers['Content-Encoding'] -contains 'gzip') { - # If still gzipped despite our header, decompress manually - $bytes = [System.Text.Encoding]::UTF8.GetBytes($ContentString) - $memoryStream = New-Object System.IO.MemoryStream(, $bytes) - $gzipStream = New-Object System.IO.Compression.GzipStream($memoryStream, [System.IO.Compression.CompressionMode]::Decompress) - $reader = New-Object System.IO.StreamReader($gzipStream) - $ContentString = $reader.ReadToEnd() - $reader.Close() - $gzipStream.Close() - $memoryStream.Close() + # Get raw bytes for proper gzip decompression + $bytes = $Response.RawContentStream.ToArray() + try { + $memoryStream = New-Object System.IO.MemoryStream(, $bytes) + $gzipStream = New-Object System.IO.Compression.GzipStream($memoryStream, [System.IO.Compression.CompressionMode]::Decompress) + $reader = New-Object System.IO.StreamReader($gzipStream, [System.Text.Encoding]::UTF8) + $ContentString = $reader.ReadToEnd() + $reader.Close() + $gzipStream.Close() + $memoryStream.Close() + } catch { + # Fallback: try to use the content as-is if decompression fails + Write-Warning "Gzip decompression failed, using content as-is: $($_.Exception.Message)" + $ContentString = $Response.Content + } + } else { + # Content is not gzipped, use as-is + $ContentString = $Response.Content } # Parse the content as JSON From 2d7ec4384f153af2db87d22c9a8a16a5dca22d9d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 18:24:39 -0400 Subject: [PATCH 065/125] testing --- .../GraphHelper/New-TeamsAPIGetRequest.ps1 | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index d7081b101d6b..1bdbd910da35 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -10,8 +10,8 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 $NextURL = $Uri $ReturnedData = do { try { - # Use Invoke-WebRequest first to get full response control - $Response = Invoke-WebRequest -ContentType "$ContentType;charset=UTF-8" -Uri $NextURL -Method $Method -Headers @{ + # Use Invoke-RestMethod with automatic decompression and explicit headers to prevent gzip + $Data = Invoke-RestMethod -ContentType "$ContentType;charset=UTF-8" -Uri $NextURL -Method $Method -Headers @{ Authorization = $token.Authorization 'x-ms-client-request-id' = [guid]::NewGuid().ToString() 'x-ms-client-session-id' = [guid]::NewGuid().ToString() @@ -21,34 +21,8 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 'Accept' = 'application/json' 'Accept-Encoding' = 'identity' 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' - } + } -DisableKeepAlive - # Handle response content - check for gzip encoding first - Write-Information "Response Headers: $($Response.Headers | ConvertTo-Json -Depth 10)" - - if ($Response.Headers['Content-Encoding'] -contains 'gzip') { - # Get raw bytes for proper gzip decompression - $bytes = $Response.RawContentStream.ToArray() - try { - $memoryStream = New-Object System.IO.MemoryStream(, $bytes) - $gzipStream = New-Object System.IO.Compression.GzipStream($memoryStream, [System.IO.Compression.CompressionMode]::Decompress) - $reader = New-Object System.IO.StreamReader($gzipStream, [System.Text.Encoding]::UTF8) - $ContentString = $reader.ReadToEnd() - $reader.Close() - $gzipStream.Close() - $memoryStream.Close() - } catch { - # Fallback: try to use the content as-is if decompression fails - Write-Warning "Gzip decompression failed, using content as-is: $($_.Exception.Message)" - $ContentString = $Response.Content - } - } else { - # Content is not gzipped, use as-is - $ContentString = $Response.Content - } - - # Parse the content as JSON - $Data = $ContentString | ConvertFrom-Json $Data if ($noPagination) { $nextURL = $null } else { $nextURL = $data.NextLink } } catch { From d2ace52264dfb7d21788505459793fee4e4b52ce Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 18:37:14 -0400 Subject: [PATCH 066/125] Update New-TeamsAPIGetRequest.ps1 --- .../GraphHelper/New-TeamsAPIGetRequest.ps1 | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 1bdbd910da35..51539d81033c 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -10,19 +10,44 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 $NextURL = $Uri $ReturnedData = do { try { - # Use Invoke-RestMethod with automatic decompression and explicit headers to prevent gzip - $Data = Invoke-RestMethod -ContentType "$ContentType;charset=UTF-8" -Uri $NextURL -Method $Method -Headers @{ - Authorization = $token.Authorization - 'x-ms-client-request-id' = [guid]::NewGuid().ToString() - 'x-ms-client-session-id' = [guid]::NewGuid().ToString() - 'x-ms-correlation-id' = [guid]::NewGuid() - 'X-Requested-With' = 'XMLHttpRequest' - 'x-ms-tnm-applicationid' = '045268c0-445e-4ac1-9157-d58f67b167d9' - 'Accept' = 'application/json' - 'Accept-Encoding' = 'identity' - 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' - } -DisableKeepAlive + # Use .NET HttpClient directly to bypass PowerShell HTTP handling issues + $httpClient = New-Object System.Net.Http.HttpClient + $httpClient.DefaultRequestHeaders.Add('Authorization', $token.Authorization) + $httpClient.DefaultRequestHeaders.Add('x-ms-client-request-id', [guid]::NewGuid().ToString()) + $httpClient.DefaultRequestHeaders.Add('x-ms-client-session-id', [guid]::NewGuid().ToString()) + $httpClient.DefaultRequestHeaders.Add('x-ms-correlation-id', [guid]::NewGuid().ToString()) + $httpClient.DefaultRequestHeaders.Add('X-Requested-With', 'XMLHttpRequest') + $httpClient.DefaultRequestHeaders.Add('x-ms-tnm-applicationid', '045268c0-445e-4ac1-9157-d58f67b167d9') + $httpClient.DefaultRequestHeaders.Add('Accept', 'application/json') + $httpClient.DefaultRequestHeaders.Add('Accept-Encoding', 'identity') + $httpClient.DefaultRequestHeaders.Add('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36') + # Disable automatic decompression to prevent .NET compression issues + $handler = New-Object System.Net.Http.HttpClientHandler + $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::None + $httpClient.Dispose() + $httpClient = New-Object System.Net.Http.HttpClient($handler) + + # Re-add headers after creating new client with handler + $httpClient.DefaultRequestHeaders.Add('Authorization', $token.Authorization) + $httpClient.DefaultRequestHeaders.Add('x-ms-client-request-id', [guid]::NewGuid().ToString()) + $httpClient.DefaultRequestHeaders.Add('x-ms-client-session-id', [guid]::NewGuid().ToString()) + $httpClient.DefaultRequestHeaders.Add('x-ms-correlation-id', [guid]::NewGuid().ToString()) + $httpClient.DefaultRequestHeaders.Add('X-Requested-With', 'XMLHttpRequest') + $httpClient.DefaultRequestHeaders.Add('x-ms-tnm-applicationid', '045268c0-445e-4ac1-9157-d58f67b167d9') + $httpClient.DefaultRequestHeaders.Add('Accept', 'application/json') + $httpClient.DefaultRequestHeaders.Add('Accept-Encoding', 'identity') + $httpClient.DefaultRequestHeaders.Add('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36') + + $response = $httpClient.GetAsync($NextURL).Result + $contentString = $response.Content.ReadAsStringAsync().Result + + # Clean up + $httpClient.Dispose() + $handler.Dispose() + + # Parse JSON + $Data = $contentString | ConvertFrom-Json $Data if ($noPagination) { $nextURL = $null } else { $nextURL = $data.NextLink } } catch { From 84484181bc29adb307495e49ea84f88aa80239d4 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 18:44:59 -0400 Subject: [PATCH 067/125] fix teams voice --- .../Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 | 7 ++----- .../CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 index 0e19160420f8..7f7b4c72911f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsVoice.ps1 @@ -23,9 +23,8 @@ function Invoke-ListTeamsVoice { $GraphRequest = do { Write-Host "Getting page $Skip" $Results = New-TeamsAPIGetRequest -uri "https://api.interfaces.records.teams.microsoft.com/Skype.TelephoneNumberMgmt/Tenants/$($TenantId)/telephone-numbers?skip=$($Skip)&locale=en-US&top=999" -tenantid $TenantFilter - Write-Information ($Results | ConvertTo-Json -Depth 10) + #Write-Information ($Results | ConvertTo-Json -Depth 10) $data = $Results.TelephoneNumbers | ForEach-Object { - Write-Host 'Reached the loop' $CompleteRequest = $_ | Select-Object *, @{Name = 'AssignedTo'; Expression = { $users | Where-Object -Property id -EQ $_.TargetId } } if ($CompleteRequest.AcquisitionDate) { $CompleteRequest.AcquisitionDate = $_.AcquisitionDate -split 'T' | Select-Object -First 1 @@ -35,11 +34,9 @@ function Invoke-ListTeamsVoice { $CompleteRequest.AssignedTo ? $null : ($CompleteRequest | Add-Member -NotePropertyName 'AssignedTo' -NotePropertyValue 'Unassigned' -Force) $CompleteRequest } - Write-Host 'Finished the loop' $Skip = $Skip + 999 $Data } while ($data.Count -eq 999) - Write-Host 'Exiting the Do.' $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message @@ -50,7 +47,7 @@ function Invoke-ListTeamsVoice { Write-Host 'Returning the response' Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = @($GraphRequest) + Body = @($GraphRequest | Where-Object { $_.TelephoneNumber }) }) } diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 51539d81033c..2f80508155cd 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -43,8 +43,8 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 $contentString = $response.Content.ReadAsStringAsync().Result # Clean up - $httpClient.Dispose() - $handler.Dispose() + $httpClient.Dispose() | Out-Null + $handler.Dispose() | Out-Null # Parse JSON $Data = $contentString | ConvertFrom-Json From bf273a8aabfa8549de7d8239e43d54166cd96cf3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 20:38:44 -0400 Subject: [PATCH 068/125] tidy code --- .../GraphHelper/New-TeamsAPIGetRequest.ps1 | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 index 2f80508155cd..9d97e7a01ab0 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-TeamsAPIGetRequest.ps1 @@ -6,52 +6,49 @@ function New-TeamsAPIGetRequest($Uri, $tenantID, $Method = 'GET', $Resource = '4 if ((Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { $token = Get-GraphToken -TenantID $tenantID -Scope "$Resource/.default" - $NextURL = $Uri $ReturnedData = do { + $handler = $null + $httpClient = $null + $response = $null try { - # Use .NET HttpClient directly to bypass PowerShell HTTP handling issues - $httpClient = New-Object System.Net.Http.HttpClient - $httpClient.DefaultRequestHeaders.Add('Authorization', $token.Authorization) - $httpClient.DefaultRequestHeaders.Add('x-ms-client-request-id', [guid]::NewGuid().ToString()) - $httpClient.DefaultRequestHeaders.Add('x-ms-client-session-id', [guid]::NewGuid().ToString()) - $httpClient.DefaultRequestHeaders.Add('x-ms-correlation-id', [guid]::NewGuid().ToString()) - $httpClient.DefaultRequestHeaders.Add('X-Requested-With', 'XMLHttpRequest') - $httpClient.DefaultRequestHeaders.Add('x-ms-tnm-applicationid', '045268c0-445e-4ac1-9157-d58f67b167d9') - $httpClient.DefaultRequestHeaders.Add('Accept', 'application/json') - $httpClient.DefaultRequestHeaders.Add('Accept-Encoding', 'identity') - $httpClient.DefaultRequestHeaders.Add('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36') - - # Disable automatic decompression to prevent .NET compression issues + # Create handler and client with compression disabled $handler = New-Object System.Net.Http.HttpClientHandler $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::None - $httpClient.Dispose() $httpClient = New-Object System.Net.Http.HttpClient($handler) - # Re-add headers after creating new client with handler - $httpClient.DefaultRequestHeaders.Add('Authorization', $token.Authorization) - $httpClient.DefaultRequestHeaders.Add('x-ms-client-request-id', [guid]::NewGuid().ToString()) - $httpClient.DefaultRequestHeaders.Add('x-ms-client-session-id', [guid]::NewGuid().ToString()) - $httpClient.DefaultRequestHeaders.Add('x-ms-correlation-id', [guid]::NewGuid().ToString()) - $httpClient.DefaultRequestHeaders.Add('X-Requested-With', 'XMLHttpRequest') - $httpClient.DefaultRequestHeaders.Add('x-ms-tnm-applicationid', '045268c0-445e-4ac1-9157-d58f67b167d9') - $httpClient.DefaultRequestHeaders.Add('Accept', 'application/json') - $httpClient.DefaultRequestHeaders.Add('Accept-Encoding', 'identity') - $httpClient.DefaultRequestHeaders.Add('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36') + # Add all required headers + $headers = @{ + 'Authorization' = $token.Authorization + 'x-ms-client-request-id' = [guid]::NewGuid().ToString() + 'x-ms-client-session-id' = [guid]::NewGuid().ToString() + 'x-ms-correlation-id' = [guid]::NewGuid().ToString() + 'X-Requested-With' = 'XMLHttpRequest' + 'x-ms-tnm-applicationid' = '045268c0-445e-4ac1-9157-d58f67b167d9' + 'Accept' = 'application/json' + 'Accept-Encoding' = 'identity' + 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' + } + + foreach ($header in $headers.GetEnumerator()) { + $httpClient.DefaultRequestHeaders.Add($header.Key, $header.Value) + } $response = $httpClient.GetAsync($NextURL).Result $contentString = $response.Content.ReadAsStringAsync().Result - # Clean up - $httpClient.Dispose() | Out-Null - $handler.Dispose() | Out-Null - - # Parse JSON + # Parse JSON and return data $Data = $contentString | ConvertFrom-Json + $Data if ($noPagination) { $nextURL = $null } else { $nextURL = $data.NextLink } } catch { throw "Failed to make Teams API Get Request $_" + } finally { + # Proper cleanup in finally block to ensure disposal even on exceptions + if ($response) { $response.Dispose() } + if ($httpClient) { $httpClient.Dispose() } + if ($handler) { $handler.Dispose() } } } until ($null -eq $NextURL) return $ReturnedData From dbd5265d4ec500621143a1b0d3bfaec714770f22 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 21:21:59 -0400 Subject: [PATCH 069/125] fix ids in edit group fixes ticket 25781735749 --- .../Groups/Invoke-EditGroup.ps1 | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 index a1fce2b3049d..4ae53e4b2692 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 @@ -22,7 +22,6 @@ function Invoke-EditGroup { $AddMembers = $UserObj.AddMember - $TenantId = $UserObj.tenantId ?? $UserObj.tenantFilter $MemberODataBindString = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' @@ -71,7 +70,7 @@ function Invoke-EditGroup { try { # Add to group user action and edit group page sends in different formats, so we need to handle both $Member = $_.value ?? $_ - $MemberID = $_.addedFields.id + $MemberID = $_.value if (!$MemberID) { $MemberID = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$Member" -tenantid $TenantId).id } @@ -148,7 +147,7 @@ function Invoke-EditGroup { if ($RemoveContact) { $RemoveContact | ForEach-Object { $Member = $_.value - $MemberID = $_.addedFields.id + $MemberID = $_.value if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { $Params = @{ Identity = $GroupId; Member = $MemberID ; BypassSecurityGroupManagerCheck = $true } $ExoBulkRequests.Add(@{ @@ -176,7 +175,7 @@ function Invoke-EditGroup { if ($RemoveMembers) { $RemoveMembers | ForEach-Object { $Member = $_.value - $MemberID = $_.addedFields.id + $MemberID = $_.value if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { $Params = @{ Identity = $GroupId; Member = $Member ; BypassSecurityGroupManagerCheck = $true } $ExoBulkRequests.Add(@{ @@ -212,7 +211,7 @@ function Invoke-EditGroup { if ($GroupType -notin @('Distribution List', 'Mail-Enabled Security')) { $AddOwners | ForEach-Object { $Owner = $_.value - $ID = $_.addedFields.id + $ID = $_.value $BulkRequests.Add(@{ id = "addOwner-$Owner" @@ -241,7 +240,7 @@ function Invoke-EditGroup { if ($RemoveOwners) { if ($GroupType -notin @('Distribution List', 'Mail-Enabled Security')) { $RemoveOwners | ForEach-Object { - $ID = $_.addedFields.id + $ID = $_.value $BulkRequests.Add(@{ id = "removeOwner-$ID" method = 'DELETE' @@ -263,8 +262,8 @@ function Invoke-EditGroup { $NewManagedBy = [System.Collections.Generic.List[string]]::new() foreach ($CurrentOwner in $CurrentOwners) { - if ($RemoveOwners -and $RemoveOwners.addedFields.id -contains $CurrentOwner) { - $OwnerToRemove = $RemoveOwners | Where-Object { $_.addedFields.id -eq $CurrentOwner } + if ($RemoveOwners -and $RemoveOwners.value -contains $CurrentOwner) { + $OwnerToRemove = $RemoveOwners | Where-Object { $_.value -eq $CurrentOwner } $ExoLogs.Add(@{ message = "Removed owner $($OwnerToRemove.label) from $($GroupName) group" target = $GroupId @@ -275,7 +274,7 @@ function Invoke-EditGroup { } if ($AddOwners) { foreach ($NewOwner in $AddOwners) { - $NewManagedBy.Add($NewOwner.addedFields.id) + $NewManagedBy.Add($NewOwner.value) $ExoLogs.Add(@{ message = "Added owner $($NewOwner.label) to $($GroupName) group" target = $GroupId @@ -436,4 +435,4 @@ function Invoke-EditGroup { Body = $Body }) -} \ No newline at end of file +} From 7a106ff11124fa090e5e114a244c6e7d8baaf32f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 21:55:58 -0400 Subject: [PATCH 070/125] fix properties and logging --- .../Administration/Groups/Invoke-EditGroup.ps1 | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 index 4ae53e4b2692..ff1e8a0ad7e3 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 @@ -69,7 +69,7 @@ function Invoke-EditGroup { $AddMembers | ForEach-Object { try { # Add to group user action and edit group page sends in different formats, so we need to handle both - $Member = $_.value ?? $_ + $Member = $_.addedFields.userPrincipalName ?? $_.value ?? $_ $MemberID = $_.value if (!$MemberID) { $MemberID = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$Member" -tenantid $TenantId).id @@ -146,7 +146,7 @@ function Invoke-EditGroup { try { if ($RemoveContact) { $RemoveContact | ForEach-Object { - $Member = $_.value + $Member = $_.addedFields.userPrincipalName ?? $_.value $MemberID = $_.value if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { $Params = @{ Identity = $GroupId; Member = $MemberID ; BypassSecurityGroupManagerCheck = $true } @@ -174,7 +174,7 @@ function Invoke-EditGroup { try { if ($RemoveMembers) { $RemoveMembers | ForEach-Object { - $Member = $_.value + $Member = $_.addedFields.userPrincipalName ?? $_.value $MemberID = $_.value if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { $Params = @{ Identity = $GroupId; Member = $Member ; BypassSecurityGroupManagerCheck = $true } @@ -210,7 +210,7 @@ function Invoke-EditGroup { if ($AddOwners) { if ($GroupType -notin @('Distribution List', 'Mail-Enabled Security')) { $AddOwners | ForEach-Object { - $Owner = $_.value + $Owner = $_.addedFields.userPrincipalName ?? $_.value $ID = $_.value $BulkRequests.Add(@{ @@ -225,7 +225,7 @@ function Invoke-EditGroup { } }) $GraphLogs.Add(@{ - message = "Added $Owner to $($GroupName) group" + message = "Added owner $($Owner) to $($GroupName) group" id = "addOwner-$Owner" }) } @@ -241,13 +241,14 @@ function Invoke-EditGroup { if ($GroupType -notin @('Distribution List', 'Mail-Enabled Security')) { $RemoveOwners | ForEach-Object { $ID = $_.value + $Owner = $_.addedFields.userPrincipalName ?? $_.value $BulkRequests.Add(@{ id = "removeOwner-$ID" method = 'DELETE' url = "groups/$($GroupId)/owners/$ID/`$ref" }) $GraphLogs.Add(@{ - message = "Removed $($_.value) from $($GroupName) group" + message = "Removed owner $($Owner) from $($GroupName) group" id = "removeOwner-$ID" }) } @@ -297,7 +298,7 @@ function Invoke-EditGroup { Write-Information "Graph Bulk Requests: $($BulkRequests.Count)" if ($BulkRequests.Count -gt 0) { #Write-Warning 'EditUser - Executing Graph Bulk Requests' - #Write-Information ($BulkRequests | ConvertTo-Json -Depth 10) + Write-Information ($BulkRequests | ConvertTo-Json -Depth 10) $RawGraphRequest = New-GraphBulkRequest -tenantid $TenantId -scope 'https://graph.microsoft.com/.default' -Requests @($BulkRequests) -asapp $true #Write-Warning 'EditUser - Executing Graph Bulk Requests - Completed' #Write-Information ($RawGraphRequest | ConvertTo-Json -Depth 10) From 85a094b622917bf77e337e228f8202d2f6b04fcd Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Jul 2025 21:58:49 -0400 Subject: [PATCH 071/125] Update Invoke-EditGroup.ps1 --- .../Identity/Administration/Groups/Invoke-EditGroup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 index ff1e8a0ad7e3..18cd79effab4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Groups/Invoke-EditGroup.ps1 @@ -298,7 +298,7 @@ function Invoke-EditGroup { Write-Information "Graph Bulk Requests: $($BulkRequests.Count)" if ($BulkRequests.Count -gt 0) { #Write-Warning 'EditUser - Executing Graph Bulk Requests' - Write-Information ($BulkRequests | ConvertTo-Json -Depth 10) + #Write-Information ($BulkRequests | ConvertTo-Json -Depth 10) $RawGraphRequest = New-GraphBulkRequest -tenantid $TenantId -scope 'https://graph.microsoft.com/.default' -Requests @($BulkRequests) -asapp $true #Write-Warning 'EditUser - Executing Graph Bulk Requests - Completed' #Write-Information ($RawGraphRequest | ConvertTo-Json -Depth 10) From ac5acaa9898a46490186960667c6f3578779ed3a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:06:10 +0200 Subject: [PATCH 072/125] add migration sstandard for auth states --- ...CIPPStandardAuthMethodsPolicyMigration.ps1 | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsPolicyMigration.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsPolicyMigration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsPolicyMigration.ps1 new file mode 100644 index 000000000000..2d40d7a7e266 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuthMethodsPolicyMigration.ps1 @@ -0,0 +1,59 @@ +function Invoke-CIPPStandardAuthMethodsPolicyMigration { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) AuthMethodsPolicyMigration + .SYNOPSIS + (Label) Complete Authentication Methods Policy Migration + .DESCRIPTION + (Helptext) Completes the migration of authentication methods policy to the new format + (DocsDescription) Sets the authentication methods policy migration state to complete. This is required when migrating from legacy authentication policies to the new unified authentication methods policy. + .NOTES + CAT + Entra (AAD) Standards + TAG + ADDEDCOMPONENT + IMPACT + Medium Impact + ADDEDDATE + 2025-01-08 + POWERSHELLEQUIVALENT + Update-MgBetaPolicyAuthenticationMethodPolicy + RECOMMENDEDBY + UPDATECOMMENTBLOCK + Run the Tools\Update-StandardsComments.ps1 script to update this comment block + .LINK + https://docs.cipp.app/user-documentation/tenant/standards/list-standards + #> + + param($Tenant, $Settings) + $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -tenantid $Tenant + + if ($Settings.remediate -eq $true) { + if ($CurrentInfo.policyMigrationState -eq 'migrationComplete') { + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Authentication methods policy migration is already complete.' -sev Info + } else { + try { + New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -tenantid $Tenant -body '{"policyMigrationState": "migrationComplete"}' -type PATCH + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Authentication methods policy migration completed successfully.' -sev Info + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to complete authentication methods policy migration: $($_.Exception.Message)" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($CurrentInfo.policyMigrationState -ne 'migrationComplete') { + Write-StandardsAlert -message 'Authentication methods policy migration is not complete. Please check if you have legacy SSPR settings or MFA settings set: https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-methods-manage' -object $CurrentInfo -tenant $tenant -standardName 'AuthMethodsPolicyMigration' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $tenant -message 'Authentication methods policy migration is not complete' -sev Alert + } + } + + if ($Settings.report -eq $true) { + $migrationComplete = $CurrentInfo.policyMigrationState -eq 'migrationComplete' + Set-CIPPStandardsCompareField -FieldName 'standards.AuthMethodsPolicyMigration' -FieldValue $migrationComplete -TenantFilter $tenant + Add-CIPPBPAField -FieldName 'AuthMethodsPolicyMigration' -FieldValue $migrationComplete -StoreAs bool -Tenant $tenant + } + +} From d1d0d5c6ec95d00612785bfb92473332552d48d0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:27:35 +0200 Subject: [PATCH 073/125] fixes compares of specific intune objects --- Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 b/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 index 579fdcd913f6..2de562a1fdf8 100644 --- a/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 +++ b/Modules/CIPPCore/Public/Compare-CIPPIntuneObject.ps1 @@ -257,7 +257,7 @@ function Compare-CIPPIntuneObject { } } } - Default { + default { if ($settingInstance.simpleSettingValue?.value) { $label = if ($intuneObj?.displayName) { $intuneObj.displayName @@ -337,6 +337,9 @@ function Compare-CIPPIntuneObject { } else { $child.choiceSettingValue.value } + if (!$childValue -and $child.simpleSettingValue.value) { + $childValue = $child.simpleSettingValue.value + } } # Add object to our temporary list @@ -351,7 +354,7 @@ function Compare-CIPPIntuneObject { } } } - Default { + default { if ($settingInstance.simpleSettingValue?.value) { $label = if ($intuneObj?.displayName) { $intuneObj.displayName From ef6aa466a48770d6009c8f4cf7fc9ef2c279dd51 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 8 Jul 2025 10:31:42 -0400 Subject: [PATCH 074/125] tenant group support in cipp roles --- .../Authentication/Get-CIPPHttpFunctions.ps1 | 4 +- .../Public/Authentication/Test-CIPPAccess.ps1 | 83 +++++++++++++++++-- .../CIPP/Settings/Invoke-ExecTenantGroup.ps1 | 2 +- .../CIPP/Settings/Invoke-ListCustomRole.ps1 | 56 +++++++++++-- .../Public/Functions/Get-TenantGroups.ps1 | 32 +++---- 5 files changed, 143 insertions(+), 34 deletions(-) diff --git a/Modules/CIPPCore/Public/Authentication/Get-CIPPHttpFunctions.ps1 b/Modules/CIPPCore/Public/Authentication/Get-CIPPHttpFunctions.ps1 index 6b2b0b626ba4..643130adb428 100644 --- a/Modules/CIPPCore/Public/Authentication/Get-CIPPHttpFunctions.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Get-CIPPHttpFunctions.ps1 @@ -1,5 +1,5 @@ function Get-CIPPHttpFunctions { - Param( + param( [switch]$ByRole, [switch]$ByRoleGroup ) @@ -8,7 +8,7 @@ function Get-CIPPHttpFunctions { $Functions = Get-Command -Module CIPPCore | Where-Object { $_.Visibility -eq 'Public' -and $_.Name -match 'Invoke-*' } $Results = foreach ($Function in $Functions) { $Help = Get-Help $Function - if ($Help.Functionality -ne 'Entrypoint') { continue } + if ($Help.Functionality -notmatch 'Entrypoint') { continue } if ($Help.Role -eq 'Public') { continue } [PSCustomObject]@{ Function = $Function.Name diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index 85652645ad8f..0bf10acc8542 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -8,9 +8,13 @@ function Test-CIPPAccess { # Get function help $FunctionName = 'Invoke-{0}' -f $Request.Params.CIPPEndpoint - try { - $Help = Get-Help $FunctionName -ErrorAction Stop - } catch {} + if ($FunctionName -ne 'Invoke-me') { + try { + $Help = Get-Help $FunctionName -ErrorAction Stop + } catch { + Write-Warning "Function '$FunctionName' not found" + } + } # Check help for role $APIRole = $Help.Role @@ -189,10 +193,39 @@ function Test-CIPPAccess { if ((($Permission.AllowedTenants | Measure-Object).Count -eq 0 -or $Permission.AllowedTenants -contains 'AllTenants') -and (($Permission.BlockedTenants | Measure-Object).Count -eq 0)) { @('AllTenants') } else { - if ($Permission.AllowedTenants -contains 'AllTenants') { - $Permission.AllowedTenants = $Tenants.customerId + # Expand tenant groups to individual tenant IDs + $ExpandedAllowedTenants = foreach ($AllowedItem in $Permission.AllowedTenants) { + if ($AllowedItem -is [PSCustomObject] -and $AllowedItem.type -eq 'Group') { + try { + $GroupMembers = Expand-CIPPTenantGroups -TenantFilter @($AllowedItem) + $GroupMembers | ForEach-Object { $_.addedFields.customerId } + } catch { + Write-Warning "Failed to expand tenant group '$($AllowedItem.label)': $($_.Exception.Message)" + @() + } + } else { + $AllowedItem + } + } + + $ExpandedBlockedTenants = foreach ($BlockedItem in $Permission.BlockedTenants) { + if ($BlockedItem -is [PSCustomObject] -and $BlockedItem.type -eq 'Group') { + try { + $GroupMembers = Expand-CIPPTenantGroups -TenantFilter @($BlockedItem) + $GroupMembers | ForEach-Object { $_.addedFields.customerId } + } catch { + Write-Warning "Failed to expand blocked tenant group '$($BlockedItem.label)': $($_.Exception.Message)" + @() + } + } else { + $BlockedItem + } + } + + if ($ExpandedAllowedTenants -contains 'AllTenants') { + $ExpandedAllowedTenants = $Tenants.customerId } - $Permission.AllowedTenants | Where-Object { $Permission.BlockedTenants -notcontains $_ } + $ExpandedAllowedTenants | Where-Object { $ExpandedBlockedTenants -notcontains $_ } } } return $LimitedTenantList @@ -217,13 +250,45 @@ function Test-CIPPAccess { $TenantAllowed = $false } else { $Tenant = ($Tenants | Where-Object { $TenantFilter -eq $_.customerId -or $TenantFilter -eq $_.defaultDomainName }).customerId - if ($Role.AllowedTenants -contains 'AllTenants') { + + # Expand allowed tenant groups to individual tenant IDs + $ExpandedAllowedTenants = foreach ($AllowedItem in $Role.AllowedTenants) { + if ($AllowedItem -is [PSCustomObject] -and $AllowedItem.type -eq 'Group') { + try { + $GroupMembers = Expand-CIPPTenantGroups -TenantFilter @($AllowedItem) + $GroupMembers | ForEach-Object { $_.addedFields.customerId } + } catch { + Write-Warning "Failed to expand allowed tenant group '$($AllowedItem.label)': $($_.Exception.Message)" + @() + } + } else { + $AllowedItem + } + } + + # Expand blocked tenant groups to individual tenant IDs + $ExpandedBlockedTenants = foreach ($BlockedItem in $Role.BlockedTenants) { + if ($BlockedItem -is [PSCustomObject] -and $BlockedItem.type -eq 'Group') { + try { + $GroupMembers = Expand-CIPPTenantGroups -TenantFilter @($BlockedItem) + $GroupMembers | ForEach-Object { $_.addedFields.customerId } + } catch { + Write-Warning "Failed to expand blocked tenant group '$($BlockedItem.label)': $($_.Exception.Message)" + @() + } + } else { + $BlockedItem + } + } + + if ($ExpandedAllowedTenants -contains 'AllTenants') { $AllowedTenants = $Tenants.customerId } else { - $AllowedTenants = $Role.AllowedTenants + $AllowedTenants = $ExpandedAllowedTenants } + if ($Tenant) { - $TenantAllowed = $AllowedTenants -contains $Tenant -and $Role.BlockedTenants -notcontains $Tenant + $TenantAllowed = $AllowedTenants -contains $Tenant -and $ExpandedBlockedTenants -notcontains $Tenant if (!$TenantAllowed) { continue } break } else { 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 029f21fe25be..5ab6cf634b28 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 @@ -7,7 +7,7 @@ function Invoke-ExecTenantGroup { .FUNCTIONALITY Entrypoint,AnyTenant .ROLE - TenantGroups.Config.ReadWrite + Tenant.Groups.ReadWrite #> [CmdletBinding()] param($Request, $TriggerMetadata) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomRole.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomRole.ps1 index a3c89065b371..51e23977d3ec 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomRole.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ListCustomRole.ps1 @@ -49,9 +49,31 @@ function Invoke-ListCustomRole { if ($Role.AllowedTenants) { try { $AllowedTenants = $Role.AllowedTenants | ConvertFrom-Json -ErrorAction Stop | ForEach-Object { - $TenantId = $_ - $TenantList | Where-Object { $_.customerId -eq $TenantId } | Select-Object -ExpandProperty defaultDomainName - } + if ($_ -is [PSCustomObject] -and $_.type -eq 'Group') { + # Return group objects as-is for frontend display + [PSCustomObject]@{ + type = 'Group' + value = $_.value + label = $_.label + } + } else { + # Convert tenant customer ID to domain name object for frontend + $TenantId = $_ + $TenantInfo = $TenantList | Where-Object { $_.customerId -eq $TenantId } + if ($TenantInfo) { + [PSCustomObject]@{ + type = 'Tenant' + value = $TenantInfo.defaultDomainName + label = "$($TenantInfo.displayName) ($($TenantInfo.defaultDomainName))" + addedFields = @{ + defaultDomainName = $TenantInfo.defaultDomainName + displayName = $TenantInfo.displayName + customerId = $TenantInfo.customerId + } + } + } + } + } | Where-Object { $_ -ne $null } $AllowedTenants = $AllowedTenants ?? @('AllTenants') $Role.AllowedTenants = @($AllowedTenants) } catch { @@ -63,9 +85,31 @@ function Invoke-ListCustomRole { if ($Role.BlockedTenants) { try { $BlockedTenants = $Role.BlockedTenants | ConvertFrom-Json -ErrorAction Stop | ForEach-Object { - $TenantId = $_ - $TenantList | Where-Object { $_.customerId -eq $TenantId } | Select-Object -ExpandProperty defaultDomainName - } + if ($_ -is [PSCustomObject] -and $_.type -eq 'Group') { + # Return group objects as-is for frontend display + [PSCustomObject]@{ + type = 'Group' + value = $_.value + label = $_.label + } + } else { + # Convert tenant customer ID to domain name object for frontend + $TenantId = $_ + $TenantInfo = $TenantList | Where-Object { $_.customerId -eq $TenantId } + if ($TenantInfo) { + [PSCustomObject]@{ + type = 'Tenant' + value = $TenantInfo.defaultDomainName + label = "$($TenantInfo.displayName) ($($TenantInfo.defaultDomainName))" + addedFields = @{ + defaultDomainName = $TenantInfo.defaultDomainName + displayName = $TenantInfo.displayName + customerId = $TenantInfo.customerId + } + } + } + } + } | Where-Object { $_ -ne $null } $BlockedTenants = $BlockedTenants ?? @() $Role.BlockedTenants = @($BlockedTenants) } catch { diff --git a/Modules/CIPPCore/Public/Functions/Get-TenantGroups.ps1 b/Modules/CIPPCore/Public/Functions/Get-TenantGroups.ps1 index ad4525a4d7de..761e1462d9cb 100644 --- a/Modules/CIPPCore/Public/Functions/Get-TenantGroups.ps1 +++ b/Modules/CIPPCore/Public/Functions/Get-TenantGroups.ps1 @@ -30,9 +30,9 @@ function Get-TenantGroups { } $Tenants = Get-Tenants @TenantParams - if ($GroupFilter) { - $Groups = Get-CIPPAzDataTableEntity @GroupTable -Filter "RowKey eq '$GroupFilter'" - $AllMembers = Get-CIPPAzDataTableEntity @MembersTable -Filter "GroupId eq '$GroupFilter'" + if ($GroupId) { + $Groups = Get-CIPPAzDataTableEntity @GroupTable -Filter "RowKey eq '$GroupId'" + $AllMembers = Get-CIPPAzDataTableEntity @MembersTable -Filter "GroupId eq '$GroupId'" } else { $Groups = Get-CIPPAzDataTableEntity @GroupTable $AllMembers = Get-CIPPAzDataTableEntity @MembersTable @@ -49,10 +49,10 @@ function Get-TenantGroups { $Group = $Groups | Where-Object { $_.RowKey -eq $Group.GroupId } if ($Group) { $Results.Add([PSCustomObject]@{ - Id = $Group.RowKey - Name = $Group.Name - Description = $Group.Description - }) + Id = $Group.RowKey + Name = $Group.Name + Description = $Group.Description + }) } } return $Results | Sort-Object Name @@ -66,10 +66,10 @@ function Get-TenantGroups { $Tenant = $Tenants | Where-Object { $Member.customerId -eq $_.customerId } if ($Tenant) { $MembersList.Add(@{ - customerId = $Tenant.customerId - displayName = $Tenant.displayName - defaultDomainName = $Tenant.defaultDomainName - }) + customerId = $Tenant.customerId + displayName = $Tenant.displayName + defaultDomainName = $Tenant.defaultDomainName + }) } } $SortedMembers = $MembersList | Sort-Object displayName @@ -77,11 +77,11 @@ function Get-TenantGroups { $SortedMembers = @() } $Results.Add([PSCustomObject]@{ - Id = $Group.RowKey - Name = $Group.Name - Description = $Group.Description - Members = @($SortedMembers) - }) + Id = $Group.RowKey + Name = $Group.Name + Description = $Group.Description + Members = @($SortedMembers) + }) } return $Results | Sort-Object Name } From 53cdca4e40be700dcefa1969c7e2b7614eb1cd4b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 8 Jul 2025 15:51:30 -0400 Subject: [PATCH 075/125] fix sharepoint member endpoint --- .../Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 index 3d8e9947ff77..0ac9be97d022 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecSetSharePointMember.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-ExecSetSharePointMember { +function Invoke-ExecSetSharePointMember { <# .FUNCTIONALITY Entrypoint @@ -27,6 +27,7 @@ Function Invoke-ExecSetSharePointMember { $UserID = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($Request.Body.user.value)" -tenantid $TenantFilter).id $Results = Remove-CIPPGroupMember -GroupType 'Team' -GroupID $GroupID -Member $UserID -TenantFilter $TenantFilter -Headers $Headers } + $StatusCode = [HttpStatusCode]::OK } else { $StatusCode = [HttpStatusCode]::BadRequest $Results = 'This type of SharePoint site is not supported.' From ca5a8f31b2046c528e1a302fc988caacef9a1c8b Mon Sep 17 00:00:00 2001 From: Zac Richards <107489668+Zacgoose@users.noreply.github.com> Date: Wed, 9 Jul 2025 09:36:36 +0800 Subject: [PATCH 076/125] Try disable delegate rules but ignore errors if it fails --- .../Users/Invoke-ExecBECRemediate.ps1 | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 index 183cf21d34ea..35b068c7a887 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1 @@ -88,6 +88,7 @@ function Invoke-ExecBECRemediate { Write-LogMessage -headers $Headers -API $APIName -message "Retrieved $(($Rules | Measure-Object).Count) total rules for $Username" -Sev 'Info' -tenant $TenantFilter $RuleDisabled = 0 $RuleFailed = 0 + $DelegateRulesSkipped = 0 $RuleMessages = [System.Collections.Generic.List[string]]::new() if (($Rules | Measure-Object).Count -eq 0) { @@ -100,13 +101,7 @@ function Invoke-ExecBECRemediate { # Rules exist, filter and process them $ProcessableRules = $Rules | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and - $_.Name -notlike 'Microsoft.Exchange.OOF.*' -and - -not ( - # Only skip legitimate system delegate rules (exact pattern + legacy identity) - $_.Name -match '^Delegate Rule -\d+$' -and # Exact: "Delegate Rule -[numbers]" - $_.Identity -match '\\' -and # Has backslash (legacy format) - $_.Identity -notmatch '@' # No @ symbol - ) + $_.Name -notlike 'Microsoft.Exchange.OOF.*' } if (($ProcessableRules | Measure-Object).Count -eq 0) { @@ -130,10 +125,17 @@ function Invoke-ExecBECRemediate { Write-LogMessage -headers $Headers -API $APIName -message "Successfully disabled rule: $($CurrentRule.Name)" -Sev 'Info' -tenant $TenantFilter $RuleDisabled++ } catch { - $ErrorMsg = "Could not disable rule '$($CurrentRule.Name)': $($_.Exception.Message)" - Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Error' -tenant $TenantFilter - $RuleMessages.Add($ErrorMsg) - $RuleFailed++ + # Check if this is a system delegate rule, if so we can ignore the error + if ($CurrentRule.Name -match '^Delegate Rule -\d+$') { + Write-LogMessage -headers $Headers -API $APIName -message "Skipping delegate rule '$($CurrentRule.Name)' - unable to disable (expected behavior)" -Sev 'Info' -tenant $TenantFilter + $DelegateRulesSkipped++ + } else { + # Handle as normal error + $ErrorMsg = "Could not disable rule '$($CurrentRule.Name)': $($_.Exception.Message)" + Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Error' -tenant $TenantFilter + $RuleMessages.Add($ErrorMsg) + $RuleFailed++ + } } } @@ -143,6 +145,12 @@ function Invoke-ExecBECRemediate { resultText = "Successfully disabled $RuleDisabled inbox rules for $Username" state = 'success' }) + } elseif ($DelegateRulesSkipped -gt 0 -and $RuleDisabled -eq 0 -and $RuleFailed -eq 0) { + # Only system rules were found, report as no processable rules + $AllResults.Add([pscustomobject]@{ + resultText = "No processable inbox rules found for $Username" + state = 'info' + }) } if ($RuleFailed -gt 0) { @@ -162,8 +170,8 @@ function Invoke-ExecBECRemediate { } } - $TotalProcessed = $RuleDisabled + $RuleFailed - Write-LogMessage -headers $Headers -API $APIName -message "Completed inbox rules processing for $Username. Total rules: $(($Rules | Measure-Object).Count), Processed: $TotalProcessed, Disabled: $RuleDisabled, Failed: $RuleFailed, System delegate rules skipped: $DelegateRulesSkipped" -Sev 'Info' -tenant $TenantFilter + $TotalProcessed = $RuleDisabled + $RuleFailed + $DelegateRulesSkipped + Write-LogMessage -headers $Headers -API $APIName -message "Completed inbox rules processing for $Username. Total rules: $(($Rules | Measure-Object).Count), Processed: $TotalProcessed, Disabled: $RuleDisabled, Failed: $RuleFailed, Delegate rules skipped: $DelegateRulesSkipped" -Sev 'Info' -tenant $TenantFilter } catch { $ErrorMsg = "Failed to process inbox rules: $($_.Exception.Message)" From 058d3070b6c74a36153cd338d0c936968abbd842 Mon Sep 17 00:00:00 2001 From: rvdwegen Date: Wed, 9 Jul 2025 15:57:29 +0200 Subject: [PATCH 077/125] Add service provider exception --- .../Invoke-ExecCAServiceExclusion.ps1 | 35 ++++++++ .../Set-CIPPCAPolicyServiceException.ps1 | 82 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAServiceExclusion.ps1 create mode 100644 Modules/CIPPCore/Public/Set-CIPPCAPolicyServiceException.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAServiceExclusion.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAServiceExclusion.ps1 new file mode 100644 index 000000000000..e5db017bf14e --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAServiceExclusion.ps1 @@ -0,0 +1,35 @@ +using namespace System.Net + +Function Invoke-ExecCAServiceExclusion { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.ConditionalAccess.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + # Interact with the request + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + $ID = $Request.Query.GUID ?? $Request.Body.GUID + + try { + $result = Set-CIPPCAPolicyServiceException -TenantFilter $TenantFilter -PolicyId $ID + $Body = @{ Results = $result } + Write-LogMessage -headers $Headers -API 'Set-CIPPCAPolicyServiceException' -message $Message -Sev 'Info' -tenant $TenantFilter + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Body = @{ Results = "Failed to add service provider exception to policy $($ID): $($ErrorMessage.NormalizedError)" } + Write-LogMessage -headers $Headers -API 'Set-CIPPCAPolicyServiceException' -message "Failed to update policy $($PolicyId) with service provider exception for tenant $($CSPtenantId): $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPCAPolicyServiceException.ps1 b/Modules/CIPPCore/Public/Set-CIPPCAPolicyServiceException.ps1 new file mode 100644 index 000000000000..8b52c1d83d6f --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPCAPolicyServiceException.ps1 @@ -0,0 +1,82 @@ +function Set-CIPPCAPolicyServiceException { + [CmdletBinding(SupportsShouldProcess = $true)] + param( + $TenantFilter, + $PolicyId + ) + + $CSPtenantId = $env:TenantID + + # Get the current policy + $policy = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)" -tenantid $TenantFilter -AsApp $true + + # If the policy is set to affect either all or all guests/external users + if ($policy.conditions.users.includeUsers -eq "All" -OR $policy.conditions.users.includeGuestsOrExternalUsers.externalTenants.membershipKind -eq "all") { + + # Check if the policy already has the correct service provider exception + if ($policy.conditions.users.excludeGuestsOrExternalUsers) { + $excludeConfig = $policy.conditions.users.excludeGuestsOrExternalUsers + + # Check if serviceProvider is already in guestOrExternalUserTypes + $hasServiceProvider = $excludeConfig.guestOrExternalUserTypes -match "serviceProvider" + + # Check if externalTenants is properly configured + if ($excludeConfig.externalTenants) { + $externalTenants = $excludeConfig.externalTenants + $hasCorrectExternalTenants = ($externalTenants.membershipKind -eq "enumerated" -and + $externalTenants.members -contains $CSPtenantId) + + # If already configured, exit without making changes + if ($hasServiceProvider -and $hasCorrectExternalTenants) { + return "Policy $PolicyId already has the correct service provider configuration. No changes needed." + } + } + } + + # If excludeGuestsOrExternalUsers is empty, add the entire exclusion + if (!($policy.conditions.users.excludeGuestsOrExternalUsers)) { + + # Define data + $excludeServiceProviderData = [pscustomobject]@{ + guestOrExternalUserTypes = "serviceProvider" + externalTenants = [pscustomobject]@{ + '@odata.type' = "#microsoft.graph.conditionalAccessEnumeratedExternalTenants" + membershipKind = "enumerated" + members = @( + $CSPtenantId + ) + } + } + + # Add data to cached policy + $policy.conditions.users.excludeGuestsOrExternalUsers = $excludeServiceProviderData + } + + # If excludeGuestsOrExternalUsers already has content correct it to match $excludeServiceProviderData + if ($policy.conditions.users.excludeGuestsOrExternalUsers) { + + # If guestOrExternalUserTypes doesn't include type serviceProvider add it + if ($policy.conditions.users.excludeGuestsOrExternalUsers.guestOrExternalUserTypes -notmatch "serviceProvider") { + $policy.conditions.users.excludeGuestsOrExternalUsers.guestOrExternalUserTypes += ",serviceProvider" + } + + # If guestOrExternalUserTypes includes type serviceProvider and membershipKind is not all tenants + if ($policy.conditions.users.excludeGuestsOrExternalUsers.guestOrExternalUserTypes -match "serviceProvider" -AND $policy.conditions.users.excludeGuestsOrExternalUsers.externalTenants.membershipKind -ne "all") { + + # If membershipKind is enumerated and members does not include our tenant add it + if ($policy.conditions.users.excludeGuestsOrExternalUsers.externalTenants.membershipKind -eq "enumerated" -AND $policy.conditions.users.excludeGuestsOrExternalUsers.externalTenants.members -notmatch $CSPtenantId) { + $policy.conditions.users.excludeGuestsOrExternalUsers.externalTenants.members += $($CSPtenantId) + } + } + } + + } + + # Patch policy with updated data. + # TemplateId,createdDateTime,modifiedDateTime can't be written back so exclude them using -ExcludeProperty + if ($PSCmdlet.ShouldProcess($PolicyId, "Update policy with service provider exception")) { + $patch = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($policy.id)" -tenantid $TenantFilter -type PATCH -body ($policy | Select-Object * -ExcludeProperty TemplateId,createdDateTime,modifiedDateTime | ConvertTo-Json -Depth 20) -AsApp $true + return "Successfully added service provider to policy $PolicyId" + } + +} From 34e75e442b188e10fdadd378d9a57d53bc93a8e5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Jul 2025 20:36:52 -0400 Subject: [PATCH 078/125] add support for tenant groups --- .../CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 42 +++++++++++++++++- .../Push-ExecScheduledCommand.ps1 | 9 +++- .../Invoke-ListScheduledItemDetails.ps1 | 31 ++++++++++++- .../Scheduler/Invoke-ListScheduledItems.ps1 | 24 +++++++++++ .../Start-UserTasksOrchestrator.ps1 | 43 +++++++++++++++++++ 5 files changed, 145 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 4a8548e0a741..e75c27add72c 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -99,11 +99,36 @@ function Add-CIPPScheduledTask { $excludedTenants = if ($task.excludedTenants.value) { $task.excludedTenants.value -join ',' } + + # Handle tenant filter - support both single tenant and tenant groups + $tenantFilter = $task.TenantFilter.value ? $task.TenantFilter.value : $task.TenantFilter + $originalTenantFilter = $task.TenantFilter + + # If tenant filter is a complex object (from form), extract the value + if ($tenantFilter -is [PSCustomObject] -and $tenantFilter.value) { + $originalTenantFilter = $tenantFilter + $tenantFilter = $tenantFilter.value + } + + # If tenant filter is a string but still seems to be JSON, try to parse it + if ($tenantFilter -is [string] -and $tenantFilter.StartsWith('{')) { + try { + $parsedTenantFilter = $tenantFilter | ConvertFrom-Json + if ($parsedTenantFilter.value) { + $originalTenantFilter = $parsedTenantFilter + $tenantFilter = $parsedTenantFilter.value + } + } catch { + # If parsing fails, use the string as is + Write-Warning "Could not parse tenant filter JSON: $tenantFilter" + } + } + $entity = @{ PartitionKey = [string]'ScheduledTask' TaskState = [string]'Planned' RowKey = [string]$RowKey - Tenant = $task.TenantFilter.value ? "$($task.TenantFilter.value)" : "$($task.TenantFilter)" + Tenant = [string]$tenantFilter excludedTenants = [string]$excludedTenants Name = [string]$task.Name Command = [string]$task.Command.value @@ -115,6 +140,21 @@ function Add-CIPPScheduledTask { Hidden = [bool]$Hidden Results = 'Planned' } + + # Store the original tenant filter for group expansion during execution + if ($originalTenantFilter -is [PSCustomObject] -and $originalTenantFilter.type -eq 'Group') { + $entity['TenantGroup'] = [string]($originalTenantFilter | ConvertTo-Json -Compress) + } elseif ($originalTenantFilter -is [string] -and $originalTenantFilter.StartsWith('{')) { + # Check if it's a serialized group object + try { + $parsedOriginal = $originalTenantFilter | ConvertFrom-Json + if ($parsedOriginal.type -eq 'Group') { + $entity['TenantGroup'] = [string]$originalTenantFilter + } + } catch { + # Not a JSON object, ignore + } + } if ($SyncType) { $entity.SyncType = $SyncType } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index bafe1e39fa59..26cf5c71c168 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -10,7 +10,12 @@ function Push-ExecScheduledCommand { $Table = Get-CippTable -tablename 'ScheduledTasks' $task = $Item.TaskInfo $commandParameters = $Item.Parameters | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashtable + + # Handle tenant resolution - support both direct tenant and group-expanded tenants $Tenant = $Item.Parameters.TenantFilter ?? $Item.TaskInfo.Tenant + + # For tenant group tasks, the tenant will be the expanded tenant from the orchestrator + # We don't need to expand groups here as that's handled in the orchestrator $TenantInfo = Get-Tenants -TenantFilter $Tenant $null = Update-AzDataTableEntity -Force @Table -Entity @{ @@ -81,14 +86,14 @@ function Push-ExecScheduledCommand { } } - if ($StoredResults.Length -gt 64000 -or $task.Tenant -eq 'AllTenants') { + if ($StoredResults.Length -gt 64000 -or $task.Tenant -eq 'AllTenants' -or $task.TenantGroup) { $TaskResultsTable = Get-CippTable -tablename 'ScheduledTaskResults' $TaskResults = @{ PartitionKey = $task.RowKey RowKey = $Tenant Results = [string](ConvertTo-Json -Compress -Depth 20 $results) } - $null = Add-AzDataTableEntity @TaskResultsTable -Entity $TaskResults -Force + $null = Add-CIPPAzDataTableEntity @TaskResultsTable -Entity $TaskResults -Force $StoredResults = @{ Results = 'Completed, details are available in the More Info pane' } | ConvertTo-Json -Compress } } catch { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 index b9dd162b974e..208c6aa12833 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 @@ -28,7 +28,7 @@ function Invoke-ListScheduledItemDetails { # Retrieve the task information $TaskTable = Get-CIPPTable -TableName 'ScheduledTasks' - $Task = Get-CIPPAzDataTableEntity @TaskTable -Filter "RowKey eq '$RowKey' and PartitionKey eq 'ScheduledTask'" | Select-Object Name, TaskState, Command, Parameters, Recurrence, ExecutedTime, ScheduledTime, PostExecution, Tenant, Hidden, Results, Timestamp + $Task = Get-CIPPAzDataTableEntity @TaskTable -Filter "RowKey eq '$RowKey' and PartitionKey eq 'ScheduledTask'" | Select-Object Name, TaskState, Command, Parameters, Recurrence, ExecutedTime, ScheduledTime, PostExecution, Tenant, TenantGroup, Hidden, Results, Timestamp if (-not $Task) { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ @@ -57,6 +57,35 @@ function Invoke-ListScheduledItemDetails { $Task.ScheduledTime = [DateTimeOffset]::FromUnixTimeSeconds($Task.ScheduledTime).UtcDateTime } catch {} + # Handle tenant group display information (similar to Invoke-ListScheduledItems) + if ($Task.TenantGroup) { + try { + $TenantGroupObject = $Task.TenantGroup | ConvertFrom-Json -ErrorAction SilentlyContinue + if ($TenantGroupObject) { + # Create a tenant group object for the frontend formatting + $TenantGroupForDisplay = [PSCustomObject]@{ + label = $TenantGroupObject.label + value = $TenantGroupObject.value + type = 'Group' + } + $Task | Add-Member -NotePropertyName TenantGroupInfo -NotePropertyValue $TenantGroupForDisplay -Force + # Update the tenant to show the group object for proper formatting + $Task.Tenant = $TenantGroupForDisplay + } + } catch { + Write-Warning "Failed to parse tenant group information for task $($Task.RowKey): $($_.Exception.Message)" + # Fall back to keeping original tenant value + } + } else { + # For regular tenants, create a tenant object for consistent formatting + $TenantForDisplay = [PSCustomObject]@{ + label = $Task.Tenant + value = $Task.Tenant + type = 'Tenant' + } + $Task.Tenant = $TenantForDisplay + } + # Get the results if available $ResultsTable = Get-CIPPTable -TableName 'ScheduledTaskResults' $ResultsFilter = "PartitionKey eq '$RowKey'" diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 index 4361e6d64ee6..dee4667f6ae8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 @@ -75,6 +75,30 @@ function Invoke-ListScheduledItems { try { $Task.ScheduledTime = [DateTimeOffset]::FromUnixTimeSeconds($Task.ScheduledTime).UtcDateTime } catch {} + + # Handle tenant group display information + if ($Task.TenantGroup) { + try { + $TenantGroupObject = $Task.TenantGroup | ConvertFrom-Json -ErrorAction SilentlyContinue + if ($TenantGroupObject) { + # Create a tenant group object for the frontend formatting + $TenantGroupForDisplay = [PSCustomObject]@{ + label = $TenantGroupObject.label + value = $TenantGroupObject.value + type = 'Group' + } + $Task | Add-Member -NotePropertyName TenantGroupInfo -NotePropertyValue $TenantGroupForDisplay -Force + # Update the tenant to show the group object for proper formatting + $Task.Tenant = @($TenantGroupForDisplay) + } + } catch { + Write-Warning "Failed to parse tenant group information for task $($Task.RowKey): $($_.Exception.Message)" + # Fall back to keeping original tenant value + } + } else { + $Task.Tenant = @($Task.Tenant) + } + $Task } diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 index 9863866fa438..108e9b5ac1c6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 @@ -50,7 +50,50 @@ function Start-UserTasksOrchestrator { } } $Batch.AddRange($AllTenantCommands) + } elseif ($task.TenantGroup) { + # Handle tenant groups - expand group to individual tenants + try { + $TenantGroupObject = $task.TenantGroup | ConvertFrom-Json + Write-Host "Expanding tenant group: $($TenantGroupObject.label) with ID: $($TenantGroupObject.value)" + + # Create a tenant filter object for expansion + $TenantFilterForExpansion = @([PSCustomObject]@{ + type = 'Group' + value = $TenantGroupObject.value + label = $TenantGroupObject.label + }) + + # Expand the tenant group to individual tenants + $ExpandedTenants = Expand-CIPPTenantGroups -TenantFilter $TenantFilterForExpansion + + $ExcludedTenants = $task.excludedTenants -split ',' + Write-Host "Excluded Tenants from this task: $ExcludedTenants" + + $GroupTenantCommands = foreach ($ExpandedTenant in $ExpandedTenants | Where-Object { $_.value -notin $ExcludedTenants }) { + $NewParams = $task.Parameters.Clone() + if ((Get-Command $task.Command).Parameters.TenantFilter) { + $NewParams.TenantFilter = $ExpandedTenant.value + } + [pscustomobject]@{ + Command = $task.Command + Parameters = $NewParams + TaskInfo = $task + FunctionName = 'ExecScheduledCommand' + } + } + $Batch.AddRange($GroupTenantCommands) + } catch { + Write-Host "Error expanding tenant group: $($_.Exception.Message)" + Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to expand tenant group for task $($task.Name): $($_.Exception.Message)" -sev Error + + # Fall back to treating as single tenant + if ((Get-Command $task.Command).Parameters.TenantFilter) { + $ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant + } + $Batch.Add($ScheduledCommand) + } } else { + # Handle single tenant if ((Get-Command $task.Command).Parameters.TenantFilter) { $ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant } From e1cd451883b328701f44dcc2a0dad70fbd0ddbd5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Jul 2025 20:50:03 -0400 Subject: [PATCH 079/125] tenant group support for scripted alerts --- .../Alerts/Invoke-ListAlertsQueue.ps1 | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1 index 5cfc332f7ef8..2fd0871a46e3 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-ListAlertsQueue { +function Invoke-ListAlertsQueue { <# .FUNCTIONALITY Entrypoint @@ -70,11 +70,40 @@ Function Invoke-ListAlertsQueue { $ExcludedTenants = @() } + # Handle tenant group display information for alerts + $TenantsForDisplay = @() + if ($Task.TenantGroup) { + try { + $TenantGroupObject = $Task.TenantGroup | ConvertFrom-Json -ErrorAction SilentlyContinue + if ($TenantGroupObject) { + # Create a tenant group object for display + $TenantGroupForDisplay = [PSCustomObject]@{ + label = $TenantGroupObject.label + value = $TenantGroupObject.value + type = 'Group' + } + $TenantsForDisplay = @($TenantGroupForDisplay) + } + } catch { + Write-Warning "Failed to parse tenant group information for alert task $($Task.RowKey): $($_.Exception.Message)" + # Fall back to regular tenant display + $TenantsForDisplay = @($Task.Tenant) + } + } else { + # For regular tenants, create a tenant object for consistent formatting + $TenantForDisplay = [PSCustomObject]@{ + label = $Task.Tenant + value = $Task.Tenant + type = 'Tenant' + } + $TenantsForDisplay = @($TenantForDisplay) + } + $TaskEntry = [PSCustomObject]@{ RowKey = $Task.RowKey PartitionKey = $Task.PartitionKey excludedTenants = @($ExcludedTenants) - Tenants = @($Task.Tenant) + Tenants = $TenantsForDisplay Conditions = $Task.Name Actions = $Task.PostExecution LogType = 'Scripted' @@ -82,10 +111,46 @@ Function Invoke-ListAlertsQueue { RepeatsEvery = $Task.Recurrence RawAlert = $Task } + if ($AllowedTenants -notcontains 'AllTenants') { - $Tenant = $TenantList | Where-Object -Property defaultDomainName -EQ $Task.Tenant - if ($AllowedTenants -contains $Tenant.customerId) { - $AllTasksArrayList.Add($TaskEntry) + # For tenant groups, we need to expand and check access + if ($Task.TenantGroup) { + try { + $TenantGroupObject = $Task.TenantGroup | ConvertFrom-Json -ErrorAction SilentlyContinue + if ($TenantGroupObject) { + # Create a tenant filter object for expansion + $TenantFilterForExpansion = @([PSCustomObject]@{ + type = 'Group' + value = $TenantGroupObject.value + label = $TenantGroupObject.label + }) + + # Expand the tenant group to individual tenants + $ExpandedTenants = Expand-CIPPTenantGroups -TenantFilter $TenantFilterForExpansion + + # Check if user has access to any tenant in the group + $HasAccess = $false + foreach ($ExpandedTenant in $ExpandedTenants) { + $TenantInfo = $TenantList | Where-Object -Property defaultDomainName -EQ $ExpandedTenant.value + if ($TenantInfo -and $AllowedTenants -contains $TenantInfo.customerId) { + $HasAccess = $true + break + } + } + + if ($HasAccess) { + $AllTasksArrayList.Add($TaskEntry) + } + } + } catch { + Write-Warning "Failed to expand tenant group for access check: $($_.Exception.Message)" + } + } else { + # Regular tenant access check + $Tenant = $TenantList | Where-Object -Property defaultDomainName -EQ $Task.Tenant + if ($AllowedTenants -contains $Tenant.customerId) { + $AllTasksArrayList.Add($TaskEntry) + } } } else { $AllTasksArrayList.Add($TaskEntry) From 359ea319389b55cc457bb4447a1b872cf655a904 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Jul 2025 20:58:09 -0400 Subject: [PATCH 080/125] Update Invoke-ListScheduledItems.ps1 --- .../CIPP/Scheduler/Invoke-ListScheduledItems.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 index dee4667f6ae8..6f14a101d484 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 @@ -89,14 +89,18 @@ function Invoke-ListScheduledItems { } $Task | Add-Member -NotePropertyName TenantGroupInfo -NotePropertyValue $TenantGroupForDisplay -Force # Update the tenant to show the group object for proper formatting - $Task.Tenant = @($TenantGroupForDisplay) + $Task.Tenant = $TenantGroupForDisplay } } catch { Write-Warning "Failed to parse tenant group information for task $($Task.RowKey): $($_.Exception.Message)" # Fall back to keeping original tenant value } } else { - $Task.Tenant = @($Task.Tenant) + $Task.Tenant = [PSCustomObject]@{ + label = $Task.Tenant + value = $Task.Tenant + type = 'Tenant' + } } $Task From 1e300e4ceb6ff1d6ef61fc25db550f4e6bbbf40c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Jul 2025 21:09:21 -0400 Subject: [PATCH 081/125] tweak scheduled tasks --- .../CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 10 +++++----- .../Invoke-ListScheduledItemDetails.ps1 | 4 ++-- .../Start-UserTasksOrchestrator.ps1 | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index e75c27add72c..cc4925ddbe42 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -99,17 +99,17 @@ function Add-CIPPScheduledTask { $excludedTenants = if ($task.excludedTenants.value) { $task.excludedTenants.value -join ',' } - + # Handle tenant filter - support both single tenant and tenant groups $tenantFilter = $task.TenantFilter.value ? $task.TenantFilter.value : $task.TenantFilter $originalTenantFilter = $task.TenantFilter - + # If tenant filter is a complex object (from form), extract the value if ($tenantFilter -is [PSCustomObject] -and $tenantFilter.value) { $originalTenantFilter = $tenantFilter $tenantFilter = $tenantFilter.value } - + # If tenant filter is a string but still seems to be JSON, try to parse it if ($tenantFilter -is [string] -and $tenantFilter.StartsWith('{')) { try { @@ -123,7 +123,7 @@ function Add-CIPPScheduledTask { Write-Warning "Could not parse tenant filter JSON: $tenantFilter" } } - + $entity = @{ PartitionKey = [string]'ScheduledTask' TaskState = [string]'Planned' @@ -140,7 +140,7 @@ function Add-CIPPScheduledTask { Hidden = [bool]$Hidden Results = 'Planned' } - + # Store the original tenant filter for group expansion during execution if ($originalTenantFilter -is [PSCustomObject] -and $originalTenantFilter.type -eq 'Group') { $entity['TenantGroup'] = [string]($originalTenantFilter | ConvertTo-Json -Compress) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 index 208c6aa12833..e9ab008ffe50 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 @@ -66,7 +66,7 @@ function Invoke-ListScheduledItemDetails { $TenantGroupForDisplay = [PSCustomObject]@{ label = $TenantGroupObject.label value = $TenantGroupObject.value - type = 'Group' + type = 'Group' } $Task | Add-Member -NotePropertyName TenantGroupInfo -NotePropertyValue $TenantGroupForDisplay -Force # Update the tenant to show the group object for proper formatting @@ -81,7 +81,7 @@ function Invoke-ListScheduledItemDetails { $TenantForDisplay = [PSCustomObject]@{ label = $Task.Tenant value = $Task.Tenant - type = 'Tenant' + type = 'Tenant' } $Task.Tenant = $TenantForDisplay } diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 index 108e9b5ac1c6..ab8383486b3d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-UserTasksOrchestrator.ps1 @@ -55,20 +55,20 @@ function Start-UserTasksOrchestrator { try { $TenantGroupObject = $task.TenantGroup | ConvertFrom-Json Write-Host "Expanding tenant group: $($TenantGroupObject.label) with ID: $($TenantGroupObject.value)" - + # Create a tenant filter object for expansion $TenantFilterForExpansion = @([PSCustomObject]@{ - type = 'Group' - value = $TenantGroupObject.value - label = $TenantGroupObject.label - }) - + type = 'Group' + value = $TenantGroupObject.value + label = $TenantGroupObject.label + }) + # Expand the tenant group to individual tenants $ExpandedTenants = Expand-CIPPTenantGroups -TenantFilter $TenantFilterForExpansion - + $ExcludedTenants = $task.excludedTenants -split ',' Write-Host "Excluded Tenants from this task: $ExcludedTenants" - + $GroupTenantCommands = foreach ($ExpandedTenant in $ExpandedTenants | Where-Object { $_.value -notin $ExcludedTenants }) { $NewParams = $task.Parameters.Clone() if ((Get-Command $task.Command).Parameters.TenantFilter) { @@ -85,7 +85,7 @@ function Start-UserTasksOrchestrator { } catch { Write-Host "Error expanding tenant group: $($_.Exception.Message)" Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to expand tenant group for task $($task.Name): $($_.Exception.Message)" -sev Error - + # Fall back to treating as single tenant if ((Get-Command $task.Command).Parameters.TenantFilter) { $ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant From b8c7320aef4b563787cfd3f8f6c30da9a6b673a7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Jul 2025 11:22:12 -0400 Subject: [PATCH 082/125] local dev fix part 2 --- .../Public/Authentication/Test-CIPPAccess.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 index 0bf10acc8542..43b3de6e8141 100644 --- a/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 +++ b/Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1 @@ -105,6 +105,18 @@ function Test-CIPPAccess { #Write-Information ($User | ConvertTo-Json -Depth 5) # Return user permissions if ($Request.Params.CIPPEndpoint -eq 'me') { + + if (!$User.userRoles) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = ( + @{ + 'clientPrincipal' = $null + 'permissions' = @() + } | ConvertTo-Json -Depth 5) + }) + } + $Permissions = Get-CippAllowedPermissions -UserRoles $User.userRoles Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK From 89e068fdb42a06a1a52b26d77f94404696318d4e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Jul 2025 21:29:19 -0400 Subject: [PATCH 083/125] multiple user support for vacation mode --- .../CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 5 +- .../Conditional/Invoke-ExecCAExclusion.ps1 | 99 ++++++++++++------- .../CIPPCore/Public/Set-CIPPCAExclusion.ps1 | 40 +++++++- 3 files changed, 105 insertions(+), 39 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index cc4925ddbe42..8bdc90d48df6 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -59,7 +59,7 @@ function Add-CIPPScheduledTask { if ($null -eq $Param -or $Param -eq '' -or ($Param | Measure-Object).Count -eq 0) { continue } - if ($Param -is [System.Collections.IDictionary] -or $Param.Key) { + if ($Param -is [System.Collections.IDictionary] -and $Param.Key) { $ht = @{} foreach ($p in $Param.GetEnumerator()) { $ht[$p.Key] = $p.Value @@ -77,6 +77,9 @@ function Add-CIPPScheduledTask { $Parameters = ($Parameters | ConvertTo-Json -Depth 10 -Compress) $AdditionalProperties = [System.Collections.Hashtable]@{} foreach ($Prop in $task.AdditionalProperties) { + if ($null -eq $Prop.Value -or $Prop.Value -eq '' -or ($Prop.Value | Measure-Object).Count -eq 0) { + continue + } $AdditionalProperties[$Prop.Key] = $Prop.Value } $AdditionalProperties = ([PSCustomObject]$AdditionalProperties | ConvertTo-Json -Compress) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 index 758b92eaef24..796a5e2c575a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-ExecCAExclusion { +function Invoke-ExecCAExclusion { <# .FUNCTIONALITY Entrypoint @@ -13,48 +13,79 @@ Function Invoke-ExecCAExclusion { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' - - #If UserId is a guid, get the user's UPN - $TenantFilter = $Request.Body.tenantFilter - $UserId = $Request.Body.UserID - $EndDate = $Request.Body.EndDate - $PolicyId = $Request.Body.PolicyId - $ExclusionType = $Request.Body.ExclusionType + try { + #If UserId is a guid, get the user's UPN + $TenantFilter = $Request.Body.tenantFilter + $UserID = $Request.Body.UserID + $Users = $Request.Body.Users + $EndDate = $Request.Body.EndDate + $PolicyId = $Request.Body.PolicyId + $ExclusionType = $Request.Body.ExclusionType - if ($UserId -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { - $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserId)" -tenantid $TenantFilter).userPrincipalName - } - if ($Request.Body.vacation -eq 'true') { - $StartDate = $Request.Body.StartDate - $EndDate = $Request.Body.EndDate - $TaskBody = [pscustomobject]@{ - TenantFilter = $TenantFilter - Name = "Add CA Exclusion Vacation Mode: $Username - $($TenantFilter)" - Command = @{ - value = 'Set-CIPPCAExclusion' - label = 'Set-CIPPCAExclusion' + if ($Users) { + $UserID = $Users.value + $Username = $Users.addedFields.userPrincipalName -join ', ' + } else { + if ($UserID -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter).userPrincipalName } - Parameters = [pscustomobject]@{ + } + if ($Request.Body.vacation -eq 'true') { + $StartDate = $Request.Body.StartDate + $EndDate = $Request.Body.EndDate + + $Parameters = [PSCustomObject]@{ ExclusionType = 'Add' - UserID = $UserID PolicyId = $PolicyId - UserName = $Username } - ScheduledTime = $StartDate + + if ($Users) { + $Parameters | Add-Member -NotePropertyName Users -NotePropertyValue $Users + } else { + $Parameters | Add-Member -NotePropertyName UserID -NotePropertyValue $UserID + } + + $TaskBody = [pscustomobject]@{ + TenantFilter = $TenantFilter + Name = "Add CA Exclusion Vacation Mode: $Username - $($TenantFilter)" + Command = @{ + value = 'Set-CIPPCAExclusion' + label = 'Set-CIPPCAExclusion' + } + Parameters = [pscustomobject]$Parameters + ScheduledTime = $StartDate + } + + Write-Information ($TaskBody | ConvertTo-Json -Depth 10) + + Add-CIPPScheduledTask -Task $TaskBody -hidden $false + #Removal of the exclusion + $TaskBody.Parameters.ExclusionType = 'Remove' + $TaskBody.Name = "Remove CA Exclusion Vacation Mode: $Username - $($TenantFilter)" + $TaskBody.ScheduledTime = $EndDate + Add-CIPPScheduledTask -Task $TaskBody -hidden $false + $body = @{ Results = "Successfully added vacation mode schedule for $Username." } + } else { + $Parameters = @{ + ExclusionType = $ExclusionType + PolicyId = $PolicyId + } + if ($Users) { + $Parameters.Users = $Users + } else { + $Parameters.UserID = $UserID + } + + Set-CIPPCAExclusion -TenantFilter $TenantFilter -Headers $Headers @Parameters } - Add-CIPPScheduledTask -Task $TaskBody -hidden $false - #Removal of the exclusion - $TaskBody.Parameters.ExclusionType = 'Remove' - $TaskBody.Name = "Remove CA Exclusion Vacation Mode: $Username - $($TenantFilter)" - $TaskBody.ScheduledTime = $EndDate - Add-CIPPScheduledTask -Task $TaskBody -hidden $false - $body = @{ Results = "Successfully added vacation mode schedule for $Username." } - } else { - Set-CIPPCAExclusion -TenantFilter $TenantFilter -ExclusionType $ExclusionType -UserID $UserID -PolicyId $PolicyId -Headers $Headers -UserName $Username + } catch { + Write-Warning "Failed to perform exclusion for $Username : $($_.Exception.Message)" + Write-Information $_.InvocationInfo.PositionMessage + $body = @{ Results = "Failed to perform exclusion for $Username : $($_.Exception.Message)" } + Write-LogMessage -headers $Headers -API 'Invoke-ExecCAExclusion' -message "Failed to perform exclusion for $Username : $_" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) } - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $Body diff --git a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 index ea3ad84de6a5..aaaf1d09755c 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 @@ -6,17 +6,39 @@ function Set-CIPPCAExclusion { $UserID, $PolicyId, $Username, + $Users, $Headers ) try { $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)" -tenantid $TenantFilter -AsApp $true if ($ExclusionType -eq 'add') { - $NewExclusions = [pscustomobject]@{ - conditions = [pscustomobject]@{ users = [pscustomobject]@{ - excludeUsers = @($CheckExististing.conditions.users.excludeUsers + $UserID) + if ($Users) { + $Username = + $ExcludeUsers = [System.Collections.Generic.List[string]]::new() + foreach ($User in $CheckExististing.conditions.users.excludeUsers) { + $ExcludeUsers.Add($User) + } + foreach ($User in $Users.value) { + $ExcludeUsers.Add($User) + } + $NewExclusions = [pscustomobject]@{ + conditions = [pscustomobject]@{ users = [pscustomobject]@{ + excludeUsers = $ExcludeUsers + } + } + } + } else { + if ($UserID -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter).userPrincipalName + } + $NewExclusions = [pscustomobject]@{ + conditions = [pscustomobject]@{ users = [pscustomobject]@{ + excludeUsers = @($CheckExististing.conditions.users.excludeUsers + $UserID) + } } } } + $RawJson = ConvertTo-Json -Depth 10 -InputObject $NewExclusions if ($PSCmdlet.ShouldProcess($PolicyId, "Add exclusion for $UserID")) { New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true @@ -24,9 +46,18 @@ function Set-CIPPCAExclusion { } if ($ExclusionType -eq 'remove') { + if ($Users) { + $UserID = $Users.value + $Username = $Users.addedFields.userPrincipalName -join ', ' + } else { + if ($UserID -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter).userPrincipalName + } + $UserID = @($UserID) + } $NewExclusions = [pscustomobject]@{ conditions = [pscustomobject]@{ users = [pscustomobject]@{ - excludeUsers = @($CheckExististing.conditions.users.excludeUsers | Where-Object { $_ -ne $UserID }) + excludeUsers = @($CheckExististing.conditions.users.excludeUsers | Where-Object { $UserID -notcontains $_ }) } } } @@ -42,3 +73,4 @@ function Set-CIPPCAExclusion { Write-LogMessage -headers $Headers -API 'Set-CIPPConditionalAccessExclusion' -message "Failed to $($ExclusionType) user exclusion for $username from policy $($PolicyId): $_" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) } } + From 5f63ec76300be86332f109e5eab2a197598dbe59 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Jul 2025 22:12:17 -0400 Subject: [PATCH 084/125] update sort for scheduled items ca exclusion tweak --- .../Scheduler/Invoke-ListScheduledItems.ps1 | 2 +- .../CIPPCore/Public/Set-CIPPCAExclusion.ps1 | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 index 6f14a101d484..b60fe1e64a15 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItems.ps1 @@ -109,7 +109,7 @@ function Invoke-ListScheduledItems { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = @($ScheduledTasks | Sort-Object -Property ExecutedTime -Descending) + Body = @($ScheduledTasks | Sort-Object -Property ScheduledTime, ExecutedTime -Descending) }) } diff --git a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 index aaaf1d09755c..7706c4559b55 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 @@ -13,13 +13,15 @@ function Set-CIPPCAExclusion { $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)" -tenantid $TenantFilter -AsApp $true if ($ExclusionType -eq 'add') { if ($Users) { - $Username = + $Username = $Users.addedFields.userPrincipalName $ExcludeUsers = [System.Collections.Generic.List[string]]::new() foreach ($User in $CheckExististing.conditions.users.excludeUsers) { $ExcludeUsers.Add($User) } foreach ($User in $Users.value) { - $ExcludeUsers.Add($User) + if ($ExcludeUsers -notcontains $User) { + $ExcludeUsers.Add($User) + } } $NewExclusions = [pscustomobject]@{ conditions = [pscustomobject]@{ users = [pscustomobject]@{ @@ -48,7 +50,7 @@ function Set-CIPPCAExclusion { if ($ExclusionType -eq 'remove') { if ($Users) { $UserID = $Users.value - $Username = $Users.addedFields.userPrincipalName -join ', ' + $Username = $Users.addedFields.userPrincipalName } else { if ($UserID -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter).userPrincipalName @@ -66,11 +68,16 @@ function Set-CIPPCAExclusion { New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true } } - "Successfully performed $($ExclusionType) exclusion for $username from policy $($PolicyId)" - Write-LogMessage -headers $Headers -API 'Set-CIPPConditionalAccessExclusion' -message "Successfully performed $($ExclusionType) exclusion for $username from policy $($PolicyId)" -Sev 'Info' -tenant $TenantFilter + + foreach ($User in $Username) { + "Successfully performed $($ExclusionType) exclusion for $User from policy $($PolicyId)" + Write-LogMessage -headers $Headers -API 'Set-CIPPCAExclusion' -message "Successfully performed $($ExclusionType) exclusion for $User from policy $($PolicyId)" -Sev 'Info' -tenant $TenantFilter + } } catch { - "Failed to $($ExclusionType) user exclusion for $username from policy $($PolicyId): $($_.Exception.Message)" - Write-LogMessage -headers $Headers -API 'Set-CIPPConditionalAccessExclusion' -message "Failed to $($ExclusionType) user exclusion for $username from policy $($PolicyId): $_" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) + foreach ($User in $Username) { + "Failed to $($ExclusionType) user exclusion for $User from policy $($PolicyId): $($_.Exception.Message)" + Write-LogMessage -headers $Headers -API 'Set-CIPPCAExclusion' -message "Failed to $($ExclusionType) user exclusion for $User from policy $($PolicyId): $_" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) + } } } From 6e53124e94c0e4da8d41a7cd81a729406d14552a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Jul 2025 23:12:46 -0400 Subject: [PATCH 085/125] update logging --- .../Conditional/Invoke-ExecCAExclusion.ps1 | 5 +++-- .../CIPPCore/Public/Set-CIPPCAExclusion.ps1 | 20 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 index 796a5e2c575a..8533226f7fb3 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 @@ -22,6 +22,7 @@ function Invoke-ExecCAExclusion { $PolicyId = $Request.Body.PolicyId $ExclusionType = $Request.Body.ExclusionType + $Policy = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)?`$select=id,displayName" -tenantid $TenantFilter if ($Users) { $UserID = $Users.value @@ -48,7 +49,7 @@ function Invoke-ExecCAExclusion { $TaskBody = [pscustomobject]@{ TenantFilter = $TenantFilter - Name = "Add CA Exclusion Vacation Mode: $Username - $($TenantFilter)" + Name = "Add CA Exclusion Vacation Mode: $PolicyName" Command = @{ value = 'Set-CIPPCAExclusion' label = 'Set-CIPPCAExclusion' @@ -62,7 +63,7 @@ function Invoke-ExecCAExclusion { Add-CIPPScheduledTask -Task $TaskBody -hidden $false #Removal of the exclusion $TaskBody.Parameters.ExclusionType = 'Remove' - $TaskBody.Name = "Remove CA Exclusion Vacation Mode: $Username - $($TenantFilter)" + $TaskBody.Name = "Remove CA Exclusion Vacation Mode: $PolicyName" $TaskBody.ScheduledTime = $EndDate Add-CIPPScheduledTask -Task $TaskBody -hidden $false $body = @{ Results = "Successfully added vacation mode schedule for $Username." } diff --git a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 index 7706c4559b55..41e7b560e982 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 @@ -10,12 +10,12 @@ function Set-CIPPCAExclusion { $Headers ) try { - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)" -tenantid $TenantFilter -AsApp $true + $CheckExisting = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)" -tenantid $TenantFilter -AsApp $true if ($ExclusionType -eq 'add') { if ($Users) { $Username = $Users.addedFields.userPrincipalName $ExcludeUsers = [System.Collections.Generic.List[string]]::new() - foreach ($User in $CheckExististing.conditions.users.excludeUsers) { + foreach ($User in $CheckExisting.conditions.users.excludeUsers) { $ExcludeUsers.Add($User) } foreach ($User in $Users.value) { @@ -35,7 +35,7 @@ function Set-CIPPCAExclusion { } $NewExclusions = [pscustomobject]@{ conditions = [pscustomobject]@{ users = [pscustomobject]@{ - excludeUsers = @($CheckExististing.conditions.users.excludeUsers + $UserID) + excludeUsers = @($CheckExisting.conditions.users.excludeUsers + $UserID) } } } @@ -43,7 +43,7 @@ function Set-CIPPCAExclusion { $RawJson = ConvertTo-Json -Depth 10 -InputObject $NewExclusions if ($PSCmdlet.ShouldProcess($PolicyId, "Add exclusion for $UserID")) { - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExisting.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true } } @@ -59,24 +59,24 @@ function Set-CIPPCAExclusion { } $NewExclusions = [pscustomobject]@{ conditions = [pscustomobject]@{ users = [pscustomobject]@{ - excludeUsers = @($CheckExististing.conditions.users.excludeUsers | Where-Object { $UserID -notcontains $_ }) + excludeUsers = @($CheckExisting.conditions.users.excludeUsers | Where-Object { $UserID -notcontains $_ }) } } } $RawJson = ConvertTo-Json -Depth 10 -InputObject $NewExclusions if ($PSCmdlet.ShouldProcess($PolicyId, "Remove exclusion for $UserID")) { - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExisting.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true } } foreach ($User in $Username) { - "Successfully performed $($ExclusionType) exclusion for $User from policy $($PolicyId)" - Write-LogMessage -headers $Headers -API 'Set-CIPPCAExclusion' -message "Successfully performed $($ExclusionType) exclusion for $User from policy $($PolicyId)" -Sev 'Info' -tenant $TenantFilter + "Successfully performed $($ExclusionType) exclusion for $User from policy $($CheckExisting.displayName)" + Write-LogMessage -headers $Headers -API 'Set-CIPPCAExclusion' -message "Successfully performed $($ExclusionType) exclusion for $User from policy $($CheckExisting.displayName)" -Sev 'Info' -tenant $TenantFilter } } catch { foreach ($User in $Username) { - "Failed to $($ExclusionType) user exclusion for $User from policy $($PolicyId): $($_.Exception.Message)" - Write-LogMessage -headers $Headers -API 'Set-CIPPCAExclusion' -message "Failed to $($ExclusionType) user exclusion for $User from policy $($PolicyId): $_" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) + "Failed to $($ExclusionType) user exclusion for $User from policy $($CheckExisting.displayName): $($_.Exception.Message)" + Write-LogMessage -headers $Headers -API 'Set-CIPPCAExclusion' -message "Failed to $($ExclusionType) user exclusion for $User from policy $($CheckExisting.displayName): $_" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) } } } From 84eb43a0dce3c19bc825e4e3817441be36e0f725 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Jul 2025 23:32:10 -0400 Subject: [PATCH 086/125] Update Invoke-ExecCAExclusion.ps1 --- .../Tenant/Conditional/Invoke-ExecCAExclusion.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 index 8533226f7fb3..1dd69799fbeb 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 @@ -22,8 +22,6 @@ function Invoke-ExecCAExclusion { $PolicyId = $Request.Body.PolicyId $ExclusionType = $Request.Body.ExclusionType - $Policy = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)?`$select=id,displayName" -tenantid $TenantFilter - if ($Users) { $UserID = $Users.value $Username = $Users.addedFields.userPrincipalName -join ', ' @@ -32,6 +30,14 @@ function Invoke-ExecCAExclusion { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter).userPrincipalName } } + + $Policy = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)?`$select=id,displayName" -tenantid $TenantFilter -asApp $true + + if (-not $Policy) { + throw "Policy with ID $PolicyId not found in tenant $TenantFilter." + } + + $PolicyName = $Policy.displayName if ($Request.Body.vacation -eq 'true') { $StartDate = $Request.Body.StartDate $EndDate = $Request.Body.EndDate From 8bdf57c222e2310c0f8f538579d467fd651d639f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Jul 2025 23:35:23 -0400 Subject: [PATCH 087/125] Update Invoke-ExecCAExclusion.ps1 --- .../Tenant/Conditional/Invoke-ExecCAExclusion.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 index 1dd69799fbeb..3ff15b901e71 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCAExclusion.ps1 @@ -17,6 +17,7 @@ function Invoke-ExecCAExclusion { #If UserId is a guid, get the user's UPN $TenantFilter = $Request.Body.tenantFilter $UserID = $Request.Body.UserID + $Username = $Request.Body.Username $Users = $Request.Body.Users $EndDate = $Request.Body.EndDate $PolicyId = $Request.Body.PolicyId @@ -26,7 +27,7 @@ function Invoke-ExecCAExclusion { $UserID = $Users.value $Username = $Users.addedFields.userPrincipalName -join ', ' } else { - if ($UserID -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + if ($UserID -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$' -and -not $Username) { $Username = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($UserID)" -tenantid $TenantFilter).userPrincipalName } } From 466cb6d9a841ff62005a3ed327d0c982b1b403bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 11 Jul 2025 13:09:12 +0200 Subject: [PATCH 088/125] - fix returns and logging. - add error handling - Fix https://github.com/KelvinTegelaar/CIPP/issues/4423 --- .../Teams-Sharepoint/Invoke-AddSite.ps1 | 4 ++-- .../Public/New-CIPPSharepointSite.ps1 | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 index 13eb06e766b1..8f720dc32a19 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-AddSite { +function Invoke-AddSite { <# .FUNCTIONALITY Entrypoint @@ -24,7 +24,7 @@ Function Invoke-AddSite { $StatusCode = [HttpStatusCode]::OK } catch { $StatusCode = [HttpStatusCode]::InternalServerError - $Result = "Failed to create SharePoint Site: $($_.Exception.Message)" + $Result = $_.Exception.Message } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 index cc5f7a3a1203..09ac3a485e20 100644 --- a/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPSharepointSite.ps1 @@ -32,7 +32,7 @@ function New-CIPPSharepointSite { #> [CmdletBinding(SupportsShouldProcess = $true)] - Param( + param( [Parameter(Mandatory = $true)] [string]$SiteName, @@ -139,7 +139,14 @@ function New-CIPPSharepointSite { 'accept' = 'application/json;odata.metadata=none' 'odata-version' = '4.0' } - $Results = New-GraphPostRequest -scope "$($SharePointInfo.AdminUrl)/.default" -uri "$($SharePointInfo.AdminUrl)/_api/SPSiteManager/create" -Body ($body | ConvertTo-Json -Compress -Depth 10) -tenantid $TenantFilter -ContentType 'application/json' -AddedHeaders $AddedHeaders + try { + $Results = New-GraphPOSTRequest -scope "$($SharePointInfo.AdminUrl)/.default" -uri "$($SharePointInfo.AdminUrl)/_api/SPSiteManager/create" -body (ConvertTo-Json -Depth 10 -InputObject $body) -tenantid $TenantFilter -AddedHeaders $AddedHeaders + } catch { + $ErrorMessage = Get-CippException -Exception $_ + $Result = "Failed to create new SharePoint site $SiteName with URL $SiteUrl. Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Error -LogData $ErrorMessage + throw $Result + } } # Check the results. This response is weird. https://learn.microsoft.com/en-us/sharepoint/dev/apis/site-creation-rest @@ -147,29 +154,29 @@ function New-CIPPSharepointSite { '0' { $Result = "Failed to create new SharePoint site $SiteName with URL $SiteUrl. The site doesn't exist." Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Error - throw $Results + throw $Result } '1' { $Result = "Successfully created new SharePoint site $SiteName with URL $SiteUrl. The site is however currently being provisioned. Please wait for it to finish." Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Info - return $Results + return $Result } '2' { $Result = "Successfully created new SharePoint site $SiteName with URL $SiteUrl" Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Info - return $Results + return $Result } '3' { $Result = "Failed to create new SharePoint site $SiteName with URL $SiteUrl. An error occurred while provisioning the site." Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Error - throw $Results + throw $Result } '4' { Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message $Result -sev Error $Result = "Failed to create new SharePoint site $SiteName with URL $SiteUrl. The site already exists." throw $Result } - Default {} + default {} } From be076b57d7f39e4b8b181a3e98c09475374f438b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 11:48:53 -0400 Subject: [PATCH 089/125] scheduler fixes --- .../CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 275 ++++++++++-------- .../Push-ExecScheduledCommand.ps1 | 1 + .../Invoke-ListScheduledItemDetails.ps1 | 2 +- .../Scheduler/Invoke-RemoveScheduledItem.ps1 | 1 - .../Invoke-ListConditionalAccessPolicies.ps1 | 71 ++--- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 1 - 6 files changed, 190 insertions(+), 161 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 8bdc90d48df6..ad3a24553976 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -24,149 +24,178 @@ function Add-CIPPScheduledTask { $Headers ) - $Table = Get-CIPPTable -TableName 'ScheduledTasks' - - if ($RunNow.IsPresent -and $RowKey) { - try { - $Filter = "PartitionKey eq 'ScheduledTask' and RowKey eq '$($RowKey)'" - $ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) - $ExistingTask.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds - $ExistingTask.TaskState = 'Planned' - Add-CIPPAzDataTableEntity @Table -Entity $ExistingTask -Force - Write-LogMessage -headers $Headers -API 'RunNow' -message "Task $($ExistingTask.Name) scheduled to run now" -Sev 'Info' -Tenant $ExistingTask.Tenant - return "Task $($ExistingTask.Name) scheduled to run now" - } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -headers $Headers -API 'RunNow' -message "Could not run task: $ErrorMessage" -Sev 'Error' - return "Could not run task: $ErrorMessage" - } - } else { - if ($DisallowDuplicateName) { - $Filter = "PartitionKey eq 'ScheduledTask' and Name eq '$($Task.Name)'" - $ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) - if ($ExistingTask) { - return "Task with name $($Task.Name) already exists" - } - } + try { - $propertiesToCheck = @('Webhook', 'Email', 'PSA') - $PostExecutionObject = ($propertiesToCheck | Where-Object { $task.PostExecution.$_ -eq $true }) - $PostExecution = $PostExecutionObject ? ($PostExecutionObject -join ',') : ($Task.PostExecution.value -join ',') - $Parameters = [System.Collections.Hashtable]@{} - foreach ($Key in $task.Parameters.PSObject.Properties.Name) { - $Param = $task.Parameters.$Key + $Table = Get-CIPPTable -TableName 'ScheduledTasks' - if ($null -eq $Param -or $Param -eq '' -or ($Param | Measure-Object).Count -eq 0) { - continue + if ($RunNow.IsPresent -and $RowKey) { + try { + $Filter = "PartitionKey eq 'ScheduledTask' and RowKey eq '$($RowKey)'" + $ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + $ExistingTask.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds + $ExistingTask.TaskState = 'Planned' + Add-CIPPAzDataTableEntity @Table -Entity $ExistingTask -Force + Write-LogMessage -headers $Headers -API 'RunNow' -message "Task $($ExistingTask.Name) scheduled to run now" -Sev 'Info' -Tenant $ExistingTask.Tenant + return "Task $($ExistingTask.Name) scheduled to run now" + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -headers $Headers -API 'RunNow' -message "Could not run task: $ErrorMessage" -Sev 'Error' + return "Could not run task: $ErrorMessage" } - if ($Param -is [System.Collections.IDictionary] -and $Param.Key) { - $ht = @{} - foreach ($p in $Param.GetEnumerator()) { - $ht[$p.Key] = $p.Value + } else { + if ($DisallowDuplicateName) { + $Filter = "PartitionKey eq 'ScheduledTask' and Name eq '$($Task.Name)'" + $ExistingTask = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) + if ($ExistingTask) { + return "Task with name $($Task.Name) already exists" } - $Parameters[$Key] = [PSCustomObject]$ht - } else { - $Parameters[$Key] = $Param } - } - if ($Headers) { - $Parameters.Headers = $Headers | Select-Object -Property 'x-forwarded-for', 'x-ms-client-principal', 'x-ms-client-principal-idp', 'x-ms-client-principal-name' - } + $propertiesToCheck = @('Webhook', 'Email', 'PSA') + $PostExecutionObject = ($propertiesToCheck | Where-Object { $task.PostExecution.$_ -eq $true }) + $PostExecution = $PostExecutionObject ? ($PostExecutionObject -join ',') : ($Task.PostExecution.value -join ',') + $Parameters = [System.Collections.Hashtable]@{} + foreach ($Key in $task.Parameters.PSObject.Properties.Name) { + $Param = $task.Parameters.$Key + + if ($null -eq $Param -or $Param -eq '' -or ($Param | Measure-Object).Count -eq 0) { + continue + } - $Parameters = ($Parameters | ConvertTo-Json -Depth 10 -Compress) - $AdditionalProperties = [System.Collections.Hashtable]@{} - foreach ($Prop in $task.AdditionalProperties) { - if ($null -eq $Prop.Value -or $Prop.Value -eq '' -or ($Prop.Value | Measure-Object).Count -eq 0) { - continue + # handle different object types in params + if ($Param -is [System.Collections.IDictionary] -or $Param[0].Key) { + Write-Information "Parameter $Key is a hashtable" + $ht = @{} + foreach ($p in $Param.GetEnumerator()) { + $ht[$p.Key] = $p.Value + } + $Parameters[$Key] = [PSCustomObject]$ht + Write-Information "Converted $Key to PSObject $($Parameters[$Key] | ConvertTo-Json -Compress)" + } elseif ($Param -is [System.Object[]] -and -not ($Param -is [string])) { + Write-Information "Parameter $Key is an enumerable object" + $Param = $Param | ForEach-Object { + if ($null -eq $_) { + # Skip null entries + return + } + if ($_ -is [System.Collections.IDictionary]) { + [PSCustomObject]$_ + } elseif ($_ -is [PSCustomObject]) { + $_ + } else { + $_ + } + } | Where-Object { $null -ne $_ } + $Parameters[$Key] = $Param + } else { + Write-Information "Parameter $Key is a simple value" + $Parameters[$Key] = $Param + } } - $AdditionalProperties[$Prop.Key] = $Prop.Value - } - $AdditionalProperties = ([PSCustomObject]$AdditionalProperties | ConvertTo-Json -Compress) - if ($Parameters -eq 'null') { $Parameters = '' } - if (!$Task.RowKey) { - $RowKey = (New-Guid).Guid - } else { - $RowKey = $Task.RowKey - } - $Recurrence = if ([string]::IsNullOrEmpty($task.Recurrence.value)) { - $task.Recurrence - } else { - $task.Recurrence.value - } + if ($Headers) { + $Parameters.Headers = $Headers | Select-Object -Property 'x-forwarded-for', 'x-ms-client-principal', 'x-ms-client-principal-idp', 'x-ms-client-principal-name' + } - if ([int64]$task.ScheduledTime -eq 0 -or [string]::IsNullOrEmpty($task.ScheduledTime)) { - $task.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds - } - $excludedTenants = if ($task.excludedTenants.value) { - $task.excludedTenants.value -join ',' - } + $Parameters = ($Parameters | ConvertTo-Json -Depth 10 -Compress) + $AdditionalProperties = [System.Collections.Hashtable]@{} + foreach ($Prop in $task.AdditionalProperties) { + if ($null -eq $Prop.Value -or $Prop.Value -eq '' -or ($Prop.Value | Measure-Object).Count -eq 0) { + continue + } + $AdditionalProperties[$Prop.Key] = $Prop.Value + } + $AdditionalProperties = ([PSCustomObject]$AdditionalProperties | ConvertTo-Json -Compress) + if ($Parameters -eq 'null') { $Parameters = '' } + if (!$Task.RowKey) { + $RowKey = (New-Guid).Guid + } else { + $RowKey = $Task.RowKey + } - # Handle tenant filter - support both single tenant and tenant groups - $tenantFilter = $task.TenantFilter.value ? $task.TenantFilter.value : $task.TenantFilter - $originalTenantFilter = $task.TenantFilter + $Recurrence = if ([string]::IsNullOrEmpty($task.Recurrence.value)) { + $task.Recurrence + } else { + $task.Recurrence.value + } - # If tenant filter is a complex object (from form), extract the value - if ($tenantFilter -is [PSCustomObject] -and $tenantFilter.value) { - $originalTenantFilter = $tenantFilter - $tenantFilter = $tenantFilter.value - } + if ([int64]$task.ScheduledTime -eq 0 -or [string]::IsNullOrEmpty($task.ScheduledTime)) { + $task.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds + } + $excludedTenants = if ($task.excludedTenants.value) { + $task.excludedTenants.value -join ',' + } - # If tenant filter is a string but still seems to be JSON, try to parse it - if ($tenantFilter -is [string] -and $tenantFilter.StartsWith('{')) { - try { - $parsedTenantFilter = $tenantFilter | ConvertFrom-Json - if ($parsedTenantFilter.value) { - $originalTenantFilter = $parsedTenantFilter - $tenantFilter = $parsedTenantFilter.value + # Handle tenant filter - support both single tenant and tenant groups + $tenantFilter = $task.TenantFilter.value ? $task.TenantFilter.value : $task.TenantFilter + $originalTenantFilter = $task.TenantFilter + + # If tenant filter is a complex object (from form), extract the value + if ($tenantFilter -is [PSCustomObject] -and $tenantFilter.value) { + $originalTenantFilter = $tenantFilter + $tenantFilter = $tenantFilter.value + } + + # If tenant filter is a string but still seems to be JSON, try to parse it + if ($tenantFilter -is [string] -and $tenantFilter.StartsWith('{')) { + try { + $parsedTenantFilter = $tenantFilter | ConvertFrom-Json + if ($parsedTenantFilter.value) { + $originalTenantFilter = $parsedTenantFilter + $tenantFilter = $parsedTenantFilter.value + } + } catch { + # If parsing fails, use the string as is + Write-Warning "Could not parse tenant filter JSON: $tenantFilter" } - } catch { - # If parsing fails, use the string as is - Write-Warning "Could not parse tenant filter JSON: $tenantFilter" } - } - $entity = @{ - PartitionKey = [string]'ScheduledTask' - TaskState = [string]'Planned' - RowKey = [string]$RowKey - Tenant = [string]$tenantFilter - excludedTenants = [string]$excludedTenants - Name = [string]$task.Name - Command = [string]$task.Command.value - Parameters = [string]$Parameters - ScheduledTime = [string]$task.ScheduledTime - Recurrence = [string]$Recurrence - PostExecution = [string]$PostExecution - AdditionalProperties = [string]$AdditionalProperties - Hidden = [bool]$Hidden - Results = 'Planned' - } + $entity = @{ + PartitionKey = [string]'ScheduledTask' + TaskState = [string]'Planned' + RowKey = [string]$RowKey + Tenant = [string]$tenantFilter + excludedTenants = [string]$excludedTenants + Name = [string]$task.Name + Command = [string]$task.Command.value + Parameters = [string]$Parameters + ScheduledTime = [string]$task.ScheduledTime + Recurrence = [string]$Recurrence + PostExecution = [string]$PostExecution + AdditionalProperties = [string]$AdditionalProperties + Hidden = [bool]$Hidden + Results = 'Planned' + } - # Store the original tenant filter for group expansion during execution - if ($originalTenantFilter -is [PSCustomObject] -and $originalTenantFilter.type -eq 'Group') { - $entity['TenantGroup'] = [string]($originalTenantFilter | ConvertTo-Json -Compress) - } elseif ($originalTenantFilter -is [string] -and $originalTenantFilter.StartsWith('{')) { - # Check if it's a serialized group object - try { - $parsedOriginal = $originalTenantFilter | ConvertFrom-Json - if ($parsedOriginal.type -eq 'Group') { - $entity['TenantGroup'] = [string]$originalTenantFilter + # Store the original tenant filter for group expansion during execution + if ($originalTenantFilter -is [PSCustomObject] -and $originalTenantFilter.type -eq 'Group') { + $entity['TenantGroup'] = [string]($originalTenantFilter | ConvertTo-Json -Compress) + } elseif ($originalTenantFilter -is [string] -and $originalTenantFilter.StartsWith('{')) { + # Check if it's a serialized group object + try { + $parsedOriginal = $originalTenantFilter | ConvertFrom-Json + if ($parsedOriginal.type -eq 'Group') { + $entity['TenantGroup'] = [string]$originalTenantFilter + } + } catch { + # Not a JSON object, ignore } + } + if ($SyncType) { + $entity.SyncType = $SyncType + } + try { + Add-CIPPAzDataTableEntity @Table -Entity $entity -Force } catch { - # Not a JSON object, ignore + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + return "Could not add task: $ErrorMessage" } + return "Successfully added task: $($entity.Name)" } - if ($SyncType) { - $entity.SyncType = $SyncType - } - try { - Add-CIPPAzDataTableEntity @Table -Entity $entity -Force - } catch { - $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - return "Could not add task: $ErrorMessage" - } - return "Successfully added task: $($entity.Name)" + } catch { + Write-Warning "Failed to add scheduled task: $($_.Exception.Message)" + Write-Information $_.InvocationInfo.PositionMessage + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + throw "Could not add task: $ErrorMessage" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index 26cf5c71c168..af24f3b8e92d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -62,6 +62,7 @@ function Push-ExecScheduledCommand { $results = & $Item.Command @commandParameters } catch { $results = "Task Failed: $($_.Exception.Message)" + $State = 'Failed' } Write-Host 'ran the command. Processing results' diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 index e9ab008ffe50..10d66559b4b5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ListScheduledItemDetails.ps1 @@ -28,7 +28,7 @@ function Invoke-ListScheduledItemDetails { # Retrieve the task information $TaskTable = Get-CIPPTable -TableName 'ScheduledTasks' - $Task = Get-CIPPAzDataTableEntity @TaskTable -Filter "RowKey eq '$RowKey' and PartitionKey eq 'ScheduledTask'" | Select-Object Name, TaskState, Command, Parameters, Recurrence, ExecutedTime, ScheduledTime, PostExecution, Tenant, TenantGroup, Hidden, Results, Timestamp + $Task = Get-CIPPAzDataTableEntity @TaskTable -Filter "RowKey eq '$RowKey' and PartitionKey eq 'ScheduledTask'" | Select-Object RowKey, Name, TaskState, Command, Parameters, Recurrence, ExecutedTime, ScheduledTime, PostExecution, Tenant, TenantGroup, Hidden, Results, Timestamp if (-not $Task) { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 index 11a0a23f8716..c967d94f6f28 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-RemoveScheduledItem.ps1 @@ -35,5 +35,4 @@ function Invoke-RemoveScheduledItem { Body = @{ Results = 'Task removed successfully.' } }) - } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 index 91f7251eccc7..f4a02084bcb2 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 @@ -23,13 +23,11 @@ function Invoke-ListConditionalAccessPolicies { $Locations ) if ($id -eq 'All') { - return 'All' + return @{label = 'All'; value = 'All' } } $DisplayName = $Locations | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName - if ([string]::IsNullOrEmpty($displayName)) { - return $ID - } else { - return $DisplayName + if (![string]::IsNullOrEmpty($displayName)) { + return @{label = $DisplayName; value = $ID } } } @@ -41,13 +39,13 @@ function Invoke-ListConditionalAccessPolicies { $RoleDefinitions ) if ($id -eq 'All') { - return 'All' + return @{label = 'All'; value = 'All' } } $DisplayName = $RoleDefinitions | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName if ([string]::IsNullOrEmpty($displayName)) { - return $ID + return @{label = $ID; value = $ID } } else { - return $DisplayName + return @{label = $DisplayName; value = $ID } } } @@ -59,13 +57,13 @@ function Invoke-ListConditionalAccessPolicies { $Users ) if ($id -eq 'All') { - return 'All' + return @{label = 'All'; value = 'All' } } $DisplayName = $Users | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName if ([string]::IsNullOrEmpty($displayName)) { - return $ID + return @{label = $ID; value = $ID } } else { - return $DisplayName + return @{label = $DisplayName; value = $ID } } } @@ -76,13 +74,13 @@ function Invoke-ListConditionalAccessPolicies { $Groups ) if ($id -eq 'All') { - return 'All' + return @{label = 'All'; value = 'All' } } $DisplayName = $Groups | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName if ([string]::IsNullOrEmpty($displayName)) { - return 'No Data' + return @{label = 'No Data'; value = 'No Data' } } else { - return $DisplayName + return @{label = $DisplayName; value = $ID } } } @@ -95,7 +93,7 @@ function Invoke-ListConditionalAccessPolicies { $ServicePrincipals ) if ($id -eq 'All') { - return 'All' + return @{label = 'All'; value = 'All' } } $return = $ServicePrincipals | Where-Object { $_.appId -eq $ID } | Select-Object -ExpandProperty DisplayName @@ -112,7 +110,10 @@ function Invoke-ListConditionalAccessPolicies { $return = '' } - return $return + if ($return) { + $return = @{label = $return; value = $ID } + return $return + } } # Interact with query parameters or the body of the request. @@ -176,25 +177,25 @@ function Invoke-ListConditionalAccessPolicies { createdDateTime = $(if (![string]::IsNullOrEmpty($cap.createdDateTime)) { [datetime]$cap.createdDateTime } else { '' }) modifiedDateTime = $(if (![string]::IsNullOrEmpty($cap.modifiedDateTime)) { [datetime]$cap.modifiedDateTime }else { '' }) state = $cap.state - clientAppTypes = ($cap.conditions.clientAppTypes) -join ',' - includePlatforms = ($cap.conditions.platforms.includePlatforms) -join ',' - excludePlatforms = ($cap.conditions.platforms.excludePlatforms) -join ',' - includeLocations = (Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.includeLocations) -join ',' - excludeLocations = (Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.excludeLocations) -join ',' - includeApplications = ($cap.conditions.applications.includeApplications | ForEach-Object { Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $_ }) -join ',' - excludeApplications = ($cap.conditions.applications.excludeApplications | ForEach-Object { Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $_ }) -join ',' - includeUserActions = ($cap.conditions.applications.includeUserActions | Out-String) - includeAuthenticationContextClassReferences = ($cap.conditions.applications.includeAuthenticationContextClassReferences | Out-String) - includeUsers = ($cap.conditions.users.includeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) | Out-String - excludeUsers = ($cap.conditions.users.excludeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) | Out-String - includeGroups = ($cap.conditions.users.includeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) | Out-String - excludeGroups = ($cap.conditions.users.excludeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) | Out-String - includeRoles = ($cap.conditions.users.includeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) | Out-String - excludeRoles = ($cap.conditions.users.excludeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) | Out-String - grantControlsOperator = ($cap.grantControls.operator) -join ',' - builtInControls = ($cap.grantControls.builtInControls) -join ',' - customAuthenticationFactors = ($cap.grantControls.customAuthenticationFactors) -join ',' - termsOfUse = ($cap.grantControls.termsOfUse) -join ',' + clientAppTypes = @(if ($cap.conditions.clientAppTypes) { $cap.conditions.clientAppTypes | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) + includePlatforms = @(if ($cap.conditions.platforms.includePlatforms) { $cap.conditions.platforms.includePlatforms | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) + excludePlatforms = @(if ($cap.conditions.platforms.excludePlatforms) { $cap.conditions.platforms.excludePlatforms | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) + includeLocations = @(Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.includeLocations) + excludeLocations = @(Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.excludeLocations) + includeApplications = @(Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $cap.conditions.applications.includeApplications) + excludeApplications = @(Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $cap.conditions.applications.excludeApplications) + includeUserActions = @($cap.conditions.applications.includeUserActions ) + includeAuthenticationContextClassReferences = @($cap.conditions.applications.includeAuthenticationContextClassReferences ) + includeUsers = @($cap.conditions.users.includeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) + excludeUsers = @($cap.conditions.users.excludeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) + includeGroups = @($cap.conditions.users.includeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) + excludeGroups = @($cap.conditions.users.excludeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) + includeRoles = @($cap.conditions.users.includeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) + excludeRoles = @($cap.conditions.users.excludeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) + grantControlsOperator = @(if ($cap.grantControls.operator) { $cap.grantControls.operator | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) + builtInControls = @(if ($cap.grantControls.builtInControls) { $cap.grantControls.builtInControls | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) + customAuthenticationFactors = @(if ($cap.grantControls.customAuthenticationFactors) { $cap.grantControls.customAuthenticationFactors | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) + termsOfUse = @(if ($cap.grantControls.termsOfUse) { $cap.grantControls.termsOfUse | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) rawjson = ($cap | ConvertTo-Json -Depth 100) } $temp diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index ba9194085e0e..c54be1fb5153 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -102,7 +102,6 @@ function New-CIPPBackup { return [pscustomobject]@{ BackupName = $RowKey BackupState = $State - BackupData = $BackupData } } From 74652dd83ced06f901a66c4da070159217c91257 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 12:01:22 -0400 Subject: [PATCH 090/125] Update New-CIPPBackup.ps1 --- Modules/CIPPCore/Public/New-CIPPBackup.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 index c54be1fb5153..024ba4571721 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackup.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackup.ps1 @@ -89,7 +89,7 @@ function New-CIPPBackup { try { $null = Add-CIPPAzDataTableEntity @Table -entity $entity -Force Write-LogMessage -headers $Headers -API $APINAME -message 'Created backup' -Sev 'Debug' - $State = 'Backup finished succesfully' + $State = 'Backup finished successfully' } catch { $State = 'Failed to write backup to table storage' $ErrorMessage = Get-CippException -Exception $_ @@ -99,9 +99,9 @@ function New-CIPPBackup { } } - return [pscustomobject]@{ - BackupName = $RowKey - BackupState = $State - } + return @([pscustomobject]@{ + BackupName = $RowKey + BackupState = $State + }) } From 551f5d4f7d35fd4df38d4236755657add15af51d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 12:21:05 -0400 Subject: [PATCH 091/125] scheduler/offboarding tweaks --- .../Activity Triggers/Push-ExecScheduledCommand.ps1 | 9 ++++++--- .../Administration/Users/Invoke-CIPPOffboardingJob.ps1 | 6 ++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index af24f3b8e92d..3ed8752ba62d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -74,9 +74,12 @@ function Push-ExecScheduledCommand { if ($results -is [String]) { $results = @{ Results = $results } } - if ($results -is [array] -and $results[0] -is [string]) { - $results = $results | Where-Object { $_ -is [string] } - $results = $results | ForEach-Object { @{ Results = $_ } } + if ($results -is [array] -and $results[0] -is [string] -or $results[0].resultText -is [string]) { + $results = $results | Where-Object { $_ -is [string] -or $_.resultText -is [string] } + $results = $results | ForEach-Object { + $Message = $_.resultText ?? $_ + @{ Results = $Message } + } } if ($results -is [string]) { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 index 2fa91fdb82b8..0b1c0254759e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-CIPPOffboardingJob.ps1 @@ -11,7 +11,9 @@ function Invoke-CIPPOffboardingJob { if ($Options -is [string]) { $Options = $Options | ConvertFrom-Json } - $UserID = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Username)?`$select=id" -tenantid $TenantFilter).id + $User = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($Username)?`$select=id,displayName" -tenantid $TenantFilter + $UserID = $User.id + $DisplayName = $User.displayName Write-Host "Running offboarding job for $Username with options: $($Options | ConvertTo-Json -Depth 10)" $Return = switch ($Options) { { $_.ConvertToShared -eq $true } { @@ -30,7 +32,7 @@ function Invoke-CIPPOffboardingJob { } { $_.ResetPass -eq $true } { try { - Set-CIPPResetPassword -tenantFilter $TenantFilter -UserID $username -Headers $Headers -APIName $APIName + Set-CIPPResetPassword -tenantFilter $TenantFilter -DisplayName -UserID $username -Headers $Headers -APIName $APIName } catch { $_.Exception.Message } From 1837ec2760bf96434195c1fefb39ab300c11902d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 14:23:08 -0400 Subject: [PATCH 092/125] application gallery support --- .../Push-ExecAppApprovalTemplate.ps1 | 67 +++++++++++-- .../Invoke-CIPPStandardAppDeploy.ps1 | 93 ++++++++++++++++--- 2 files changed, 138 insertions(+), 22 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 index 5c55136cccaf..0ca240a2a702 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 @@ -13,16 +13,67 @@ function Push-ExecAppApprovalTemplate { return } - $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Item.Tenant - if ($Item.AppId -notin $ServicePrincipalList.appId) { - Write-Information "Adding $($Item.AppId) to tenant $($Item.Tenant)." - $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Item.tenant -body "{ `"appId`": `"$($Item.appId)`" }" - Write-LogMessage -message "Added $($Item.AppId) to tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info + # Get the template data to determine if it's a Gallery Template or Enterprise App + $Table = Get-CIPPTable -TableName 'templates' + $Template = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'AppApprovalTemplate' and RowKey eq '$TemplateId'" + + if (!$Template) { + Write-LogMessage -message "Template $TemplateId not found" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Error + return + } + + $TemplateData = $Template.JSON | ConvertFrom-Json + # Default to EnterpriseApp for backward compatibility with older templates + $AppType = $TemplateData.AppType + if (-not $AppType) { + $AppType = 'EnterpriseApp' + } + + # Handle Gallery Templates + if ($AppType -eq 'GalleryTemplate') { + Write-Information "Deploying Gallery Template $($TemplateData.AppName) to tenant $($Item.Tenant)." + + # Use the Gallery Template instantiation API + $GalleryTemplateId = $TemplateData.GalleryTemplateId + if (!$GalleryTemplateId) { + Write-LogMessage -message 'Gallery Template ID not found in template data' -tenant $Item.Tenant -API 'Add Multitenant App' -sev Error + return + } + + # Check if the app already exists in the tenant + $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Item.Tenant + if ($TemplateData.GalleryTemplateId -in $ServicePrincipalList.applicationTemplateId) { + Write-LogMessage -message "Gallery Template app $($TemplateData.AppName) already exists in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info + return + } + + # Instantiate the gallery template + $InstantiateBody = @{ + displayName = $TemplateData.AppName + } | ConvertTo-Json -Depth 10 + + $InstantiateResult = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applicationTemplates/$GalleryTemplateId/instantiate" -type POST -tenantid $Item.tenant -body $InstantiateBody + + if ($InstantiateResult.application.appId) { + Write-LogMessage -message "Successfully deployed Gallery Template $($TemplateData.AppName) to tenant $($Item.Tenant). Application ID: $($InstantiateResult.application.appId)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info + New-CIPPApplicationCopy -App $InstantiateResult.application.appId -Tenant $Item.Tenant + } else { + Write-LogMessage -message "Gallery Template deployment completed but application ID not returned for $($TemplateData.AppName) in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Warning + } + } else { - Write-LogMessage -message "This app already exists in tenant $($Item.Tenant). We're adding the required permissions." -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info + # Handle Enterprise Apps (existing logic) + $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Item.Tenant + if ($Item.AppId -notin $ServicePrincipalList.appId) { + Write-Information "Adding $($Item.AppId) to tenant $($Item.Tenant)." + $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Item.tenant -body "{ `"appId`": `"$($Item.appId)`" }" + Write-LogMessage -message "Added $($Item.AppId) to tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info + } else { + Write-LogMessage -message "This app already exists in tenant $($Item.Tenant). We're adding the required permissions." -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info + } + Add-CIPPApplicationPermission -TemplateId $TemplateId -Tenantfilter $Item.Tenant + Add-CIPPDelegatedPermission -TemplateId $TemplateId -Tenantfilter $Item.Tenant } - Add-CIPPApplicationPermission -TemplateId $TemplateId -Tenantfilter $Item.Tenant - Add-CIPPDelegatedPermission -TemplateId $TemplateId -Tenantfilter $Item.Tenant } catch { Write-LogMessage -message "Error adding application to tenant $($Item.Tenant) - $($_.Exception.Message)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Error Write-Error $_.Exception.Message diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 index f4c51a1a7048..93083d0c51ad 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 @@ -38,11 +38,23 @@ function Invoke-CIPPStandardAppDeploy { $Mode = $Settings.mode ?? 'copy' if ($Mode -eq 'template') { - $AppsToAdd = $Settings.templateIds.addedFields.AppId - } + # For template mode, we need to check each template individually + # since Gallery Templates and Enterprise Apps have different deployment methods + $AppsToAdd = @() + $Table = Get-CIPPTable -TableName 'templates' + $AppsToAdd = foreach ($TemplateId in $Settings.templateIds.value) { + $Template = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'AppApprovalTemplate' and RowKey eq '$TemplateId'" + if ($Template) { + $TemplateData = $Template.JSON | ConvertFrom-Json + if ($TemplateData.AppId) { + $TemplateData.AppId ?? $TemplateData.GalleryTemplateId + } + } + } + } $MissingApps = foreach ($App in $AppsToAdd) { - if ($App -notin $AppExists.appId) { + if ($App -notin $AppExists.appId -and $App -notin $AppExists.applicationTemplateId) { $App } } @@ -67,21 +79,74 @@ function Invoke-CIPPStandardAppDeploy { $TemplateName = $Settings.templateIds.label $AppIds = $Settings.templateIds.addedFields.AppId - foreach ($AppId in $AppIds) { - if ($AppId -notin $AppExists.appId) { - Write-Information "Adding $AppId to tenant $Tenant." - $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Tenant -body "{ `"appId`": `"$AppId`" }" - Write-LogMessage -message "Added $AppId to tenant $Tenant" -tenant $Tenant -API 'Add Multitenant App' -sev Info - } - } + # Get template data to determine deployment type for each template + $Table = Get-CIPPTable -TableName 'templates' + foreach ($TemplateId in $TemplateIds) { try { - Add-CIPPApplicationPermission -TemplateId $TemplateId -TenantFilter $Tenant - Add-CIPPDelegatedPermission -TemplateId $TemplateId -TenantFilter $Tenant - Write-LogMessage -API 'Standards' -tenant $tenant -message "Added application(s) from template $($TemplateName) and updated it's permissions" -sev Info + # Get the template data to determine if it's a Gallery Template or Enterprise App + $Template = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'AppApprovalTemplate' and RowKey eq '$TemplateId'" + + if (!$Template) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Template $TemplateId not found" -sev Error + continue + } + + $TemplateData = $Template.JSON | ConvertFrom-Json + # Default to EnterpriseApp for backward compatibility with older templates + $AppType = $TemplateData.AppType + if (-not $AppType) { + $AppType = 'EnterpriseApp' + } + + if ($AppType -eq 'GalleryTemplate') { + # Handle Gallery Template deployment + Write-Information "Deploying Gallery Template $($TemplateData.AppName) to tenant $Tenant." + + $GalleryTemplateId = $TemplateData.GalleryTemplateId + if (!$GalleryTemplateId) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Gallery Template ID not found in template data for $($TemplateData.TemplateName)" -sev Error + continue + } + + # Check if the app already exists in the tenant + if ($TemplateData.GalleryTemplateId -in $AppExists.applicationTemplateId) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Gallery Template app $($TemplateData.AppName) already exists in tenant $Tenant" -sev Info + continue + } + + # Instantiate the gallery template + $InstantiateBody = @{ + displayName = $TemplateData.AppName + } | ConvertTo-Json -Depth 10 + + $InstantiateResult = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applicationTemplates/$GalleryTemplateId/instantiate" -type POST -tenantid $Tenant -body $InstantiateBody + + if ($InstantiateResult.application.appId) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully deployed Gallery Template $($TemplateData.AppName) to tenant $Tenant. Application ID: $($InstantiateResult.application.appId)" -sev Info + New-CIPPApplicationCopy -App $InstantiateResult.application.appId -Tenant $Tenant + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Gallery Template deployment completed but application ID not returned for $($TemplateData.AppName) in tenant $Tenant" -sev Warning + } + + } else { + # Handle Enterprise App deployment (existing logic) + $AppId = $TemplateData.AppId + if ($AppId -notin $AppExists.appId) { + Write-Information "Adding $AppId to tenant $Tenant." + $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Tenant -body "{ `"appId`": `"$AppId`" }" + Write-LogMessage -message "Added $AppId to tenant $Tenant" -tenant $Tenant -API 'Standards' -sev Info + } + + # Apply permissions for Enterprise Apps + Add-CIPPApplicationPermission -TemplateId $TemplateId -TenantFilter $Tenant + Add-CIPPDelegatedPermission -TemplateId $TemplateId -TenantFilter $Tenant + Write-LogMessage -API 'Standards' -tenant $tenant -message "Added application $($TemplateData.AppName) from Enterprise App template and updated its permissions" -sev Info + } + } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to add app from approval template $($TemplateName). Error: $ErrorMessage" -sev Error + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to deploy template $TemplateId. Error: $ErrorMessage" -sev Error } } } From 4c717013de4350d71bb80be908411d8eb5f3cf67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 11 Jul 2025 20:38:07 +0200 Subject: [PATCH 093/125] Update license files to newest from MS --- ConversionTable.csv | 255 +++++++++++------- Modules/CIPPCore/Public/ConversionTable.csv | 255 +++++++++++------- Modules/CippExtensions/ConversionTable.csv | 255 +++++++++++------- .../CippExtensions/Public/ConversionTable.csv | 255 +++++++++++------- 4 files changed, 628 insertions(+), 392 deletions(-) diff --git a/ConversionTable.csv b/ConversionTable.csv index 704f7c507d5f..5b7cea1f09ea 100644 --- a/ConversionTable.csv +++ b/ConversionTable.csv @@ -20,6 +20,7 @@ Clipchamp Premium,Clipchamp_Premium,0fe440c5-f2bf-442b-a4f4-9a7af77a200b,ONEDRIV Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,ONEDRIVECLIPCHAMP,f7e5b77d-f293-410a-bae8-f941f19fe680,OneDrive for Business (Clipchamp) +Clipchamp Premium Add-on,Clipchamp_Premium_Add_on,4b2c20e4-939d-4bf4-9dd8-6870240cfe19,CLIPCHAMP_PREMIUM,430b908f-78e1-4812-b045-cf83320e7d5d,Microsoft Clipchamp Premium Microsoft 365 Audio Conferencing,MCOMEETADV,0c266dff-15dd-4b49-8397-2bb16070ed52,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Entra ID Basic,AAD_BASIC,2b9c8e7c-319c-43a2-a2a0-48c5c6161de7,AAD_BASIC,c4da7f8a-5ee2-4c99-a7e1-87d2df57f6fe,Microsoft Entra BASIC Microsoft Entra ID P1,AAD_PREMIUM,078d2b04-f1bd-4111-bbd4-b4b1b354cef4,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 @@ -418,6 +419,13 @@ Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-96 Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,DYN365_CDS_VIRAL,17ab22cd-a0b3-4536-910a-cb6eb12696c0,Common Data Service Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_APPS_DYN365_VIRAL_TRIAL_MIXED_REALITY,066e2fd1-ba15-40e7-aa96-d6636b1cdf71,Power Apps for Dynamics 365 Mixed Reality Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_AUTOMATE_DYN365_VIRAL_TRIAL_MIXED_REALITY,26fa8a18-2812-4b3d-96b4-864818ce26be,Power Automate for Dynamics 365 Mixed Reality +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Forms_Pro_Talent,1c4ae475-5608-43fa-b3f7-d20e07cf24b4,Microsoft Dynamics 365 Customer Voice for Talent +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_SELF_SERVICE_OPS,835b837b-63c1-410e-bf6b-bdef201ad129,Dynamics 365 Human Resource Self Service +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_OPS,8b21a5dc-5485-49ed-a2d4-0e772c830f6d,Dynamics 365 Human Resources +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_Attach,3219525a-4064-45ec-9c35-a33ea6b39a49,Dynamics 365 Human Resources Attach +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_ATTACH_OPS,90d8cb62-e98a-4639-8342-8c7d2c8215ba,Dynamics 365 Human Resources Attach License +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,CRM_HYBRIDCONNECTOR,0210d5c8-49d2-4dd1-a01b-a91c7c14e0bf,CRM Hybrid Connector Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 for Marketing Additional Application,DYN365_MARKETING_APPLICATION_ADDON,99c5688b-6c75-4496-876f-07f0fbd69add,DYN365_MARKETING_APPLICATION_ADDON,51cf0638-4861-40c0-8b20-1161ab2f80be,Dynamics 365 for Marketing Additional Application @@ -967,111 +975,129 @@ Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSE Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,Microsoft Communications Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner @@ -1091,8 +1117,10 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,RETIRED - Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -1105,18 +1133,20 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3633,13 +3663,19 @@ Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,M Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,NBPROFESSIONALFORCRM,3e58e97c-9abe-ebab-cd5f-d543d1529634,MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,POWERAPPS_DYN_APPS,874fc546-6efe-4d22-90b8-5c4e7aa59f4b,POWERAPPS FOR DYNAMICS 365 Microsoft Entra ID Governance,Microsoft_Entra_ID_Governance,cf6b0d46-4093-4546-a0ab-0b1546dcc10e,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Internet_Access,8d23cb83-ab07-418f-8517-d7aca77307dc,Microsoft Entra Internet Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Private_Access,f057aab1-b184-49b2-85c0-881b02a405c5,Microsoft Entra Private Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Verifiable_Credentials_Service_Request,aae826b7-14cd-4691-8178-2b312f7072ea,Verifiable Credentials Service Request +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,Power BI (free) +Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Imagine Academy,IT_ACADEMY_AD,ba9a34de-4489-469d-879c-0f0f145321cd,IT_ACADEMY_AD,d736def0-1fde-43f0-a5be-e3f8b2de6e41,MS IMAGINE ACADEMY +Microsoft Intune Advanced Analytics,Microsoft_Intune_Advanced_Analytics,5e36d0d4-e9e5-4052-aba0-0257465c9b86,Intune_AdvancedEA,2a4baa0e-5e99-4c38-b1f2-6864960f1bd1,Microsoft Intune Advanced Analytics Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Microsoft Intune Device for Government,INTUNE_A_D_GOV,2c21e77a-e0d6-4570-b38a-7ff2dc17d2ca,EXCHANGE_S_FOUNDATION_GOV,922ba911-5694-4e99-a794-73aed9bfeec8,Exchange Foundation for Government @@ -3696,6 +3732,10 @@ Microsoft Stream Storage Add-On (500 GB),STREAM_STORAGE,9bd7c846-9556-4453-a542- Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,CDS_CLOUD_FOR_SUSTAINABILITY_PLUS,ba80223c-e515-4642-a838-3e7d66f70cb6,Common Data Services for Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_PLUS,ed29ae92-ff5f-4446-8460-83c54d0e7088,MCS - BizApps Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_USL_PLUS,beaf5b5c-d11c-4417-b5cb-cd9f9e6719b0,MCS - BizApps Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_APPS_FOR_MCS_USL_PLUS,c5502fe7-406d-442a-827f-4948b821ba08,Power Apps for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_AUTOMATE_FOR_MCS_USL_PLUS,1c22bb50-96fb-49e5-baa6-195cab19eee2,Power Automate for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,MCS_BizApps_Cloud_for_Sustainability_USL,c46c42af-d654-4385-8c85-29a84f3dfb22,MCS - BizApps - Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_APPS_FOR_MCS_USL,5ffd371c-037a-41a2-98a3-6452f8c5de17,Power Apps for Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_AUTOMATE_FOR_MCS_USL,ccbe468e-7973-442c-8ec4-5fbe16438711,Power Automate for Cloud for Sustainability USL @@ -3808,18 +3848,20 @@ Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003 Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MTRProManagement,ecc74eae-eeb7-4ad5-9c88-e8b2bfca75b8,Microsoft Teams Rooms Pro Management Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Rooms_Pro,0374d34c-6be4-4dbb-b3f0-26105db0b28a,Teams Rooms Pro Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Basic,8081ca9c-188c-4b49-a8e5-c23b5e9463a8,Teams Rooms Test 1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Pro,ec17f317-f4bc-451e-b2da-0167e5c260f9,Teams Rooms Test 2 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,SPECIALTY_DEVICES,cfce7ae3-4b41-4438-999c-c0e91f3b7fb9,Specialty devices Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOMEETADV_GOV,f544b08d-1645-4287-82de-8d91f37c02a1,Microsoft 365 Audio Conferencing for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOEV_GOV,db23fce2-a974-42ef-9002-d78dd42a0f22,Microsoft 365 Phone System for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,TEAMS_GOV,304767db-7d23-49e8-a945-4a7eb65f9f28,Microsoft Teams for Government @@ -3914,14 +3956,14 @@ Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-305 Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-30551b2ad607,TEAMSMULTIGEO,41eda15d-6b52-453b-906f-bc4a5b25a26b,Teams Multi-Geo Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,NONPROFIT_PORTAL,7dbc2d88-20e2-4eb6-b065-4510b38d6eb2,Nonprofit Portal -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3931,9 +3973,8 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3942,6 +3983,9 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3969,14 +4013,14 @@ Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-89 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3985,9 +4029,8 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3995,6 +4038,9 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,BPOS_S_TODO_2,c87f142c-d1e9-4363-8630-aaea9c4d9ae5,To-Do (Plan 2) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -5059,8 +5105,11 @@ Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-5 Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,CDS_ POWERAPPS_PER_USER_CUSTOM,2e8dde43-6986-479d-b179-7dbe31c31f60,CDS Power Apps Per User Custom Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan -Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service - P2 +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER_NEW,74d93933-6f22-436e-9441-66d205435abb,AI Builder capacity Per User add-on +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,DO NOT USE - AI Builder capacity Per User add-on Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan Power Apps Premium for Government,POWERAPPS_PER_USER_GCC,8e4c6baa-f2ff-4884-9c38-93785d0d7ba1,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,AI Builder capacity Per User add-on @@ -5130,6 +5179,8 @@ Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1 Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Power BI Premium Per User Add-On,PBI_PREMIUM_PER_USER_ADDON,de376a03-6e5b-42ec-855f-093fb50b8ca5,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_CE_GCC,66024bbf-4cd4-4329-95c8-c932e2ae01a8,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_GCC,1b572d5e-1bf8-4b19-9259-f9eda31a6972,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Dept,PBI_PREMIUM_PER_USER_DEPT,f168a3fb-7bcf-4a27-98c3-c235ea4b78b4,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation @@ -5426,6 +5477,14 @@ Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-49 Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MICROSOFT_ECDN,85704d55-2e73-47ee-93b4-4b8ea14db92b,Microsoft eCDN +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_MGMT,0504111f-feb8-4a3c-992a-70280f9a2869,Microsoft Teams Premium Intelligent +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_CUST,cc8c0802-a325-43df-8cba-995d0c6cb373,Microsoft Teams Premium Personalized +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_PROTECTION,f8b44f54-18bb-46a3-9658-44ab58712968,Microsoft Teams Premium Secure +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,QUEUES_APP,ab2d4fb5-f80a-4bf1-a11d-7f1da254041b,Queues app for Microsoft Teams Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MMR_P1,bdaa59a3-74fd-4137-981a-31d4f84eb8a0,Meeting Room Managed Services Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System diff --git a/Modules/CIPPCore/Public/ConversionTable.csv b/Modules/CIPPCore/Public/ConversionTable.csv index 704f7c507d5f..5b7cea1f09ea 100644 --- a/Modules/CIPPCore/Public/ConversionTable.csv +++ b/Modules/CIPPCore/Public/ConversionTable.csv @@ -20,6 +20,7 @@ Clipchamp Premium,Clipchamp_Premium,0fe440c5-f2bf-442b-a4f4-9a7af77a200b,ONEDRIV Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,ONEDRIVECLIPCHAMP,f7e5b77d-f293-410a-bae8-f941f19fe680,OneDrive for Business (Clipchamp) +Clipchamp Premium Add-on,Clipchamp_Premium_Add_on,4b2c20e4-939d-4bf4-9dd8-6870240cfe19,CLIPCHAMP_PREMIUM,430b908f-78e1-4812-b045-cf83320e7d5d,Microsoft Clipchamp Premium Microsoft 365 Audio Conferencing,MCOMEETADV,0c266dff-15dd-4b49-8397-2bb16070ed52,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Entra ID Basic,AAD_BASIC,2b9c8e7c-319c-43a2-a2a0-48c5c6161de7,AAD_BASIC,c4da7f8a-5ee2-4c99-a7e1-87d2df57f6fe,Microsoft Entra BASIC Microsoft Entra ID P1,AAD_PREMIUM,078d2b04-f1bd-4111-bbd4-b4b1b354cef4,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 @@ -418,6 +419,13 @@ Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-96 Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,DYN365_CDS_VIRAL,17ab22cd-a0b3-4536-910a-cb6eb12696c0,Common Data Service Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_APPS_DYN365_VIRAL_TRIAL_MIXED_REALITY,066e2fd1-ba15-40e7-aa96-d6636b1cdf71,Power Apps for Dynamics 365 Mixed Reality Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_AUTOMATE_DYN365_VIRAL_TRIAL_MIXED_REALITY,26fa8a18-2812-4b3d-96b4-864818ce26be,Power Automate for Dynamics 365 Mixed Reality +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Forms_Pro_Talent,1c4ae475-5608-43fa-b3f7-d20e07cf24b4,Microsoft Dynamics 365 Customer Voice for Talent +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_SELF_SERVICE_OPS,835b837b-63c1-410e-bf6b-bdef201ad129,Dynamics 365 Human Resource Self Service +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_OPS,8b21a5dc-5485-49ed-a2d4-0e772c830f6d,Dynamics 365 Human Resources +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_Attach,3219525a-4064-45ec-9c35-a33ea6b39a49,Dynamics 365 Human Resources Attach +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_ATTACH_OPS,90d8cb62-e98a-4639-8342-8c7d2c8215ba,Dynamics 365 Human Resources Attach License +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,CRM_HYBRIDCONNECTOR,0210d5c8-49d2-4dd1-a01b-a91c7c14e0bf,CRM Hybrid Connector Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 for Marketing Additional Application,DYN365_MARKETING_APPLICATION_ADDON,99c5688b-6c75-4496-876f-07f0fbd69add,DYN365_MARKETING_APPLICATION_ADDON,51cf0638-4861-40c0-8b20-1161ab2f80be,Dynamics 365 for Marketing Additional Application @@ -967,111 +975,129 @@ Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSE Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,Microsoft Communications Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner @@ -1091,8 +1117,10 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,RETIRED - Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -1105,18 +1133,20 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3633,13 +3663,19 @@ Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,M Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,NBPROFESSIONALFORCRM,3e58e97c-9abe-ebab-cd5f-d543d1529634,MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,POWERAPPS_DYN_APPS,874fc546-6efe-4d22-90b8-5c4e7aa59f4b,POWERAPPS FOR DYNAMICS 365 Microsoft Entra ID Governance,Microsoft_Entra_ID_Governance,cf6b0d46-4093-4546-a0ab-0b1546dcc10e,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Internet_Access,8d23cb83-ab07-418f-8517-d7aca77307dc,Microsoft Entra Internet Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Private_Access,f057aab1-b184-49b2-85c0-881b02a405c5,Microsoft Entra Private Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Verifiable_Credentials_Service_Request,aae826b7-14cd-4691-8178-2b312f7072ea,Verifiable Credentials Service Request +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,Power BI (free) +Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Imagine Academy,IT_ACADEMY_AD,ba9a34de-4489-469d-879c-0f0f145321cd,IT_ACADEMY_AD,d736def0-1fde-43f0-a5be-e3f8b2de6e41,MS IMAGINE ACADEMY +Microsoft Intune Advanced Analytics,Microsoft_Intune_Advanced_Analytics,5e36d0d4-e9e5-4052-aba0-0257465c9b86,Intune_AdvancedEA,2a4baa0e-5e99-4c38-b1f2-6864960f1bd1,Microsoft Intune Advanced Analytics Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Microsoft Intune Device for Government,INTUNE_A_D_GOV,2c21e77a-e0d6-4570-b38a-7ff2dc17d2ca,EXCHANGE_S_FOUNDATION_GOV,922ba911-5694-4e99-a794-73aed9bfeec8,Exchange Foundation for Government @@ -3696,6 +3732,10 @@ Microsoft Stream Storage Add-On (500 GB),STREAM_STORAGE,9bd7c846-9556-4453-a542- Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,CDS_CLOUD_FOR_SUSTAINABILITY_PLUS,ba80223c-e515-4642-a838-3e7d66f70cb6,Common Data Services for Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_PLUS,ed29ae92-ff5f-4446-8460-83c54d0e7088,MCS - BizApps Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_USL_PLUS,beaf5b5c-d11c-4417-b5cb-cd9f9e6719b0,MCS - BizApps Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_APPS_FOR_MCS_USL_PLUS,c5502fe7-406d-442a-827f-4948b821ba08,Power Apps for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_AUTOMATE_FOR_MCS_USL_PLUS,1c22bb50-96fb-49e5-baa6-195cab19eee2,Power Automate for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,MCS_BizApps_Cloud_for_Sustainability_USL,c46c42af-d654-4385-8c85-29a84f3dfb22,MCS - BizApps - Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_APPS_FOR_MCS_USL,5ffd371c-037a-41a2-98a3-6452f8c5de17,Power Apps for Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_AUTOMATE_FOR_MCS_USL,ccbe468e-7973-442c-8ec4-5fbe16438711,Power Automate for Cloud for Sustainability USL @@ -3808,18 +3848,20 @@ Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003 Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MTRProManagement,ecc74eae-eeb7-4ad5-9c88-e8b2bfca75b8,Microsoft Teams Rooms Pro Management Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Rooms_Pro,0374d34c-6be4-4dbb-b3f0-26105db0b28a,Teams Rooms Pro Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Basic,8081ca9c-188c-4b49-a8e5-c23b5e9463a8,Teams Rooms Test 1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Pro,ec17f317-f4bc-451e-b2da-0167e5c260f9,Teams Rooms Test 2 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,SPECIALTY_DEVICES,cfce7ae3-4b41-4438-999c-c0e91f3b7fb9,Specialty devices Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOMEETADV_GOV,f544b08d-1645-4287-82de-8d91f37c02a1,Microsoft 365 Audio Conferencing for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOEV_GOV,db23fce2-a974-42ef-9002-d78dd42a0f22,Microsoft 365 Phone System for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,TEAMS_GOV,304767db-7d23-49e8-a945-4a7eb65f9f28,Microsoft Teams for Government @@ -3914,14 +3956,14 @@ Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-305 Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-30551b2ad607,TEAMSMULTIGEO,41eda15d-6b52-453b-906f-bc4a5b25a26b,Teams Multi-Geo Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,NONPROFIT_PORTAL,7dbc2d88-20e2-4eb6-b065-4510b38d6eb2,Nonprofit Portal -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3931,9 +3973,8 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3942,6 +3983,9 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3969,14 +4013,14 @@ Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-89 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3985,9 +4029,8 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3995,6 +4038,9 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,BPOS_S_TODO_2,c87f142c-d1e9-4363-8630-aaea9c4d9ae5,To-Do (Plan 2) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -5059,8 +5105,11 @@ Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-5 Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,CDS_ POWERAPPS_PER_USER_CUSTOM,2e8dde43-6986-479d-b179-7dbe31c31f60,CDS Power Apps Per User Custom Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan -Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service - P2 +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER_NEW,74d93933-6f22-436e-9441-66d205435abb,AI Builder capacity Per User add-on +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,DO NOT USE - AI Builder capacity Per User add-on Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan Power Apps Premium for Government,POWERAPPS_PER_USER_GCC,8e4c6baa-f2ff-4884-9c38-93785d0d7ba1,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,AI Builder capacity Per User add-on @@ -5130,6 +5179,8 @@ Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1 Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Power BI Premium Per User Add-On,PBI_PREMIUM_PER_USER_ADDON,de376a03-6e5b-42ec-855f-093fb50b8ca5,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_CE_GCC,66024bbf-4cd4-4329-95c8-c932e2ae01a8,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_GCC,1b572d5e-1bf8-4b19-9259-f9eda31a6972,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Dept,PBI_PREMIUM_PER_USER_DEPT,f168a3fb-7bcf-4a27-98c3-c235ea4b78b4,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation @@ -5426,6 +5477,14 @@ Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-49 Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MICROSOFT_ECDN,85704d55-2e73-47ee-93b4-4b8ea14db92b,Microsoft eCDN +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_MGMT,0504111f-feb8-4a3c-992a-70280f9a2869,Microsoft Teams Premium Intelligent +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_CUST,cc8c0802-a325-43df-8cba-995d0c6cb373,Microsoft Teams Premium Personalized +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_PROTECTION,f8b44f54-18bb-46a3-9658-44ab58712968,Microsoft Teams Premium Secure +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,QUEUES_APP,ab2d4fb5-f80a-4bf1-a11d-7f1da254041b,Queues app for Microsoft Teams Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MMR_P1,bdaa59a3-74fd-4137-981a-31d4f84eb8a0,Meeting Room Managed Services Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System diff --git a/Modules/CippExtensions/ConversionTable.csv b/Modules/CippExtensions/ConversionTable.csv index 704f7c507d5f..5b7cea1f09ea 100644 --- a/Modules/CippExtensions/ConversionTable.csv +++ b/Modules/CippExtensions/ConversionTable.csv @@ -20,6 +20,7 @@ Clipchamp Premium,Clipchamp_Premium,0fe440c5-f2bf-442b-a4f4-9a7af77a200b,ONEDRIV Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,ONEDRIVECLIPCHAMP,f7e5b77d-f293-410a-bae8-f941f19fe680,OneDrive for Business (Clipchamp) +Clipchamp Premium Add-on,Clipchamp_Premium_Add_on,4b2c20e4-939d-4bf4-9dd8-6870240cfe19,CLIPCHAMP_PREMIUM,430b908f-78e1-4812-b045-cf83320e7d5d,Microsoft Clipchamp Premium Microsoft 365 Audio Conferencing,MCOMEETADV,0c266dff-15dd-4b49-8397-2bb16070ed52,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Entra ID Basic,AAD_BASIC,2b9c8e7c-319c-43a2-a2a0-48c5c6161de7,AAD_BASIC,c4da7f8a-5ee2-4c99-a7e1-87d2df57f6fe,Microsoft Entra BASIC Microsoft Entra ID P1,AAD_PREMIUM,078d2b04-f1bd-4111-bbd4-b4b1b354cef4,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 @@ -418,6 +419,13 @@ Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-96 Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,DYN365_CDS_VIRAL,17ab22cd-a0b3-4536-910a-cb6eb12696c0,Common Data Service Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_APPS_DYN365_VIRAL_TRIAL_MIXED_REALITY,066e2fd1-ba15-40e7-aa96-d6636b1cdf71,Power Apps for Dynamics 365 Mixed Reality Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_AUTOMATE_DYN365_VIRAL_TRIAL_MIXED_REALITY,26fa8a18-2812-4b3d-96b4-864818ce26be,Power Automate for Dynamics 365 Mixed Reality +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Forms_Pro_Talent,1c4ae475-5608-43fa-b3f7-d20e07cf24b4,Microsoft Dynamics 365 Customer Voice for Talent +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_SELF_SERVICE_OPS,835b837b-63c1-410e-bf6b-bdef201ad129,Dynamics 365 Human Resource Self Service +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_OPS,8b21a5dc-5485-49ed-a2d4-0e772c830f6d,Dynamics 365 Human Resources +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_Attach,3219525a-4064-45ec-9c35-a33ea6b39a49,Dynamics 365 Human Resources Attach +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_ATTACH_OPS,90d8cb62-e98a-4639-8342-8c7d2c8215ba,Dynamics 365 Human Resources Attach License +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,CRM_HYBRIDCONNECTOR,0210d5c8-49d2-4dd1-a01b-a91c7c14e0bf,CRM Hybrid Connector Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 for Marketing Additional Application,DYN365_MARKETING_APPLICATION_ADDON,99c5688b-6c75-4496-876f-07f0fbd69add,DYN365_MARKETING_APPLICATION_ADDON,51cf0638-4861-40c0-8b20-1161ab2f80be,Dynamics 365 for Marketing Additional Application @@ -967,111 +975,129 @@ Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSE Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,Microsoft Communications Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner @@ -1091,8 +1117,10 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,RETIRED - Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -1105,18 +1133,20 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3633,13 +3663,19 @@ Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,M Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,NBPROFESSIONALFORCRM,3e58e97c-9abe-ebab-cd5f-d543d1529634,MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,POWERAPPS_DYN_APPS,874fc546-6efe-4d22-90b8-5c4e7aa59f4b,POWERAPPS FOR DYNAMICS 365 Microsoft Entra ID Governance,Microsoft_Entra_ID_Governance,cf6b0d46-4093-4546-a0ab-0b1546dcc10e,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Internet_Access,8d23cb83-ab07-418f-8517-d7aca77307dc,Microsoft Entra Internet Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Private_Access,f057aab1-b184-49b2-85c0-881b02a405c5,Microsoft Entra Private Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Verifiable_Credentials_Service_Request,aae826b7-14cd-4691-8178-2b312f7072ea,Verifiable Credentials Service Request +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,Power BI (free) +Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Imagine Academy,IT_ACADEMY_AD,ba9a34de-4489-469d-879c-0f0f145321cd,IT_ACADEMY_AD,d736def0-1fde-43f0-a5be-e3f8b2de6e41,MS IMAGINE ACADEMY +Microsoft Intune Advanced Analytics,Microsoft_Intune_Advanced_Analytics,5e36d0d4-e9e5-4052-aba0-0257465c9b86,Intune_AdvancedEA,2a4baa0e-5e99-4c38-b1f2-6864960f1bd1,Microsoft Intune Advanced Analytics Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Microsoft Intune Device for Government,INTUNE_A_D_GOV,2c21e77a-e0d6-4570-b38a-7ff2dc17d2ca,EXCHANGE_S_FOUNDATION_GOV,922ba911-5694-4e99-a794-73aed9bfeec8,Exchange Foundation for Government @@ -3696,6 +3732,10 @@ Microsoft Stream Storage Add-On (500 GB),STREAM_STORAGE,9bd7c846-9556-4453-a542- Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,CDS_CLOUD_FOR_SUSTAINABILITY_PLUS,ba80223c-e515-4642-a838-3e7d66f70cb6,Common Data Services for Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_PLUS,ed29ae92-ff5f-4446-8460-83c54d0e7088,MCS - BizApps Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_USL_PLUS,beaf5b5c-d11c-4417-b5cb-cd9f9e6719b0,MCS - BizApps Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_APPS_FOR_MCS_USL_PLUS,c5502fe7-406d-442a-827f-4948b821ba08,Power Apps for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_AUTOMATE_FOR_MCS_USL_PLUS,1c22bb50-96fb-49e5-baa6-195cab19eee2,Power Automate for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,MCS_BizApps_Cloud_for_Sustainability_USL,c46c42af-d654-4385-8c85-29a84f3dfb22,MCS - BizApps - Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_APPS_FOR_MCS_USL,5ffd371c-037a-41a2-98a3-6452f8c5de17,Power Apps for Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_AUTOMATE_FOR_MCS_USL,ccbe468e-7973-442c-8ec4-5fbe16438711,Power Automate for Cloud for Sustainability USL @@ -3808,18 +3848,20 @@ Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003 Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MTRProManagement,ecc74eae-eeb7-4ad5-9c88-e8b2bfca75b8,Microsoft Teams Rooms Pro Management Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Rooms_Pro,0374d34c-6be4-4dbb-b3f0-26105db0b28a,Teams Rooms Pro Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Basic,8081ca9c-188c-4b49-a8e5-c23b5e9463a8,Teams Rooms Test 1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Pro,ec17f317-f4bc-451e-b2da-0167e5c260f9,Teams Rooms Test 2 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,SPECIALTY_DEVICES,cfce7ae3-4b41-4438-999c-c0e91f3b7fb9,Specialty devices Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOMEETADV_GOV,f544b08d-1645-4287-82de-8d91f37c02a1,Microsoft 365 Audio Conferencing for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOEV_GOV,db23fce2-a974-42ef-9002-d78dd42a0f22,Microsoft 365 Phone System for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,TEAMS_GOV,304767db-7d23-49e8-a945-4a7eb65f9f28,Microsoft Teams for Government @@ -3914,14 +3956,14 @@ Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-305 Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-30551b2ad607,TEAMSMULTIGEO,41eda15d-6b52-453b-906f-bc4a5b25a26b,Teams Multi-Geo Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,NONPROFIT_PORTAL,7dbc2d88-20e2-4eb6-b065-4510b38d6eb2,Nonprofit Portal -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3931,9 +3973,8 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3942,6 +3983,9 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3969,14 +4013,14 @@ Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-89 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3985,9 +4029,8 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3995,6 +4038,9 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,BPOS_S_TODO_2,c87f142c-d1e9-4363-8630-aaea9c4d9ae5,To-Do (Plan 2) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -5059,8 +5105,11 @@ Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-5 Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,CDS_ POWERAPPS_PER_USER_CUSTOM,2e8dde43-6986-479d-b179-7dbe31c31f60,CDS Power Apps Per User Custom Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan -Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service - P2 +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER_NEW,74d93933-6f22-436e-9441-66d205435abb,AI Builder capacity Per User add-on +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,DO NOT USE - AI Builder capacity Per User add-on Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan Power Apps Premium for Government,POWERAPPS_PER_USER_GCC,8e4c6baa-f2ff-4884-9c38-93785d0d7ba1,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,AI Builder capacity Per User add-on @@ -5130,6 +5179,8 @@ Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1 Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Power BI Premium Per User Add-On,PBI_PREMIUM_PER_USER_ADDON,de376a03-6e5b-42ec-855f-093fb50b8ca5,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_CE_GCC,66024bbf-4cd4-4329-95c8-c932e2ae01a8,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_GCC,1b572d5e-1bf8-4b19-9259-f9eda31a6972,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Dept,PBI_PREMIUM_PER_USER_DEPT,f168a3fb-7bcf-4a27-98c3-c235ea4b78b4,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation @@ -5426,6 +5477,14 @@ Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-49 Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MICROSOFT_ECDN,85704d55-2e73-47ee-93b4-4b8ea14db92b,Microsoft eCDN +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_MGMT,0504111f-feb8-4a3c-992a-70280f9a2869,Microsoft Teams Premium Intelligent +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_CUST,cc8c0802-a325-43df-8cba-995d0c6cb373,Microsoft Teams Premium Personalized +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_PROTECTION,f8b44f54-18bb-46a3-9658-44ab58712968,Microsoft Teams Premium Secure +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,QUEUES_APP,ab2d4fb5-f80a-4bf1-a11d-7f1da254041b,Queues app for Microsoft Teams Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MMR_P1,bdaa59a3-74fd-4137-981a-31d4f84eb8a0,Meeting Room Managed Services Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System diff --git a/Modules/CippExtensions/Public/ConversionTable.csv b/Modules/CippExtensions/Public/ConversionTable.csv index 704f7c507d5f..5b7cea1f09ea 100644 --- a/Modules/CippExtensions/Public/ConversionTable.csv +++ b/Modules/CippExtensions/Public/ConversionTable.csv @@ -20,6 +20,7 @@ Clipchamp Premium,Clipchamp_Premium,0fe440c5-f2bf-442b-a4f4-9a7af77a200b,ONEDRIV Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Clipchamp Standard,Clipchamp_Standard,481f3bc2-5756-4b28-9375-5c8c86b99e6b,ONEDRIVECLIPCHAMP,f7e5b77d-f293-410a-bae8-f941f19fe680,OneDrive for Business (Clipchamp) +Clipchamp Premium Add-on,Clipchamp_Premium_Add_on,4b2c20e4-939d-4bf4-9dd8-6870240cfe19,CLIPCHAMP_PREMIUM,430b908f-78e1-4812-b045-cf83320e7d5d,Microsoft Clipchamp Premium Microsoft 365 Audio Conferencing,MCOMEETADV,0c266dff-15dd-4b49-8397-2bb16070ed52,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Entra ID Basic,AAD_BASIC,2b9c8e7c-319c-43a2-a2a0-48c5c6161de7,AAD_BASIC,c4da7f8a-5ee2-4c99-a7e1-87d2df57f6fe,Microsoft Entra BASIC Microsoft Entra ID P1,AAD_PREMIUM,078d2b04-f1bd-4111-bbd4-b4b1b354cef4,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 @@ -418,6 +419,13 @@ Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-96 Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,DYN365_CDS_VIRAL,17ab22cd-a0b3-4536-910a-cb6eb12696c0,Common Data Service Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_APPS_DYN365_VIRAL_TRIAL_MIXED_REALITY,066e2fd1-ba15-40e7-aa96-d6636b1cdf71,Power Apps for Dynamics 365 Mixed Reality Dynamics 365 Guides vTrial,Dynamics_365_Guides_vTrial,99cb3f83-fbec-4aa1-8262-9679e6df7c53,POWER_AUTOMATE_DYN365_VIRAL_TRIAL_MIXED_REALITY,26fa8a18-2812-4b3d-96b4-864818ce26be,Power Automate for Dynamics 365 Mixed Reality +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Forms_Pro_Talent,1c4ae475-5608-43fa-b3f7-d20e07cf24b4,Microsoft Dynamics 365 Customer Voice for Talent +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_SELF_SERVICE_OPS,835b837b-63c1-410e-bf6b-bdef201ad129,Dynamics 365 Human Resource Self Service +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_OPS,8b21a5dc-5485-49ed-a2d4-0e772c830f6d,Dynamics 365 Human Resources +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_Attach,3219525a-4064-45ec-9c35-a33ea6b39a49,Dynamics 365 Human Resources Attach +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,D365_HR_ATTACH_OPS,90d8cb62-e98a-4639-8342-8c7d2c8215ba,Dynamics 365 Human Resources Attach License +Dynamics 365 Human Resources Attach to Qualifying Dynamics 365 Base Offer,DYN365_HUMAN_RESOURCES_ATTACH,83c489a4-94b6-4dcc-9fdc-ff9b107a4621,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,CRM_HYBRIDCONNECTOR,0210d5c8-49d2-4dd1-a01b-a91c7c14e0bf,CRM Hybrid Connector Dynamics 365 Hybrid Connector,CRM_HYBRIDCONNECTOR,de176c31-616d-4eae-829a-718918d7ec23,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Dynamics 365 for Marketing Additional Application,DYN365_MARKETING_APPLICATION_ADDON,99c5688b-6c75-4496-876f-07f0fbd69add,DYN365_MARKETING_APPLICATION_ADDON,51cf0638-4861-40c0-8b20-1161ab2f80be,Dynamics 365 for Marketing Additional Application @@ -967,111 +975,129 @@ Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSE Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 Microsoft 365 A3 - Unattended License for students use benefit,M365EDU_A3_STUUSEBNFT_RPA1,1aa94593-ca12-4254-a738-81a5972958e8,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,Microsoft Communications Compliance -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 -Microsoft 365 A5 for Faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RECORDS_MANAGEMENT,65cc641f-cccd-4643-97e0-a17e3045e541,Microsoft Records Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,STREAM_O365_E5,6c6042f5-6f01-4d67-b8c1-eb99d36eed3e,Microsoft Stream for Office 365 E5 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MINECRAFT_EDUCATION_EDITION,4c246bbc-f513-4311-beff-eba54c353256,Minecraft Education Edition +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,EQUIVIO_ANALYTICS,4de31727-a228-4ec3-a5bf-8e45b5ca48cc,Office 365 Advanced eDiscovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_O365,8c098270-9dd4-4350-9b30-ba4703f3b36b,Office 365 Cloud App Security +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PAM_ENTERPRISE,b1188c4c-1b36-4018-b48b-ee07604f6feb,Office 365 Privileged Access Management +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SAFEDOCS,bf6f5520-59e3-4f82-974b-7dbbc4fd27c7,Office 365 SafeDocs +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWERAPPS_O365_P3,9c0dab89-a30c-4117-86e7-97bda240acd2,Power Apps for Office 365 (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,SWAY,a23b959c-7ce8-4e57-9140-b90eb88a9e97,Sway +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,BPOS_S_TODO_3,3fb82609-8c27-4f7b-bd51-30634711ee67,To-Do (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MICROSOFTENDPOINTDLP,64bfac92-2b17-4482-b5e5-a0304429de3e,Microsoft Endpoint DLP +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for faculty,M365EDU_A5_FACULTY,e97c048c-37a4-45fb-ab50-922fbf07a370,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CDS_O365_P3,afa73018-811e-46e9-988f-f75d2b1b8430,Common Data Service for Teams Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,LOCKBOX_ENTERPRISE,9f431833-0334-42de-a7dc-70aa40db46db,Customer Lockbox +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CustomerLockboxA_Enterprise,3ec18638-bd4c-4d3b-8905-479ed636b83e,Customer Lockbox (A) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_Exchange,cd31b152-6326-4d1b-ae1b-997b625182e6,Data Classification in Microsoft 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMON_DEFENDER_PLATFORM_FOR_OFFICE,a312bdeb-1e21-40d0-84b1-0e73f128144f,Defender Platform for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_S_ENTERPRISE,efb87545-963c-4e0d-99df-69c6916d9eb0,Exchange Online (Plan 2) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,GRAPH_CONNECTORS_SEARCH_INDEX,a6520331-d7d4-4276-95f5-15c0933bc757,Graph Connectors Search with Index Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Content_Explorer,d9fa6af4-e046-4c89-9226-729a0786685d,Information Protection and Governance Analytics - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ContentExplorer_Standard,2b815d45-56e4-4e3a-b65c-66cb9175b560,Information Protection and Governance Analytics – Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP2,efb0351d-3b08-4503-993d-383af8de41e3,Information Protection for Office 365 - Premium Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MIP_S_CLP1,5136a095-5cf0-4aff-bec3-e84448b38ea5,Information Protection for Office 365 - Standard Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_ADVANCED_AUDITING,2f442157-a11c-46b9-ae5b-6e39ff4e5849,Microsoft 365 Advanced Auditing -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for Enterprise +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICESUBSCRIPTION,43de0ff5-c92c-492b-9116-175376d08c38,Microsoft 365 Apps for enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_AUDIT_PLATFORM,f6de4823-28fa-440b-b886-4783fa86ddba,Microsoft 365 Audit Platform Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_COMMUNICATION_COMPLIANCE,a413a9ff-720c-4822-98ef-2f37c2a21f4c,Microsoft 365 Communication Compliance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MTP,bf28f719-7844-4079-9c78-c1307898e192,Microsoft 365 Defender +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFTBOOKINGS,199a5c09-e0ca-4e37-8f7c-b05d533e1ea2,Microsoft Bookings +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CLIPCHAMP,a1ace008-72f3-4ea0-8dac-33b3a23a2472,Microsoft Clipchamp Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_DLP,6dc145d6-95dd-4191-b9c3-185575ee6f6b,Microsoft Communications DLP Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,CUSTOMER_KEY,6db1f1db-2b46-403f-be40-e39395f08dbb,Microsoft Customer Key -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATP_ENTERPRISE,f20fedf3-f3c3-43c3-8267-2bfdd51c0939,Microsoft Defender for Office 365 (Plan 1) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,THREAT_INTELLIGENCE,8e0c0a52-6a6c-4d40-8370-dd62790dcd70,Microsoft Defender for Office 365 (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCEL_PREMIUM,531ee2f8-b1cb-453b-9c21-d2180d014ca5,Microsoft Excel Advanced Analytics Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,OFFICE_FORMS_PLAN_3,96c1e14a-ef43-418d-b115-9636cdaa8eed,Microsoft Forms (Plan 3) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INFO_GOVERNANCE,e26c2fcc-ab91-4a61-b35c-03cdc8dddf66,Microsoft Information Governance Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK,d587c7a3-bda9-4f99-8776-9bcf59c84f75,Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,Microsoft Insider Risk Management - Exchange Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,KAIZALA_STANDALONE,0898bdbb-73b0-471a-81e5-20f1fe4dd66e,Microsoft Kaizala Pro +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MICROSOFT_LOOP,c4b8c31a-fb44-4c65-9837-a21f55fcabda,Microsoft Loop Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ML_CLASSIFICATION,d2d51368-76c9-4317-ada2-a12c004c432f,Microsoft ML-Based Classification Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,EXCHANGE_ANALYTICS,34c0d7a0-a70f-4668-9238-47f9fc208882,Microsoft MyAnalytics (Full) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner @@ -1091,8 +1117,10 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PREMIUM_ENCRYPTION,617b097b-4b93-4ede-83de-5f075bb5fb2f,Premium Encryption in Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PROJECT_O365_P3,b21a6b06-1988-436e-a07b-51ec6d9f52ad,Project for Office (Plan E5) +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,COMMUNICATIONS_COMPLIANCE,41fcdd7d-4733-4863-9cf4-c65b83ce2df4,RETIRED - Microsoft Communications Compliance -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INSIDER_RISK_MANAGEMENT,9d0c4ee5-e4a1-4625-ab39-d82b619b1a34,RETIRED - Microsoft Insider Risk Management +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DATA_INVESTIGATIONS,46129a58-a698-46f0-aa5b-17f6586297d9,Retired - Microsoft Data Investigations Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SCHOOL_DATA_SYNC_P2,500b6a2a-7a50-4f40-b5f9-160e5b8c2f48,School Data Sync (Plan 2) Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,SHAREPOINTENTERPRISE_EDU,63038b2c-28d0-45f6-bc36-33062963b498,SharePoint (Plan 2) for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -1105,18 +1133,20 @@ Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,UNIVERSAL_PRINT_01,795f6fe0-cc4d-4773-b050-5dde4dc704c9,Universal Print Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Virtualization Rights for Windows 10 (E3/E5+VDA),e7c91390-7625-45be-94e0-e16907e03118,Windows 10/11 Enterprise Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE,7bf960f6-2cd9-443a-8046-5dbff9558365,Windows Update for Business Deployment Service -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM,6c57d4b6-3b23-47a5-9bc9-69f17b4947b3,Azure Information Protection Premium P1 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,RMS_S_PREMIUM2,5689bec4-755d-4753-8b61-40975025187c,Azure Information Protection Premium P2 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,DYN365_CDS_O365_P3,28b0fa46-c39a-4188-89e2-58e979a6b014,Common Data Service +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,Intune_ServiceNow,3eeb8536-fecf-41bf-a3f8-d6f17a9f3efc,Intune ServiceNow Integration Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,MFA_PREMIUM,8a256a2b-b617-496d-b51b-e76466e88db0,Microsoft Azure Multi-Factor Authentication Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ADALLOM_S_STANDALONE,2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2,Microsoft Defender for Cloud Apps Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,ATA,14ab5db5-e6c4-4b20-b4bc-13e36fd2227f,Microsoft Defender for Identity -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune -Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune for Education +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,AAD_PREMIUM_P2,eec0eb4f-6444-4f95-aba0-50c24d67f998,Microsoft Entra ID P2 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,INTUNE_EDU,da24caf9-af8e-485c-b7c8-e73336da2693,Microsoft Intune Plan 1 for Education Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,FLOW_O365_P3,07699545-9485-468e-95b6-2fca3738be01,Power Automate for Office 365 Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,POWER_VIRTUAL_AGENTS_O365_P3,ded3d325-1bdc-453e-8432-5bac26d7a014,Power Virtual Agents for Office 365 +Microsoft 365 A5 for Students,M365EDU_A5_STUDENT,46c119d4-0379-4a9d-85e4-97c66d3f909e,REMOTE_HELP,a4c6cf29-1168-4076-ba5c-e8fe0e62b17e,Remote help Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Microsoft 365 A5 for students use benefit,M365EDU_A5_STUUSEBNFT,31d57bc7-3a05-4867-ab53-97a17835a411,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3633,13 +3663,19 @@ Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,M Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,NBPROFESSIONALFORCRM,3e58e97c-9abe-ebab-cd5f-d543d1529634,MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY Microsoft Dynamics CRM Online,CRMSTANDARD,d17b27af-3f49-4822-99f9-56a661538792,POWERAPPS_DYN_APPS,874fc546-6efe-4d22-90b8-5c4e7aa59f4b,POWERAPPS FOR DYNAMICS 365 Microsoft Entra ID Governance,Microsoft_Entra_ID_Governance,cf6b0d46-4093-4546-a0ab-0b1546dcc10e,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Internet_Access,8d23cb83-ab07-418f-8517-d7aca77307dc,Microsoft Entra Internet Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Premium_Private_Access,f057aab1-b184-49b2-85c0-881b02a405c5,Microsoft Entra Private Access +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Verifiable_Credentials_Service_Request,aae826b7-14cd-4691-8178-2b312f7072ea,Verifiable Credentials Service Request +Microsoft Entra Suite Add-on for Microsoft Entra ID P2,Microsoft_Entra_Suite_Step_Up_for_Microsoft_Entra_ID_P2,2ef3064c-c95c-426c-96dd-9ffeaa2f2c37,Entra_Identity_Governance,e866a266-3cff-43a3-acca-0c90a7e00c8b,Entra Identity Governance Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,Power BI (free) +Microsoft Fabric (Free),POWER_BI_STANDARD,a403ebcc-fae0-4ca2-8c8c-7a907fd6c235,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for faculty,POWER_BI_STANDARD_FACULTY,ade29b5f-397e-4eb9-a287-0344bd46c68d,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,EXCHANGE_S_FOUNDATION Microsoft Fabric (Free) for student,POWER_BI_STANDARD_STUDENT,bdcaf6aa-04c1-4b8f-b64e-6e3bd505ac64,BI_AZURE_P0,2049e525-b859-401b-b2a0-e0a31c4b1fe4,BI_AZURE_P0 Microsoft Imagine Academy,IT_ACADEMY_AD,ba9a34de-4489-469d-879c-0f0f145321cd,IT_ACADEMY_AD,d736def0-1fde-43f0-a5be-e3f8b2de6e41,MS IMAGINE ACADEMY +Microsoft Intune Advanced Analytics,Microsoft_Intune_Advanced_Analytics,5e36d0d4-e9e5-4052-aba0-0257465c9b86,Intune_AdvancedEA,2a4baa0e-5e99-4c38-b1f2-6864960f1bd1,Microsoft Intune Advanced Analytics Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Intune Device,INTUNE_A_D,2b317a4a-77a6-4188-9437-b68a77b4e2c6,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Microsoft Intune Device for Government,INTUNE_A_D_GOV,2c21e77a-e0d6-4570-b38a-7ff2dc17d2ca,EXCHANGE_S_FOUNDATION_GOV,922ba911-5694-4e99-a794-73aed9bfeec8,Exchange Foundation for Government @@ -3696,6 +3732,10 @@ Microsoft Stream Storage Add-On (500 GB),STREAM_STORAGE,9bd7c846-9556-4453-a542- Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,CDS_CLOUD_FOR_SUSTAINABILITY_PLUS,ba80223c-e515-4642-a838-3e7d66f70cb6,Common Data Services for Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_PLUS,ed29ae92-ff5f-4446-8460-83c54d0e7088,MCS - BizApps Cloud for Sustainability Plus Microsoft Sustainability Manager Premium,Microsoft_Sustainability_Manager_Premium,aecb477b-2f56-4e38-b711-b752c24fc19b,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,MCS_BIZAPPS_CLOUD_FOR_SUSTAINABILITY_USL_PLUS,beaf5b5c-d11c-4417-b5cb-cd9f9e6719b0,MCS - BizApps Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_APPS_FOR_MCS_USL_PLUS,c5502fe7-406d-442a-827f-4948b821ba08,Power Apps for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,POWER_AUTOMATE_FOR_MCS_USL_PLUS,1c22bb50-96fb-49e5-baa6-195cab19eee2,Power Automate for Cloud for Sustainability USL Plus +Microsoft Sustainability Manager Premium USL Plus,MICROSOFT_SUSTAINABILITY_MANAGER_PREMIUM_USL_ADDON,9d576ffb-dd32-4c33-91ee-91625b61424a,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,MCS_BizApps_Cloud_for_Sustainability_USL,c46c42af-d654-4385-8c85-29a84f3dfb22,MCS - BizApps - Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_APPS_FOR_MCS_USL,5ffd371c-037a-41a2-98a3-6452f8c5de17,Power Apps for Cloud for Sustainability USL Microsoft Sustainability Manager USL Essentials,Microsoft_Cloud_for_Sustainability_USL,ece037b4-a52b-4cf8-93ea-649e5d83767a,POWER_AUTOMATE_FOR_MCS_USL,ccbe468e-7973-442c-8ec4-5fbe16438711,Power Automate for Cloud for Sustainability USL @@ -3808,18 +3848,20 @@ Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003 Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) Microsoft Teams Rooms Pro,Microsoft_Teams_Rooms_Pro,4cde982a-ede4-4409-9ae6-b003453c8ea6,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) -Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,TEAMS1,57ff2da0-773e-42df-b2af-ffb7a2317929,Microsoft Teams Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MTRProManagement,ecc74eae-eeb7-4ad5-9c88-e8b2bfca75b8,Microsoft Teams Rooms Pro Management Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Rooms_Pro,0374d34c-6be4-4dbb-b3f0-26105db0b28a,Teams Rooms Pro Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Basic,8081ca9c-188c-4b49-a8e5-c23b5e9463a8,Teams Rooms Test 1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,Teams_Room_Pro,ec17f317-f4bc-451e-b2da-0167e5c260f9,Teams Rooms Test 2 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WHITEBOARD_PLAN3,4a51bca5-1eff-43f5-878c-177680f191af,Whiteboard (Plan 3) Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,WINDEFATP,871d91ec-ec1a-452b-a83f-bd76c7d770ef,Microsoft Defender for Endpoint Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,AAD_PREMIUM,41781fb2-bc02-4b7c-bd55-b576c07bb09d,Microsoft Entra ID P1 Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,INTUNE_A,c1ec4a95-1f05-45b3-a911-aa3fa01094f5,Microsoft Intune Plan 1 +Microsoft Teams Rooms Pro for EDU,Microsoft_Teams_Rooms_Pro_FAC,c25e2b36-e161-4946-bef2-69239729f690,SPECIALTY_DEVICES,cfce7ae3-4b41-4438-999c-c0e91f3b7fb9,Specialty devices Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOMEETADV_GOV,f544b08d-1645-4287-82de-8d91f37c02a1,Microsoft 365 Audio Conferencing for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,MCOEV_GOV,db23fce2-a974-42ef-9002-d78dd42a0f22,Microsoft 365 Phone System for Government Microsoft Teams Rooms Pro for GCC,Microsoft_Teams_Rooms_Pro_GCC,31ecb341-2a17-483e-9140-c473006d1e1a,TEAMS_GOV,304767db-7d23-49e8-a945-4a7eb65f9f28,Microsoft Teams for Government @@ -3914,14 +3956,14 @@ Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-305 Office 365 Multi-Geo Capabilities,OFFICE365_MULTIGEO,84951599-62b7-46f3-9c9d-30551b2ad607,TEAMSMULTIGEO,41eda15d-6b52-453b-906f-bc4a5b25a26b,Teams Multi-Geo Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation Nonprofit Portal,NONPROFIT_PORTAL,aa2695c9-8d59-4800-9dc8-12e01f1735af,NONPROFIT_PORTAL,7dbc2d88-20e2-4eb6-b065-4510b38d6eb2,Nonprofit Portal -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3931,9 +3973,8 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Nucleus,db4d623d-b514-490b-b7ef-8885eee514de,Nucleus Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3942,6 +3983,9 @@ Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c897 Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for faculty,STANDARDWOFFPACK_FACULTY,94763226-9b3c-4e75-a931-5c89701abe66,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -3969,14 +4013,14 @@ Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-89 Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,VIVA_LEARNING_SEEDED,b76fb638-6ba6-402a-b9f9-83d28acb3d86,Viva Learning Seeded Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 Plus for faculty,STANDARDWOFFPACK_IW_FACULTY,78e66a63-337a-4a9a-8959-41c6654dfb56,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Azure Active Directory Basic for Education +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Azure Rights Management Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,EXCHANGE_S_STANDARD,9aaf7827-d63c-4b61-89c3-182f06f82e5c,Exchange Online (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INFORMATION_BARRIERS,c4801e8a-cb58-4c35-aca6-f2dcc106f287,Information Barriers -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,RMS_S_ENTERPRISE,bea4c11e-220a-4e6d-8eb8-8ea15d019f90,Microsoft Microsoft Entra Rights +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,M365_LIGHTHOUSE_CUSTOMER_PLAN1,6f23d6a9-adbf-481c-8538-b4c095654487,Microsoft 365 Lighthouse (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICE_FORMS_PLAN_2,9b5de886-f035-4ff2-b3d8-c9127bea3620,Microsoft Forms (Plan 2) -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Plan 2 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,KAIZALA_O365_P2,54fc630f-5a40-48ee-8965-af0503c1386e,Microsoft Kaizala Pro Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECTWORKMANAGEMENT,b737dad2-2f6c-4c65-90e3-ca563267e8b9,Microsoft Planner Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MICROSOFT_SEARCH,94065c59-bc8e-4e8b-89e5-5138d471eaff,Microsoft Search Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Deskless,8c7d2df8-86f0-4902-b2ed-a0458298f3b3,Microsoft StaffHub @@ -3985,9 +4029,8 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,INTUNE_O365,882e1d05-acd1-4ccb-8708-6ee03664b117,Mobile Device Management for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTWAC_EDU,e03c7e47-402c-463c-ab25-949079bedb21,Office for the Web for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,OFFICEMOBILE_SUBSCRIPTION,c63d4d19-e8cb-460e-b37c-4d6c34603745,Office Mobile Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 -Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,PROJECT_O365_P1,a55dfd10-0864-46d9-a3cd-da5991a3e0e2,Project for Office (Plan E1) +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,Bing_Chat_Enterprise,0d0c0d31-fae7-41f2-b909-eaf4d7f26dba,RETIRED - Commercial data protection for Microsoft Copilot Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SCHOOL_DATA_SYNC_P1,c33802dd-1b50-4b9a-8bb9-f13d2cdeadac,School Data Sync (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,SHAREPOINTSTANDARD_EDU,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5,SharePoint (Plan 1) for Education Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,MCOSTANDARD,0feaeb32-d00e-4d66-bd5a-43b5b83db82c,Skype for Business Online (Plan 2) @@ -3995,6 +4038,9 @@ Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4 Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,BPOS_S_TODO_2,c87f142c-d1e9-4363-8630-aaea9c4d9ae5,To-Do (Plan 2) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,WHITEBOARD_PLAN1,b8afc642-032e-4de5-8c0a-507a7bba7e5d,Whiteboard (Plan 1) Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,YAMMER_EDU,2078e8df-cff6-4290-98cb-5408261a760a,Yammer for Academic +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,POWERAPPS_O365_P2,c68f8d98-5534-41c8-bf36-22fa496fa792,Power Apps for Office 365 +Office 365 A1 for students,STANDARDWOFFPACK_STUDENT,314c4481-f395-4525-be8b-2ec4bb1e9d91,FLOW_O365_P2,76846ad7-7776-4c40-a281-a386362dd1b9,Power Automate for Office 365 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,AAD_BASIC_EDU,1d0f309f-fdf9-4b2a-9ae7-9c48b91f1426,Microsoft Entra ID Basic for Education Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,DYN365_CDS_O365_P1,40b010bb-0b69-4654-ac5e-ba161433f4b4,Common Data Service - O365 P1 Office 365 A1 Plus for students,STANDARDWOFFPACK_IW_STUDENT,e82ae690-a2d5-4d76-8d30-7c6e01e6022e,EducationAnalyticsP1,a9b86446-fa4e-498f-a92a-41b447e03337,Education Analytics @@ -5059,8 +5105,11 @@ Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-5 Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,CDS_ POWERAPPS_PER_USER_CUSTOM,2e8dde43-6986-479d-b179-7dbe31c31f60,CDS Power Apps Per User Custom Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Per User BD Only,POWERAPPS_PER_USER_BD_ONLY,2ced8a00-3ed1-4295-ab7c-57170ff28e58,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan -Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service - P2 +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Power_Pages_Internal_User,60bf28f9-2b70-4522-96f7-335f5e06c941,Power Pages Internal User Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER_NEW,74d93933-6f22-436e-9441-66d205435abb,AI Builder capacity Per User add-on +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,DYN365_CDS_P2,6ea4c1ef-c259-46df-bce2-943342cd3cb2,Common Data Service +Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,DO NOT USE - AI Builder capacity Per User add-on Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,POWERAPPS_PER_USER,ea2cf03b-ac60-46ae-9c1d-eeaeb63cec86,Power Apps per User Plan Power Apps Premium,POWERAPPS_PER_USER,b30411f5-fea1-4a59-9ad9-3db7c7ead579,Flow_PowerApps_PerUser,dc789ed8-0170-4b65-a415-eb77d5bb350a,Power Automate for Power Apps per User Plan Power Apps Premium for Government,POWERAPPS_PER_USER_GCC,8e4c6baa-f2ff-4884-9c38-93785d0d7ba1,CDSAICAPACITY_PERUSER,91f50f7b-2204-4803-acac-5cf5668b8b39,AI Builder capacity Per User add-on @@ -5130,6 +5179,8 @@ Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1 Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User Power BI Premium Per User,PBI_PREMIUM_PER_USER,c1d032e0-5619-4761-9b5c-75b6831e1711,BI_AZURE_P2,70d33638-9c74-4d01-bfd3-562de28bd4ba,Power BI Pro Power BI Premium Per User Add-On,PBI_PREMIUM_PER_USER_ADDON,de376a03-6e5b-42ec-855f-093fb50b8ca5,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,BI_AZURE_P3,0bf3c642-7bb5-4ccc-884e-59d09df0266c,Power BI Premium Per User +Power BI Premium Per User Add-On for Faculty,PBI_PREMIUM_PER_USER_ADDON_FACULTY,c05b235f-be75-4029-8851-6a4170758eef,PURVIEW_DISCOVERY,c948ea65-2053-4a5a-8a62-9eaaaf11b522,Purview Discovery Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_CE_GCC,66024bbf-4cd4-4329-95c8-c932e2ae01a8,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Add-On for GCC,PBI_PREMIUM_PER_USER_ADDON_GCC,1b572d5e-1bf8-4b19-9259-f9eda31a6972,BI_AZURE_P3_GOV,32d15238-9a8c-46da-af3f-21fc5351d365,Power BI Premium Per User for Government Power BI Premium Per User Dept,PBI_PREMIUM_PER_USER_DEPT,f168a3fb-7bcf-4a27-98c3-c235ea4b78b4,EXCHANGE_S_FOUNDATION,113feb6c-3fe4-4440-bddc-54d774bf0318,Exchange Foundation @@ -5426,6 +5477,14 @@ Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-49 Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments Teams Premium (for Departments),Teams_Premium_(for_Departments),52ea0e27-ae73-4983-a08f-13561ebdb823,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MICROSOFT_ECDN,85704d55-2e73-47ee-93b4-4b8ea14db92b,Microsoft eCDN +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_MGMT,0504111f-feb8-4a3c-992a-70280f9a2869,Microsoft Teams Premium Intelligent +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_CUST,cc8c0802-a325-43df-8cba-995d0c6cb373,Microsoft Teams Premium Personalized +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_PROTECTION,f8b44f54-18bb-46a3-9658-44ab58712968,Microsoft Teams Premium Secure +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_VIRTUALAPPT,9104f592-f2a7-4f77-904c-ca5a5715883f,Microsoft Teams Premium Virtual Appointment +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,MCO_VIRTUAL_APPT,711413d0-b36e-4cd4-93db-0a50a4ab7ea3,Microsoft Teams Premium Virtual Appointments +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,TEAMSPRO_WEBINAR,78b58230-ec7e-4309-913c-93a45cc4735b,Microsoft Teams Premium Webinar +Teams Premium for Faculty,Teams_Premium_for_Faculty,960a972f-d017-4a17-8f64-b42c8035bc7d,QUEUES_APP,ab2d4fb5-f80a-4bf1-a11d-7f1da254041b,Queues app for Microsoft Teams Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MMR_P1,bdaa59a3-74fd-4137-981a-31d4f84eb8a0,Meeting Room Managed Services Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOMEETADV,3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40,Microsoft 365 Audio Conferencing Teams Rooms Premium,MTR_PREM,4fb214cb-a430-4a91-9c91-4976763aa78f,MCOEV,4828c8ec-dc2e-4779-b502-87ac9ce28ab7,Microsoft 365 Phone System From 67162fe6ea2ccdd22727d587e61d868757d74deb Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 15:12:42 -0400 Subject: [PATCH 094/125] fix sherweb license add from form --- Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 index 8c2c46712b11..1f4796422ab2 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecCSPLicense.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-ExecCSPLicense { +function Invoke-ExecCSPLicense { <# .FUNCTIONALITY Entrypoint @@ -17,7 +17,7 @@ Function Invoke-ExecCSPLicense { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Body.tenantFilter $Action = $Request.Body.Action - $SKU = $Request.Body.SKU + $SKU = $Request.Body.SKU.value ?? $Request.Body.SKU try { if ($Action -eq 'Add') { From fff1b0781048deeb5e5cf5310b0df00c2557860b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 16:00:13 -0400 Subject: [PATCH 095/125] app manifest support --- .../Push-ExecAppApprovalTemplate.ps1 | 68 ++++++++++- .../Core/Invoke-ExecServicePrincipals.ps1 | 7 +- .../Invoke-CIPPStandardAppDeploy.ps1 | 107 ++++++++++++++++-- 3 files changed, 170 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 index 0ca240a2a702..acde7892b480 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAppApprovalTemplate.ps1 @@ -43,7 +43,7 @@ function Push-ExecAppApprovalTemplate { # Check if the app already exists in the tenant $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Item.Tenant if ($TemplateData.GalleryTemplateId -in $ServicePrincipalList.applicationTemplateId) { - Write-LogMessage -message "Gallery Template app $($TemplateData.AppName) already exists in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info + Write-LogMessage -message "Gallery Template app $($TemplateData.AppName) already exists in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Gallery App' -sev Info return } @@ -55,10 +55,70 @@ function Push-ExecAppApprovalTemplate { $InstantiateResult = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/applicationTemplates/$GalleryTemplateId/instantiate" -type POST -tenantid $Item.tenant -body $InstantiateBody if ($InstantiateResult.application.appId) { - Write-LogMessage -message "Successfully deployed Gallery Template $($TemplateData.AppName) to tenant $($Item.Tenant). Application ID: $($InstantiateResult.application.appId)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info - New-CIPPApplicationCopy -App $InstantiateResult.application.appId -Tenant $Item.Tenant + Write-LogMessage -message "Successfully deployed Gallery Template $($TemplateData.AppName) to tenant $($Item.Tenant). Application ID: $($InstantiateResult.application.appId)" -tenant $Item.Tenant -API 'Add Gallery App' -sev Info + # Get application registration + $App = $InstantiateResult.application.appId + $Application = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/applications(appId='$App')" -tenantid $Item.tenant -AsApp $true + if ($Application.requiredResourceAccess) { + Add-CIPPDelegatedPermission -RequiredResourceAccess $Application.requiredResourceAccess -ApplicationId $App -Tenantfilter $Item.Tenant + Add-CIPPApplicationPermission -RequiredResourceAccess $Application.requiredResourceAccess -ApplicationId $App -Tenantfilter $Item.Tenant + } } else { - Write-LogMessage -message "Gallery Template deployment completed but application ID not returned for $($TemplateData.AppName) in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Warning + Write-LogMessage -message "Gallery Template deployment completed but application ID not returned for $($TemplateData.AppName) in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Gallery App' -sev Warning + } + + } elseif ($AppType -eq 'ApplicationManifest') { + Write-Information "Deploying Application Manifest $($TemplateData.AppName) to tenant $($Item.Tenant)." + + # Get the application manifest from template data + $ApplicationManifest = $TemplateData.ApplicationManifest + if (!$ApplicationManifest) { + Write-LogMessage -message 'Application Manifest not found in template data' -tenant $Item.Tenant -API 'Add Multitenant App' -sev Error + return + } + + # Check for existing application by display name + $ExistingApp = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/applications?`$filter=displayName eq '$($TemplateData.AppName)'&`$top=1" -tenantid $Item.Tenant -NoAuthCheck $true + if ($ExistingApp -and $ExistingApp.value) { + Write-LogMessage -message "Application Manifest $($TemplateData.AppName) already exists in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add App Manifest' -sev Info + return + } + + $PropertiesToRemove = @('appId', 'id', 'createdDateTime', 'publisherDomain', 'servicePrincipalLockConfiguration', 'identifierUris', 'applicationIdUris') + + # Strip tenant-specific data that might cause conflicts + $CleanManifest = $ApplicationManifest | ConvertTo-Json -Depth 10 | ConvertFrom-Json + foreach ($Property in $PropertiesToRemove) { + $CleanManifest.PSObject.Properties.Remove($Property) + } + + # Create the application from manifest + try { + $CreateBody = $CleanManifest | ConvertTo-Json -Depth 10 + $CreatedApp = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/applications' -type POST -tenantid $Item.tenant -body $CreateBody + + if ($CreatedApp.appId) { + # Create service principal for the application + $ServicePrincipalBody = @{ + appId = $CreatedApp.appId + } | ConvertTo-Json + + $ServicePrincipal = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Item.tenant -body $ServicePrincipalBody + + Write-LogMessage -message "Successfully deployed Application Manifest $($TemplateData.AppName) to tenant $($Item.Tenant). Application ID: $($CreatedApp.appId)" -tenant $Item.Tenant -API 'Add App Manifest' -sev Info + $DelegateResourceAccess = $ApplicationManifest.requiredResourceAccess + $ApplicationResourceAccess = $ApplicationManifest.requiredResourceAccess + if ($ApplicationManifest.requiredResourceAccess) { + Add-CIPPDelegatedPermission -RequiredResourceAccess $ApplicationManifest.requiredResourceAccess -ApplicationId $App -Tenantfilter $Tenant + Add-CIPPApplicationPermission -RequiredResourceAccess $ApplicationManifest.requiredResourceAccess -ApplicationId $App -Tenantfilter $Tenant + } + + } else { + Write-LogMessage -message "Application Manifest deployment failed - no application ID returned for $($TemplateData.AppName) in tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add App Manifest' -sev Error + } + } catch { + Write-LogMessage -message "Error creating application from manifest in tenant $($Item.Tenant) - $($_.Exception.Message)" -tenant $Item.Tenant -API 'Add App Manifest' -sev Error + throw } } else { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 index c0f7873d34ac..3901f22ff01a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 @@ -61,7 +61,12 @@ function Invoke-ExecServicePrincipals { $Results = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/servicePrincipals/$($Request.Query.Id)" -tenantid $TenantFilter -NoAuthCheck $true } else { $Action = 'List' - $Results = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$orderby=displayName&$count=true' -ComplexFilter -tenantid $TenantFilter -NoAuthCheck $true + $Uri = 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$orderby=displayName&$count=true' + if ($Request.Query.Select) { + $Uri = '{0}&$select={1}' -f $Uri, $Request.Query.Select + } + + $Results = New-GraphGetRequest -Uri $Uri -ComplexFilter -tenantid $TenantFilter -NoAuthCheck $true } } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 index 93083d0c51ad..1848e47918a6 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAppDeploy.ps1 @@ -47,15 +47,60 @@ function Invoke-CIPPStandardAppDeploy { $Template = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'AppApprovalTemplate' and RowKey eq '$TemplateId'" if ($Template) { $TemplateData = $Template.JSON | ConvertFrom-Json - if ($TemplateData.AppId) { - $TemplateData.AppId ?? $TemplateData.GalleryTemplateId + # Default to EnterpriseApp for backward compatibility with older templates + $AppType = $TemplateData.AppType + if (-not $AppType) { + $AppType = 'EnterpriseApp' + } + + # Return different identifiers based on app type for checking + if ($AppType -eq 'ApplicationManifest') { + # For Application Manifests, use display name for checking + $TemplateData.AppName + } elseif ($AppType -eq 'GalleryTemplate') { + # For Gallery Templates, use gallery template ID + $TemplateData.GalleryTemplateId + } else { + # For Enterprise Apps, use app ID + $TemplateData.AppId } } } } - $MissingApps = foreach ($App in $AppsToAdd) { - if ($App -notin $AppExists.appId -and $App -notin $AppExists.applicationTemplateId) { - $App + + # Check for missing apps based on template type + $MissingApps = [System.Collections.Generic.List[string]]::new() + if ($Mode -eq 'template') { + $Table = Get-CIPPTable -TableName 'templates' + foreach ($TemplateId in $Settings.templateIds.value) { + $Template = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'AppApprovalTemplate' and RowKey eq '$TemplateId'" + if ($Template) { + $TemplateData = $Template.JSON | ConvertFrom-Json + $AppType = $TemplateData.AppType ?? 'EnterpriseApp' + + $IsAppMissing = $false + if ($AppType -eq 'ApplicationManifest') { + # For Application Manifests, check by display name + $IsAppMissing = $TemplateData.AppName -notin $AppExists.displayName + } elseif ($AppType -eq 'GalleryTemplate') { + # For Gallery Templates, check by application template ID + $IsAppMissing = $TemplateData.GalleryTemplateId -notin $AppExists.applicationTemplateId + } else { + # For Enterprise Apps, check by app ID + $IsAppMissing = $TemplateData.AppId -notin $AppExists.appId + } + + if ($IsAppMissing) { + $MissingApps.Add($TemplateData.AppName ?? $TemplateData.AppId ?? $TemplateData.GalleryTemplateId) + } + } + } + } else { + # For copy mode, check by app ID as before + $MissingApps = foreach ($App in $AppsToAdd) { + if ($App -notin $AppExists.appId -and $App -notin $AppExists.applicationTemplateId) { + $App + } } } if ($Settings.remediate -eq $true) { @@ -76,8 +121,6 @@ function Invoke-CIPPStandardAppDeploy { } } elseif ($Mode -eq 'template') { $TemplateIds = $Settings.templateIds.value - $TemplateName = $Settings.templateIds.label - $AppIds = $Settings.templateIds.addedFields.AppId # Get template data to determine deployment type for each template $Table = Get-CIPPTable -TableName 'templates' @@ -129,6 +172,56 @@ function Invoke-CIPPStandardAppDeploy { Write-LogMessage -API 'Standards' -tenant $tenant -message "Gallery Template deployment completed but application ID not returned for $($TemplateData.AppName) in tenant $Tenant" -sev Warning } + } elseif ($AppType -eq 'ApplicationManifest') { + # Handle Application Manifest deployment + Write-Information "Deploying Application Manifest $($TemplateData.AppName) to tenant $Tenant." + + $ApplicationManifest = $TemplateData.ApplicationManifest + if (!$ApplicationManifest) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Application Manifest not found in template data for $($TemplateData.TemplateName)" -sev Error + continue + } + + # Check if an application with the same display name already exists + $ExistingApp = $AppExists | Where-Object { $_.displayName -eq $TemplateData.AppName } + if ($ExistingApp) { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Application with name '$($TemplateData.AppName)' already exists in tenant $Tenant" -sev Info + continue + } + + $PropertiesToRemove = @('appId', 'id', 'createdDateTime', 'publisherDomain', 'servicePrincipalLockConfiguration', 'identifierUris', 'applicationIdUris') + + # Strip tenant-specific data that might cause conflicts + $CleanManifest = $ApplicationManifest | ConvertTo-Json -Depth 10 | ConvertFrom-Json + foreach ($Property in $PropertiesToRemove) { + $CleanManifest.PSObject.Properties.Remove($Property) + } + # Create the application from manifest + try { + $CreateBody = $CleanManifest | ConvertTo-Json -Depth 10 + $CreatedApp = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/applications' -type POST -tenantid $Tenant -body $CreateBody + + if ($CreatedApp.appId) { + # Create service principal for the application + $ServicePrincipalBody = @{ + appId = $CreatedApp.appId + } | ConvertTo-Json + + $ServicePrincipal = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Tenant -body $ServicePrincipalBody + + Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully deployed Application Manifest $($TemplateData.AppName) to tenant $Tenant. Application ID: $($CreatedApp.appId)" -sev Info + + if ($CreatedApp.requiredResourceAccess) { + Add-CIPPDelegatedPermission -RequiredResourceAccess $CreatedApp.requiredResourceAccess -ApplicationId $CreatedApp.appId -Tenantfilter $Tenant + Add-CIPPApplicationPermission -RequiredResourceAccess $CreatedApp.requiredResourceAccess -ApplicationId $CreatedApp.appId -Tenantfilter $Tenant + } + } else { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Application Manifest deployment failed - no application ID returned for $($TemplateData.AppName) in tenant $Tenant" -sev Error + } + } catch { + Write-LogMessage -API 'Standards' -tenant $tenant -message "Error creating application from manifest in tenant $Tenant - $($_.Exception.Message)" -sev Error + } + } else { # Handle Enterprise App deployment (existing logic) $AppId = $TemplateData.AppId From 1871edc7c749d88a951dc2fad029857031f02f15 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 21:55:05 -0400 Subject: [PATCH 096/125] new application actions --- .../Core/Invoke-ExecServicePrincipals.ps1 | 2 +- .../Invoke-ExecApplication.ps1 | 135 ++++++++++++++++++ .../GraphHelper/New-GraphBulkRequest.ps1 | 8 +- 3 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 index 3901f22ff01a..704a75cb30ad 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 @@ -3,7 +3,7 @@ function Invoke-ExecServicePrincipals { .FUNCTIONALITY Entrypoint,AnyTenant .ROLE - CIPP.Core.ReadWrite + Tenant.Application.ReadWrite #> [CmdletBinding()] param($Request, $TriggerMetadata) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 new file mode 100644 index 000000000000..94a5cb4802e9 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 @@ -0,0 +1,135 @@ +function Invoke-ExecApplication { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.Application.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug' + + $ValidTypes = @('applications', 'servicePrincipals') + $ValidActions = @('Update', 'Upsert', 'Delete', 'RemoveKey', 'RemovePassword') + + $Id = $Request.Query.Id ?? $Request.Body.Id + $Type = $Request.Query.Type ?? $Request.Body.Type + if (-not $Id) { + $AppId = $Request.Query.AppId ?? $Request.Body.AppId + if (-not $AppId) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = "Required parameter 'Id' or 'AppId' is missing" + }) + return + } + $IdPath = "(appId='$AppId')" + } else { + $IdPath = "/$Id" + } + if ($Type -and $ValidTypes -notcontains $Type) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = "Invalid Type specified. Valid types are: $($ValidTypes -join ', ')" + }) + return + } + + $Uri = "https://graph.microsoft.com/beta/$($Type)$($IdPath)" + $Action = $Request.Query.Action ?? $Request.Body.Action + + if ($ValidActions -notcontains $Action) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = "Invalid Action specified. Valid actions are: $($ValidActions -join ', ')" + }) + return + } + + $PostParams = @{ + Uri = $Uri + } + + if ($Action -eq 'Delete') { + $PostParams.Type = 'DELETE' + } + if ($Action -eq 'Update' -or $Action -eq 'Upsert') { + $PostParams.Type = 'PATCH' + } + + if ($Action -eq 'Upsert') { + $PostParams.AddedHeaders = @{ + 'Prefer' = 'create-if-missing' + } + } + + if ($Request.Body) { + $PostParams.Body = $Request.Body.Payload | ConvertTo-Json -Compress + } + + $TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter + + try { + if ($Action -eq 'RemoveKey' -or $Action -eq 'RemovePassword') { + # Handle credential removal by patching the object + $KeyIds = $Request.Body.KeyIds.value ?? $Request.Body.KeyIds + if (-not $KeyIds -or $KeyIds.Count -eq 0) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = "KeyIds parameter is required for $Action action" + }) + return + } + + # Get the current application/service principal + $CurrentObject = New-GraphGetRequest -Uri $Uri -tenantid $TenantFilter -AsApp $true + + if ($Action -eq 'RemoveKey') { + # Filter out the key credentials to remove + $UpdatedKeyCredentials = $CurrentObject.keyCredentials | Where-Object { $_.keyId -notin $KeyIds } + $PatchBody = @{ + keyCredentials = @($UpdatedKeyCredentials) + } + } else { + # Filter out the password credentials to remove + $UpdatedPasswordCredentials = $CurrentObject.passwordCredentials | Where-Object { $_.keyId -notin $KeyIds } + $PatchBody = @{ + passwordCredentials = @($UpdatedPasswordCredentials) + } + } + + # Update the object with the filtered credentials + $null = New-GraphPOSTRequest -Uri $Uri -Type 'PATCH' -Body ($PatchBody | ConvertTo-Json -Depth 10) -tenantid $TenantFilter -AsApp $true + + $Results = @{ + resultText = "Successfully removed $($KeyIds.Count) credential(s) from $Type" + state = 'success' + } + } else { + # Handle regular actions + $null = New-GraphPOSTRequest @PostParams -tenantid $TenantFilter -AsApp $true + $Results = @{ + resultText = "Successfully executed $Action on $Type with Id: $Id" + state = 'success' + } + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @{ Results = $Results } + }) + } catch { + $Results = @{ + resultText = "Failed to execute $Action on $Type with Id: $Id. Error: $($_.Exception.Message)" + state = 'error' + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::InternalServerError + Body = @{ Results = @($Results) } + }) + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 index de474b22f1f7..12b72accf29e 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphBulkRequest.ps1 @@ -3,19 +3,21 @@ function New-GraphBulkRequest { .FUNCTIONALITY Internal #> - Param( + param( $tenantid, $NoAuthCheck, $scope, $asapp, $Requests, - $NoPaginateIds = @() + $NoPaginateIds = @(), + [ValidateSet('v1.0', 'beta')] + $Version = 'beta' ) if ($NoAuthCheck -or (Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp - $URL = 'https://graph.microsoft.com/beta/$batch' + $URL = "https://graph.microsoft.com/$Version/`$batch" # Track consecutive Graph API failures $TenantsTable = Get-CippTable -tablename Tenants From fa50f5d7a4b9ccb3d8dab135e92effd12772263a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Jul 2025 23:07:04 -0400 Subject: [PATCH 097/125] improve password cred removal --- .../Invoke-ExecApplication.ps1 | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 index 94a5cb4802e9..c46541d8632f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecApplication.ps1 @@ -74,7 +74,7 @@ function Invoke-ExecApplication { try { if ($Action -eq 'RemoveKey' -or $Action -eq 'RemovePassword') { - # Handle credential removal by patching the object + # Handle credential removal $KeyIds = $Request.Body.KeyIds.value ?? $Request.Body.KeyIds if (-not $KeyIds -or $KeyIds.Count -eq 0) { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ @@ -84,29 +84,49 @@ function Invoke-ExecApplication { return } - # Get the current application/service principal - $CurrentObject = New-GraphGetRequest -Uri $Uri -tenantid $TenantFilter -AsApp $true - if ($Action -eq 'RemoveKey') { - # Filter out the key credentials to remove + # For key credentials, use a single PATCH request + $CurrentObject = New-GraphGetRequest -Uri $Uri -tenantid $TenantFilter -AsApp $true $UpdatedKeyCredentials = $CurrentObject.keyCredentials | Where-Object { $_.keyId -notin $KeyIds } $PatchBody = @{ keyCredentials = @($UpdatedKeyCredentials) } + + $Response = New-GraphPOSTRequest -Uri $Uri -Type 'PATCH' -Body ($PatchBody | ConvertTo-Json -Depth 10) -tenantid $TenantFilter -AsApp $true + + $Results = @{ + resultText = "Successfully removed $($KeyIds.Count) key credential(s) from $Type" + state = 'success' + details = @($Response) + } } else { - # Filter out the password credentials to remove - $UpdatedPasswordCredentials = $CurrentObject.passwordCredentials | Where-Object { $_.keyId -notin $KeyIds } - $PatchBody = @{ - passwordCredentials = @($UpdatedPasswordCredentials) + # For password credentials, use bulk removePassword requests + $BulkRequests = foreach ($KeyId in $KeyIds) { + $RemoveBody = @{ + keyId = $KeyId + } + + @{ + id = $KeyId + method = 'POST' + url = "$($Type)$($IdPath)/removePassword" + body = $RemoveBody + headers = @{ + 'Content-Type' = 'application/json' + } + } } - } - # Update the object with the filtered credentials - $null = New-GraphPOSTRequest -Uri $Uri -Type 'PATCH' -Body ($PatchBody | ConvertTo-Json -Depth 10) -tenantid $TenantFilter -AsApp $true + $BulkResults = New-GraphBulkRequest -Requests @($BulkRequests) -tenantid $TenantFilter -AsApp $true - $Results = @{ - resultText = "Successfully removed $($KeyIds.Count) credential(s) from $Type" - state = 'success' + $SuccessCount = ($BulkResults | Where-Object { $_.status -eq 204 }).Count + $FailureCount = ($BulkResults | Where-Object { $_.status -ne 204 }).Count + + $Results = @{ + resultText = "Bulk RemovePassword completed. Success: $SuccessCount, Failures: $FailureCount" + state = if ($FailureCount -eq 0) { 'success' } else { 'error' } + details = @($BulkResults) + } } } else { # Handle regular actions @@ -132,4 +152,4 @@ function Invoke-ExecApplication { Body = @{ Results = @($Results) } }) } -} \ No newline at end of file +} From 4397a3c381f4d165bfa22a135cccb1faa40a2ef0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 15:07:22 +0200 Subject: [PATCH 098/125] add disable Ca --- .../Invoke-ListConditionalAccessPolicies.ps1 | 71 +++++++++---------- Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 8 ++- ...-CIPPStandardConditionalAccessTemplate.ps1 | 2 +- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 index f4a02084bcb2..91f7251eccc7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 @@ -23,11 +23,13 @@ function Invoke-ListConditionalAccessPolicies { $Locations ) if ($id -eq 'All') { - return @{label = 'All'; value = 'All' } + return 'All' } $DisplayName = $Locations | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName - if (![string]::IsNullOrEmpty($displayName)) { - return @{label = $DisplayName; value = $ID } + if ([string]::IsNullOrEmpty($displayName)) { + return $ID + } else { + return $DisplayName } } @@ -39,13 +41,13 @@ function Invoke-ListConditionalAccessPolicies { $RoleDefinitions ) if ($id -eq 'All') { - return @{label = 'All'; value = 'All' } + return 'All' } $DisplayName = $RoleDefinitions | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName if ([string]::IsNullOrEmpty($displayName)) { - return @{label = $ID; value = $ID } + return $ID } else { - return @{label = $DisplayName; value = $ID } + return $DisplayName } } @@ -57,13 +59,13 @@ function Invoke-ListConditionalAccessPolicies { $Users ) if ($id -eq 'All') { - return @{label = 'All'; value = 'All' } + return 'All' } $DisplayName = $Users | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName if ([string]::IsNullOrEmpty($displayName)) { - return @{label = $ID; value = $ID } + return $ID } else { - return @{label = $DisplayName; value = $ID } + return $DisplayName } } @@ -74,13 +76,13 @@ function Invoke-ListConditionalAccessPolicies { $Groups ) if ($id -eq 'All') { - return @{label = 'All'; value = 'All' } + return 'All' } $DisplayName = $Groups | Where-Object { $_.id -eq $ID } | Select-Object -ExpandProperty DisplayName if ([string]::IsNullOrEmpty($displayName)) { - return @{label = 'No Data'; value = 'No Data' } + return 'No Data' } else { - return @{label = $DisplayName; value = $ID } + return $DisplayName } } @@ -93,7 +95,7 @@ function Invoke-ListConditionalAccessPolicies { $ServicePrincipals ) if ($id -eq 'All') { - return @{label = 'All'; value = 'All' } + return 'All' } $return = $ServicePrincipals | Where-Object { $_.appId -eq $ID } | Select-Object -ExpandProperty DisplayName @@ -110,10 +112,7 @@ function Invoke-ListConditionalAccessPolicies { $return = '' } - if ($return) { - $return = @{label = $return; value = $ID } - return $return - } + return $return } # Interact with query parameters or the body of the request. @@ -177,25 +176,25 @@ function Invoke-ListConditionalAccessPolicies { createdDateTime = $(if (![string]::IsNullOrEmpty($cap.createdDateTime)) { [datetime]$cap.createdDateTime } else { '' }) modifiedDateTime = $(if (![string]::IsNullOrEmpty($cap.modifiedDateTime)) { [datetime]$cap.modifiedDateTime }else { '' }) state = $cap.state - clientAppTypes = @(if ($cap.conditions.clientAppTypes) { $cap.conditions.clientAppTypes | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) - includePlatforms = @(if ($cap.conditions.platforms.includePlatforms) { $cap.conditions.platforms.includePlatforms | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) - excludePlatforms = @(if ($cap.conditions.platforms.excludePlatforms) { $cap.conditions.platforms.excludePlatforms | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) - includeLocations = @(Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.includeLocations) - excludeLocations = @(Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.excludeLocations) - includeApplications = @(Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $cap.conditions.applications.includeApplications) - excludeApplications = @(Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $cap.conditions.applications.excludeApplications) - includeUserActions = @($cap.conditions.applications.includeUserActions ) - includeAuthenticationContextClassReferences = @($cap.conditions.applications.includeAuthenticationContextClassReferences ) - includeUsers = @($cap.conditions.users.includeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) - excludeUsers = @($cap.conditions.users.excludeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) - includeGroups = @($cap.conditions.users.includeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) - excludeGroups = @($cap.conditions.users.excludeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) - includeRoles = @($cap.conditions.users.includeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) - excludeRoles = @($cap.conditions.users.excludeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) - grantControlsOperator = @(if ($cap.grantControls.operator) { $cap.grantControls.operator | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) - builtInControls = @(if ($cap.grantControls.builtInControls) { $cap.grantControls.builtInControls | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) - customAuthenticationFactors = @(if ($cap.grantControls.customAuthenticationFactors) { $cap.grantControls.customAuthenticationFactors | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) - termsOfUse = @(if ($cap.grantControls.termsOfUse) { $cap.grantControls.termsOfUse | ForEach-Object { return @{label = $_; value = $_ } } } else { @() }) + clientAppTypes = ($cap.conditions.clientAppTypes) -join ',' + includePlatforms = ($cap.conditions.platforms.includePlatforms) -join ',' + excludePlatforms = ($cap.conditions.platforms.excludePlatforms) -join ',' + includeLocations = (Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.includeLocations) -join ',' + excludeLocations = (Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.excludeLocations) -join ',' + includeApplications = ($cap.conditions.applications.includeApplications | ForEach-Object { Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $_ }) -join ',' + excludeApplications = ($cap.conditions.applications.excludeApplications | ForEach-Object { Get-ApplicationNameFromId -Applications $AllApplications -ServicePrincipals $AllServicePrincipals -id $_ }) -join ',' + includeUserActions = ($cap.conditions.applications.includeUserActions | Out-String) + includeAuthenticationContextClassReferences = ($cap.conditions.applications.includeAuthenticationContextClassReferences | Out-String) + includeUsers = ($cap.conditions.users.includeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) | Out-String + excludeUsers = ($cap.conditions.users.excludeUsers | ForEach-Object { Get-UserNameFromId -Users $UserListOutput -id $_ }) | Out-String + includeGroups = ($cap.conditions.users.includeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) | Out-String + excludeGroups = ($cap.conditions.users.excludeGroups | ForEach-Object { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) | Out-String + includeRoles = ($cap.conditions.users.includeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) | Out-String + excludeRoles = ($cap.conditions.users.excludeRoles | ForEach-Object { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) | Out-String + grantControlsOperator = ($cap.grantControls.operator) -join ',' + builtInControls = ($cap.grantControls.builtInControls) -join ',' + customAuthenticationFactors = ($cap.grantControls.customAuthenticationFactors) -join ',' + termsOfUse = ($cap.grantControls.termsOfUse) -join ',' rawjson = ($cap | ConvertTo-Json -Depth 100) } $temp diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 8dd288691767..4ef1a08e7278 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -7,6 +7,7 @@ function New-CIPPCAPolicy { $State, $Overwrite, $ReplacePattern = 'none', + $DisableSD = $false, $APIName = 'Create CA Policy', $Headers ) @@ -225,7 +226,12 @@ function New-CIPPCAPolicy { } } } - + if ($DisableSD -eq $true) { + #Send request to disable security defaults. + $body = '{ "isEnabled": false }' + $null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -Type patch -Body $body -ContentType 'application/json' + Write-LogMessage -Headers $User -API $APINAME -tenant $($Tenant) -message "Disabled Security Defaults for tenant $($TenantFilter)" -Sev 'Info' + } $RawJSON = ConvertTo-Json -InputObject $JSONObj -Depth 10 -Compress Write-Information $RawJSON try { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 index 3220da6c44f0..6e6e34788e12 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 @@ -39,7 +39,7 @@ function Invoke-CIPPStandardConditionalAccessTemplate { $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'CATemplate' and RowKey eq '$($Setting.TemplateList.value)'" $JSONObj = (Get-CippAzDataTableEntity @Table -Filter $Filter).JSON - $null = New-CIPPCAPolicy -replacePattern 'displayName' -TenantFilter $tenant -state $Setting.state -RawJSON $JSONObj -Overwrite $true -APIName $APIName -Headers $Request.Headers + $null = New-CIPPCAPolicy -replacePattern 'displayName' -TenantFilter $tenant -state $Setting.state -RawJSON $JSONObj -Overwrite $true -APIName $APIName -Headers $Request.Headers -DisableSD $Setting.DisableSD } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update conditional access rule $($JSONObj.displayName). Error: $ErrorMessage" -sev 'Error' From fb9819cfe29ef34a012be9d8da9b3bb6db145480 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 15:24:12 +0200 Subject: [PATCH 099/125] updates for ca disablement --- .../HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 | 4 ++-- Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 index fc6fa8b523f3..bd5c6e397f4b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCAPolicy.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-AddCAPolicy { +function Invoke-AddCAPolicy { <# .FUNCTIONALITY Entrypoint @@ -19,7 +19,7 @@ Function Invoke-AddCAPolicy { $results = foreach ($Tenant in $tenants) { try { - $CAPolicy = New-CIPPCAPolicy -replacePattern $Request.Body.replacename -Overwrite $request.Body.overwrite -TenantFilter $Tenant -state $Request.Body.NewState -RawJSON $Request.Body.RawJSON -APIName $APIName -Headers $Headers + $CAPolicy = New-CIPPCAPolicy -replacePattern $Request.Body.replacename -Overwrite $request.Body.overwrite -TenantFilter $Tenant -state $Request.Body.NewState -DisableSD $Request.Body.DisableSD -RawJSON $Request.Body.RawJSON -APIName $APIName -Headers $Headers "$CAPolicy" } catch { "$($_.Exception.Message)" diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 4ef1a08e7278..a6c6ede0714d 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -231,6 +231,7 @@ function New-CIPPCAPolicy { $body = '{ "isEnabled": false }' $null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -Type patch -Body $body -ContentType 'application/json' Write-LogMessage -Headers $User -API $APINAME -tenant $($Tenant) -message "Disabled Security Defaults for tenant $($TenantFilter)" -Sev 'Info' + Start-Sleep 3 } $RawJSON = ConvertTo-Json -InputObject $JSONObj -Depth 10 -Compress Write-Information $RawJSON From aa4614300f0510524b1de8ace1f15961ba90591a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 22:44:14 +0200 Subject: [PATCH 100/125] new allignment score api --- .../Standards/Invoke-ListTenantAlignment.ps1 | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 new file mode 100644 index 000000000000..984b69ebbe87 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 @@ -0,0 +1,275 @@ +using namespace System.Net + +function Invoke-ListTenantAlignment { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Tenant.Standards.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + $Headers = $Request.Headers + + # Get all standard templates + $TemplateTable = Get-CippTable -tablename 'templates' + $TemplateFilter = "PartitionKey eq 'StandardsTemplateV2'" + $Templates = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $TemplateFilter) | ForEach-Object { + $JSON = $_.JSON -replace '"Action":', '"action":' + try { + $RowKey = $_.RowKey + $Data = $JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue + } catch { + Write-Host "$($RowKey) standard could not be loaded: $($_.Exception.Message)" + return + } + if ($Data) { + $Data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force + $Data + } + } + + # Get standards comparison data using the same pattern as ListStandardsCompare + $StandardsTable = Get-CIPPTable -TableName 'CippStandardsReports' + $Standards = Get-CIPPAzDataTableEntity @StandardsTable + + # Build tenant standards data structure like in ListStandardsCompare + $TenantStandards = @{} + foreach ($Standard in $Standards) { + $FieldName = $Standard.RowKey + $FieldValue = $Standard.Value + $Tenant = $Standard.PartitionKey + + # Process field value like in ListStandardsCompare + if ($FieldValue -is [System.Boolean]) { + $FieldValue = [bool]$FieldValue + } elseif ($FieldValue -like '*{*') { + $FieldValue = ConvertFrom-Json -InputObject $FieldValue -ErrorAction SilentlyContinue + } else { + $FieldValue = [string]$FieldValue + } + + if (-not $TenantStandards.ContainsKey($Tenant)) { + $TenantStandards[$Tenant] = @{} + } + $TenantStandards[$Tenant][$FieldName] = @{ + Value = $FieldValue + LastRefresh = $Standard.TimeStamp.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } + } + + $Results = [System.Collections.Generic.List[object]]::new() + + # Process each template against all tenants + foreach ($Template in $Templates) { + $TemplateStandards = $Template.standards + if (-not $TemplateStandards) { + continue + } + + # Check if template has tenant assignments (scope) + $TemplateAssignedTenants = @() + $AppliestoAllTenants = $false + + if ($Template.tenantFilter -and $Template.tenantFilter.Count -gt 0) { + # Extract tenant values from the tenantFilter array + $TenantValues = $Template.tenantFilter | ForEach-Object { $_.value } + + if ($TenantValues -contains "AllTenants") { + $AppliestoAllTenants = $true + Write-Host "Template '$($Template.templateName)' applies to all tenants (AllTenants)" + } else { + $TemplateAssignedTenants = $TenantValues + Write-Host "Template '$($Template.templateName)' is assigned to specific tenants: $($TemplateAssignedTenants -join ', ')" + } + } else { + $AppliestoAllTenants = $true + Write-Host "Template '$($Template.templateName)' applies to all tenants (no tenantFilter)" + } + + # Track all standards and their reporting status like the frontend does + $AllStandards = [System.Collections.Generic.List[string]]::new() + $ReportingEnabledStandards = [System.Collections.Generic.List[string]]::new() + $ReportingDisabledStandards = [System.Collections.Generic.List[string]]::new() + + foreach ($StandardKey in $TemplateStandards.PSObject.Properties.Name) { + $StandardConfig = $TemplateStandards.$StandardKey + $StandardId = "standards.$StandardKey" + + # Check if reporting is enabled for this standard (same logic as frontend) + # Try multiple possible action property locations + $Actions = @() + if ($StandardConfig.action) { + $Actions = $StandardConfig.action + } elseif ($StandardConfig.Action) { + $Actions = $StandardConfig.Action + } elseif ($StandardConfig.PSObject.Properties['action']) { + $Actions = $StandardConfig.PSObject.Properties['action'].Value + } + + # Frontend logic: actions.filter(action => action?.value.toLowerCase() === "report" || action?.value.toLowerCase() === "remediate").length > 0 + $ReportingEnabled = $false + if ($Actions -and $Actions.Count -gt 0) { + $ReportingEnabled = ($Actions | Where-Object { $_.value -and ($_.value.ToLower() -eq "report" -or $_.value.ToLower() -eq "remediate") }).Count -gt 0 + } + + # Add to all standards list + $AllStandards.Add($StandardId) + + if ($ReportingEnabled) { + $ReportingEnabledStandards.Add($StandardId) + } else { + $ReportingDisabledStandards.Add($StandardId) + } + + # Handle IntuneTemplate arrays - don't count the base IntuneTemplate, only the specific instances + if ($StandardKey -eq 'IntuneTemplate' -and $StandardConfig -is [array]) { + # Remove the base IntuneTemplate standard since we'll add specific instances + $AllStandards.Remove($StandardId) + if ($ReportingEnabled) { + $ReportingEnabledStandards.Remove($StandardId) + } else { + $ReportingDisabledStandards.Remove($StandardId) + } + + foreach ($IntuneTemplate in $StandardConfig) { + if ($IntuneTemplate.TemplateList.value) { + $IntuneStandardId = "standards.IntuneTemplate.$($IntuneTemplate.TemplateList.value)" + + # Check if reporting is enabled for this Intune template + $IntuneActions = if ($IntuneTemplate.action) { $IntuneTemplate.action } else { @() } + Write-Host " Intune template $IntuneStandardId actions: $($IntuneActions | ForEach-Object { $_.value } | Join-String -Separator ', ')" + $IntuneReportingEnabled = ($IntuneActions | Where-Object { $_.value -and ($_.value.ToLower() -eq "report" -or $_.value.ToLower() -eq "remediate") }).Count -gt 0 + Write-Host " Intune template $IntuneStandardId reporting enabled: $IntuneReportingEnabled" + + # Add to all standards list + $AllStandards.Add($IntuneStandardId) + + if ($IntuneReportingEnabled) { + $ReportingEnabledStandards.Add($IntuneStandardId) + Write-Host " Added $IntuneStandardId to reporting enabled" + } else { + $ReportingDisabledStandards.Add($IntuneStandardId) + Write-Host " Added $IntuneStandardId to reporting disabled" + } + } + } + } + } + + # Process each tenant against this template (but only if template applies to this tenant) + foreach ($TenantName in $TenantStandards.Keys) { + # Skip this tenant if template is assigned to specific tenants and this tenant is not in the list + if (-not $AppliestoAllTenants -and $TenantName -notin $TemplateAssignedTenants) { + Write-Host "Skipping tenant '$TenantName' for template '$($Template.templateName)' - not in assigned tenant list" + continue + } + $AllCount = $AllStandards.Count + + # Check compliance for ALL standards (both reporting enabled and disabled) + # But track them separately like the frontend does + $CompliantStandards = 0 + $NonCompliantStandards = 0 + $ReportingDisabledStandardsCount = 0 + $LatestDataCollection = $null + + # Create a table to compare with frontend + $ComparisonTable = @() + + foreach ($StandardKey in $AllStandards) { + $IsReportingDisabled = $ReportingDisabledStandards -contains $StandardKey + + if ($TenantStandards[$TenantName].ContainsKey($StandardKey)) { + $StandardObject = $TenantStandards[$TenantName][$StandardKey] + $Value = $StandardObject.Value + + # Track the latest data collection timestamp + if ($StandardObject.LastRefresh) { + $RefreshTime = [DateTime]::Parse($StandardObject.LastRefresh) + if (-not $LatestDataCollection -or $RefreshTime -gt $LatestDataCollection) { + $LatestDataCollection = $RefreshTime + } + } + + # Use strict compliance logic - only explicit TRUE is compliant + $IsCompliant = ($Value -eq $true) + + # Count based on reporting status like the frontend + if ($IsReportingDisabled) { + $ReportingDisabledStandardsCount++ + $ComplianceStatus = "Reporting Disabled" + } elseif ($IsCompliant) { + $CompliantStandards++ + $ComplianceStatus = "Compliant" + } else { + $NonCompliantStandards++ + $ComplianceStatus = "Non-Compliant" + } + + $ComparisonTable += [PSCustomObject]@{ + StandardName = $StandardKey + Compliant = $IsCompliant + StandardValue = ($Value | ConvertTo-Json -Compress) + ComplianceStatus = $ComplianceStatus + ReportingDisabled = $IsReportingDisabled + } + } else { + # If standard not found, count as non-compliant if reporting enabled, or reporting disabled if reporting disabled + if ($IsReportingDisabled) { + $ReportingDisabledStandardsCount++ + $ComplianceStatus = "Reporting Disabled" + } else { + $NonCompliantStandards++ + $ComplianceStatus = "Non-Compliant" + } + + $ComparisonTable += [PSCustomObject]@{ + StandardName = $StandardKey + Compliant = $false + StandardValue = "NOT FOUND" + ComplianceStatus = $ComplianceStatus + ReportingDisabled = $IsReportingDisabled + } + } + } + + # Calculate percentage using the exact same formula as the frontend + # Frontend: Math.round((compliantCount / (allCount - reportingDisabledCount || 1)) * 100) + $AlignmentPercentage = if (($AllCount - $ReportingDisabledStandardsCount) -gt 0) { + [Math]::Round(($CompliantStandards / ($AllCount - $ReportingDisabledStandardsCount)) * 100) + } else { + 0 + } + + # Output comparison table for debugging + Write-Host "=== TENANT: $TenantName | TEMPLATE: $($Template.templateName) ===" + Write-Host "TEMPLATE STANDARDS FOUND: $($AllStandards -join ', ')" + Write-Host "TENANT STANDARDS AVAILABLE: $($TenantStandards[$TenantName].Keys | Sort-Object | Join-String -Separator ', ')" + + # Check for tenant standards that might be missing from template + $TenantOnlyStandards = $TenantStandards[$TenantName].Keys | Where-Object { $_ -notin $AllStandards } + Write-Host "TENANT-ONLY STANDARDS (not in template): $($TenantOnlyStandards -join ', ')" + + Write-Host "CALCULATION: $CompliantStandards compliant / ($AllCount total - $ReportingDisabledStandardsCount reporting disabled) = $AlignmentPercentage%" + Write-Host "" + Write-Host ($ComparisonTable | Format-Table -Property StandardName, Compliant, ComplianceStatus, ReportingDisabled, StandardValue -AutoSize | Out-String) + + $Result = [PSCustomObject]@{ + tenantFilter = $TenantName + standardName = $Template.templateName + standardId = $Template.GUID + alignmentScore = $AlignmentPercentage + latestDataCollection = if ($LatestDataCollection) { $LatestDataCollection.ToString('yyyy-MM-ddTHH:mm:ssZ') } else { $null } + } + + $Results.Add($Result) + } + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($Results) + }) +} From 6b0de81cbcdba39260f373f15142de0fb3223e87 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:25:48 +0200 Subject: [PATCH 101/125] datetime fix --- .../Standards/Invoke-ListTenantAlignment.ps1 | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 index 984b69ebbe87..cce6c491f57b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 @@ -77,7 +77,7 @@ function Invoke-ListTenantAlignment { # Extract tenant values from the tenantFilter array $TenantValues = $Template.tenantFilter | ForEach-Object { $_.value } - if ($TenantValues -contains "AllTenants") { + if ($TenantValues -contains 'AllTenants') { $AppliestoAllTenants = $true Write-Host "Template '$($Template.templateName)' applies to all tenants (AllTenants)" } else { @@ -112,7 +112,7 @@ function Invoke-ListTenantAlignment { # Frontend logic: actions.filter(action => action?.value.toLowerCase() === "report" || action?.value.toLowerCase() === "remediate").length > 0 $ReportingEnabled = $false if ($Actions -and $Actions.Count -gt 0) { - $ReportingEnabled = ($Actions | Where-Object { $_.value -and ($_.value.ToLower() -eq "report" -or $_.value.ToLower() -eq "remediate") }).Count -gt 0 + $ReportingEnabled = ($Actions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 } # Add to all standards list @@ -141,7 +141,7 @@ function Invoke-ListTenantAlignment { # Check if reporting is enabled for this Intune template $IntuneActions = if ($IntuneTemplate.action) { $IntuneTemplate.action } else { @() } Write-Host " Intune template $IntuneStandardId actions: $($IntuneActions | ForEach-Object { $_.value } | Join-String -Separator ', ')" - $IntuneReportingEnabled = ($IntuneActions | Where-Object { $_.value -and ($_.value.ToLower() -eq "report" -or $_.value.ToLower() -eq "remediate") }).Count -gt 0 + $IntuneReportingEnabled = ($IntuneActions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 Write-Host " Intune template $IntuneStandardId reporting enabled: $IntuneReportingEnabled" # Add to all standards list @@ -199,37 +199,37 @@ function Invoke-ListTenantAlignment { # Count based on reporting status like the frontend if ($IsReportingDisabled) { $ReportingDisabledStandardsCount++ - $ComplianceStatus = "Reporting Disabled" + $ComplianceStatus = 'Reporting Disabled' } elseif ($IsCompliant) { $CompliantStandards++ - $ComplianceStatus = "Compliant" + $ComplianceStatus = 'Compliant' } else { $NonCompliantStandards++ - $ComplianceStatus = "Non-Compliant" + $ComplianceStatus = 'Non-Compliant' } $ComparisonTable += [PSCustomObject]@{ - StandardName = $StandardKey - Compliant = $IsCompliant - StandardValue = ($Value | ConvertTo-Json -Compress) - ComplianceStatus = $ComplianceStatus + StandardName = $StandardKey + Compliant = $IsCompliant + StandardValue = ($Value | ConvertTo-Json -Compress) + ComplianceStatus = $ComplianceStatus ReportingDisabled = $IsReportingDisabled } } else { # If standard not found, count as non-compliant if reporting enabled, or reporting disabled if reporting disabled if ($IsReportingDisabled) { $ReportingDisabledStandardsCount++ - $ComplianceStatus = "Reporting Disabled" + $ComplianceStatus = 'Reporting Disabled' } else { $NonCompliantStandards++ - $ComplianceStatus = "Non-Compliant" + $ComplianceStatus = 'Non-Compliant' } $ComparisonTable += [PSCustomObject]@{ - StandardName = $StandardKey - Compliant = $false - StandardValue = "NOT FOUND" - ComplianceStatus = $ComplianceStatus + StandardName = $StandardKey + Compliant = $false + StandardValue = 'NOT FOUND' + ComplianceStatus = $ComplianceStatus ReportingDisabled = $IsReportingDisabled } } @@ -253,15 +253,15 @@ function Invoke-ListTenantAlignment { Write-Host "TENANT-ONLY STANDARDS (not in template): $($TenantOnlyStandards -join ', ')" Write-Host "CALCULATION: $CompliantStandards compliant / ($AllCount total - $ReportingDisabledStandardsCount reporting disabled) = $AlignmentPercentage%" - Write-Host "" + Write-Host '' Write-Host ($ComparisonTable | Format-Table -Property StandardName, Compliant, ComplianceStatus, ReportingDisabled, StandardValue -AutoSize | Out-String) $Result = [PSCustomObject]@{ - tenantFilter = $TenantName - standardName = $Template.templateName - standardId = $Template.GUID - alignmentScore = $AlignmentPercentage - latestDataCollection = if ($LatestDataCollection) { $LatestDataCollection.ToString('yyyy-MM-ddTHH:mm:ssZ') } else { $null } + tenantFilter = $TenantName + standardName = $Template.templateName + standardId = $Template.GUID + alignmentScore = $AlignmentPercentage + latestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null } } $Results.Add($Result) From d7914ea2de84333ca8c7c252c71beb350b978ba5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 00:57:17 +0200 Subject: [PATCH 102/125] updated standard --- ...voke-CIPPStandardintuneDeviceRetirementDays.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 index 07d19f264bdf..77e6e34180f5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 @@ -32,17 +32,23 @@ function Invoke-CIPPStandardintuneDeviceRetirementDays { param($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'intuneDeviceRetirementDays' - $CurrentInfo = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/managedDeviceCleanupSettings' -tenantid $Tenant) + $CurrentInfo = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/managedDeviceCleanupRules' -tenantid $Tenant) $StateIsCorrect = if ($CurrentInfo.DeviceInactivityBeforeRetirementInDays -eq $Settings.days) { $true } else { $false } - If ($Settings.remediate -eq $true) { + if ($Settings.remediate -eq $true) { if ($CurrentInfo.DeviceInactivityBeforeRetirementInDays -eq $Settings.days) { Write-LogMessage -API 'Standards' -tenant $tenant -message "DeviceInactivityBeforeRetirementInDays for $($Settings.days) days is already enabled." -sev Info } else { + if ($CurrentInfo) { + $Type = 'Patch' + $id = "('$($CurrentInfo.id)')" + } else { + $Type = 'Post' + } try { - $body = @{ DeviceInactivityBeforeRetirementInDays = $Settings.days } | ConvertTo-Json - (New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/deviceManagement/managedDeviceCleanupSettings' -Type PATCH -Body $body -ContentType 'application/json') + $body = '{"displayName":"Default Policy","description":"Default Policy","deviceCleanupRulePlatformType":"all","deviceInactivityBeforeRetirementInDays":' + $Settings.days + '}' + (New-GraphPostRequest -tenantid $tenant -Uri "https://graph.microsoft.com/beta/deviceManagement/managedDeviceCleanupRules$id" -Type $Type -Body $body -ContentType 'application/json') Write-LogMessage -API 'Standards' -tenant $tenant -message "Enabled DeviceInactivityBeforeRetirementInDays for $($Settings.days) days." -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message From fe773bef01050aed31ae60cb53cfff9026c009d4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 01:42:17 +0200 Subject: [PATCH 103/125] Revert as reporting no longer worked correctly. --- ...oke-CIPPStandardSendReceiveLimitTenant.ps1 | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 index 333788bf51e0..344f83bc00bc 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 @@ -37,31 +37,21 @@ function Invoke-CIPPStandardSendReceiveLimitTenant { return } + # Input validation if ([Int32]$Settings.ReceiveLimit -lt 1 -or [Int32]$Settings.ReceiveLimit -gt 150) { Write-LogMessage -API 'Standards' -tenant $tenant -message 'SendReceiveLimitTenant: Invalid ReceiveLimit parameter set' -sev Error return } + $AllMailBoxPlans = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MailboxPlan' | Select-Object DisplayName, MaxSendSize, MaxReceiveSize, GUID - $MaxSendSize = "$($Settings.SendLimit)MB" - $MaxReceiveSize = "$($Settings.ReceiveLimit)MB" - $MaxSendSizeBytes = $Settings.SendLimit * 1MB - $MaxReceiveSizeBytes = $Settings.ReceiveLimit * 1MB + $MaxSendSize = [int64]"$($Settings.SendLimit)MB" + $MaxReceiveSize = [int64]"$($Settings.ReceiveLimit)MB" $NotSetCorrectly = foreach ($MailboxPlan in $AllMailBoxPlans) { - if ($MailboxPlan.MaxSendSize -eq 'Unlimited') { - $PlanMaxSendSize = [int64]::MaxValue - } else { - $PlanMaxSendSize = [int64]($MailboxPlan.MaxSendSize -replace '.*\(([\d,]+).*', '$1' -replace ',', '') - } - - if ($MailboxPlan.MaxReceiveSize -eq 'Unlimited') { - $PlanMaxReceiveSize = [int64]::MaxValue - } else { - $PlanMaxReceiveSize = [int64]($MailboxPlan.MaxReceiveSize -replace '.*\(([\d,]+).*', '$1' -replace ',', '') - } - - if ($PlanMaxSendSize -ne $MaxSendSizeBytes -or $PlanMaxReceiveSize -ne $MaxReceiveSizeBytes) { + $PlanMaxSendSize = [int64]($MailboxPlan.MaxSendSize -replace '.*\(([\d,]+).*', '$1' -replace ',', '') + $PlanMaxReceiveSize = [int64]($MailboxPlan.MaxReceiveSize -replace '.*\(([\d,]+).*', '$1' -replace ',', '') + if ($PlanMaxSendSize -ne $MaxSendSize -or $PlanMaxReceiveSize -ne $MaxReceiveSize) { $MailboxPlan } } @@ -86,6 +76,7 @@ function Invoke-CIPPStandardSendReceiveLimitTenant { } if ($Settings.alert -eq $true) { + if ($NotSetCorrectly.Count -eq 0) { Write-LogMessage -API 'Standards' -tenant $tenant -message "The tenant send($($Settings.SendLimit)MB) and receive($($Settings.ReceiveLimit)MB) limits are set correctly" -sev Info } else { From 5702b8dc1c58cb7fa51f580d61a020a84ec4bcf4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 02:29:04 +0200 Subject: [PATCH 104/125] fix write hosts --- .../Standards/Invoke-ListTenantAlignment.ps1 | 38 +------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 index cce6c491f57b..b563b2229bc4 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 @@ -89,7 +89,6 @@ function Invoke-ListTenantAlignment { Write-Host "Template '$($Template.templateName)' applies to all tenants (no tenantFilter)" } - # Track all standards and their reporting status like the frontend does $AllStandards = [System.Collections.Generic.List[string]]::new() $ReportingEnabledStandards = [System.Collections.Generic.List[string]]::new() $ReportingDisabledStandards = [System.Collections.Generic.List[string]]::new() @@ -98,8 +97,7 @@ function Invoke-ListTenantAlignment { $StandardConfig = $TemplateStandards.$StandardKey $StandardId = "standards.$StandardKey" - # Check if reporting is enabled for this standard (same logic as frontend) - # Try multiple possible action property locations + $Actions = @() if ($StandardConfig.action) { $Actions = $StandardConfig.action @@ -109,13 +107,11 @@ function Invoke-ListTenantAlignment { $Actions = $StandardConfig.PSObject.Properties['action'].Value } - # Frontend logic: actions.filter(action => action?.value.toLowerCase() === "report" || action?.value.toLowerCase() === "remediate").length > 0 $ReportingEnabled = $false if ($Actions -and $Actions.Count -gt 0) { $ReportingEnabled = ($Actions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 } - # Add to all standards list $AllStandards.Add($StandardId) if ($ReportingEnabled) { @@ -124,9 +120,7 @@ function Invoke-ListTenantAlignment { $ReportingDisabledStandards.Add($StandardId) } - # Handle IntuneTemplate arrays - don't count the base IntuneTemplate, only the specific instances if ($StandardKey -eq 'IntuneTemplate' -and $StandardConfig -is [array]) { - # Remove the base IntuneTemplate standard since we'll add specific instances $AllStandards.Remove($StandardId) if ($ReportingEnabled) { $ReportingEnabledStandards.Remove($StandardId) @@ -138,44 +132,32 @@ function Invoke-ListTenantAlignment { if ($IntuneTemplate.TemplateList.value) { $IntuneStandardId = "standards.IntuneTemplate.$($IntuneTemplate.TemplateList.value)" - # Check if reporting is enabled for this Intune template $IntuneActions = if ($IntuneTemplate.action) { $IntuneTemplate.action } else { @() } - Write-Host " Intune template $IntuneStandardId actions: $($IntuneActions | ForEach-Object { $_.value } | Join-String -Separator ', ')" $IntuneReportingEnabled = ($IntuneActions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 - Write-Host " Intune template $IntuneStandardId reporting enabled: $IntuneReportingEnabled" - # Add to all standards list $AllStandards.Add($IntuneStandardId) if ($IntuneReportingEnabled) { $ReportingEnabledStandards.Add($IntuneStandardId) - Write-Host " Added $IntuneStandardId to reporting enabled" } else { $ReportingDisabledStandards.Add($IntuneStandardId) - Write-Host " Added $IntuneStandardId to reporting disabled" } } } } } - # Process each tenant against this template (but only if template applies to this tenant) foreach ($TenantName in $TenantStandards.Keys) { - # Skip this tenant if template is assigned to specific tenants and this tenant is not in the list if (-not $AppliestoAllTenants -and $TenantName -notin $TemplateAssignedTenants) { Write-Host "Skipping tenant '$TenantName' for template '$($Template.templateName)' - not in assigned tenant list" continue } $AllCount = $AllStandards.Count - # Check compliance for ALL standards (both reporting enabled and disabled) - # But track them separately like the frontend does $CompliantStandards = 0 $NonCompliantStandards = 0 $ReportingDisabledStandardsCount = 0 $LatestDataCollection = $null - - # Create a table to compare with frontend $ComparisonTable = @() foreach ($StandardKey in $AllStandards) { @@ -185,7 +167,6 @@ function Invoke-ListTenantAlignment { $StandardObject = $TenantStandards[$TenantName][$StandardKey] $Value = $StandardObject.Value - # Track the latest data collection timestamp if ($StandardObject.LastRefresh) { $RefreshTime = [DateTime]::Parse($StandardObject.LastRefresh) if (-not $LatestDataCollection -or $RefreshTime -gt $LatestDataCollection) { @@ -193,10 +174,8 @@ function Invoke-ListTenantAlignment { } } - # Use strict compliance logic - only explicit TRUE is compliant $IsCompliant = ($Value -eq $true) - # Count based on reporting status like the frontend if ($IsReportingDisabled) { $ReportingDisabledStandardsCount++ $ComplianceStatus = 'Reporting Disabled' @@ -216,7 +195,6 @@ function Invoke-ListTenantAlignment { ReportingDisabled = $IsReportingDisabled } } else { - # If standard not found, count as non-compliant if reporting enabled, or reporting disabled if reporting disabled if ($IsReportingDisabled) { $ReportingDisabledStandardsCount++ $ComplianceStatus = 'Reporting Disabled' @@ -235,26 +213,14 @@ function Invoke-ListTenantAlignment { } } - # Calculate percentage using the exact same formula as the frontend - # Frontend: Math.round((compliantCount / (allCount - reportingDisabledCount || 1)) * 100) + $AlignmentPercentage = if (($AllCount - $ReportingDisabledStandardsCount) -gt 0) { [Math]::Round(($CompliantStandards / ($AllCount - $ReportingDisabledStandardsCount)) * 100) } else { 0 } - # Output comparison table for debugging - Write-Host "=== TENANT: $TenantName | TEMPLATE: $($Template.templateName) ===" - Write-Host "TEMPLATE STANDARDS FOUND: $($AllStandards -join ', ')" - Write-Host "TENANT STANDARDS AVAILABLE: $($TenantStandards[$TenantName].Keys | Sort-Object | Join-String -Separator ', ')" - - # Check for tenant standards that might be missing from template $TenantOnlyStandards = $TenantStandards[$TenantName].Keys | Where-Object { $_ -notin $AllStandards } - Write-Host "TENANT-ONLY STANDARDS (not in template): $($TenantOnlyStandards -join ', ')" - - Write-Host "CALCULATION: $CompliantStandards compliant / ($AllCount total - $ReportingDisabledStandardsCount reporting disabled) = $AlignmentPercentage%" - Write-Host '' - Write-Host ($ComparisonTable | Format-Table -Property StandardName, Compliant, ComplianceStatus, ReportingDisabled, StandardValue -AutoSize | Out-String) $Result = [PSCustomObject]@{ tenantFilter = $TenantName From ef68f5447a73d62750527bdd1bb9251c535298ac Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:30:52 +0200 Subject: [PATCH 105/125] Fixes report --- .../Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 index bc8cbd0efb89..b201ce5477fd 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 @@ -73,9 +73,8 @@ function Invoke-CIPPStandardTeamsMeetingRecordingExpiration { if ($Settings.report -eq $true) { Add-CIPPBPAField -FieldName 'TeamsMeetingRecordingExpiration' -FieldValue $CurrentExpirationDays -StoreAs string -Tenant $Tenant - $CurrentExpirationDays = [PSCustomObject]@{ - ExpirationDays = [string]$CurrentExpirationDays - } + $CurrentExpirationDays = if ($StateIsCorrect) { $true } else { $CurrentExpirationDays } + Set-CIPPStandardsCompareField -FieldName 'standards.TeamsMeetingRecordingExpiration' -FieldValue $CurrentExpirationDays -Tenant $Tenant } } From 5306ca19dce3054d7b02d8ed20aaf78da2b3b288 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 17:36:59 +0200 Subject: [PATCH 106/125] added report --- .../Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 index 00c5f576900d..748a62f8ce9c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 @@ -62,4 +62,9 @@ function Invoke-CIPPStandardSafeSendersDisable { } } + if ($Settings.report -eq $true) { + #This script always returns true, as it only disables the Safe Senders list + Set-CIPPStandardsCompareField -FieldName 'standards.SafeSendersDisable' -FieldValue $true -Tenant $Tenant + } + } From 5db6d404aa3cfca0e58b729a3f8eb709d22c96a4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 17:57:10 +0200 Subject: [PATCH 107/125] fix state --- .../Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 index 194596088dfe..e27cfd2b32ee 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 @@ -32,7 +32,7 @@ function Invoke-CIPPStandardCloudMessageRecall { ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'CloudMessageRecall' # Get state value using null-coalescing operator - $state = $Settings.state.value ?? $Settings.state + $state = $Settings.state.value $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').MessageRecallEnabled $WantedState = if ($state -eq 'true') { $true } else { $false } @@ -41,14 +41,14 @@ function Invoke-CIPPStandardCloudMessageRecall { if ($Settings.report -eq $true) { # Default is not set, not set means it's enabled if ($null -eq $CurrentState ) { $CurrentState = $true } - Set-CIPPStandardsCompareField -FieldName 'standards.CloudMessageRecall' -FieldValue $CurrentState -TenantFilter $Tenant + Set-CIPPStandardsCompareField -FieldName 'standards.CloudMessageRecall' -FieldValue $StateIsCorrect -TenantFilter $Tenant Add-CIPPBPAField -FieldName 'MessageRecall' -FieldValue $CurrentState -StoreAs bool -Tenant $Tenant } # Input validation if (([string]::IsNullOrWhiteSpace($state) -or $state -eq 'Select a value') -and ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { Write-LogMessage -API 'Standards' -tenant $Tenant -message 'MessageRecallEnabled: Invalid state parameter set' -sev Error - Return + return } if ($Settings.remediate -eq $true) { From 67ff06fd44696bc8c3111cd57fc43c8242f8811c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:03:28 +0200 Subject: [PATCH 108/125] fix reporting --- ...oke-CIPPStandardMailboxRecipientLimits.ps1 | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 index 6f57d05f687c..5b2dca61d818 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 @@ -69,8 +69,12 @@ function Invoke-CIPPStandardMailboxRecipientLimits { if ($null -ne $Mailboxes -and @($Mailboxes).Count -gt 0) { # Process mailboxes and categorize them based on their plan limits $MailboxResults = @($Mailboxes) | ForEach-Object { - $Mailbox = $_ + $Mailbox = $_ + #if the mailbox username contains a guid, we can assume it's a system mailbox and skip it + if ($Mailbox.UserPrincipalName -match '^[^@]+@[^.]+\.[^.]+$') { + return + } # Safe hashtable lookup - check if MailboxPlanId exists and is not null $Plan = $null if ($Mailbox.MailboxPlanId -and $MailboxPlanLookup.ContainsKey($Mailbox.MailboxPlanId)) { @@ -83,8 +87,7 @@ function Invoke-CIPPStandardMailboxRecipientLimits { # If mailbox has "Unlimited" set but has a plan, use the plan's limit as the current limit $CurrentLimit = if ($Mailbox.RecipientLimits -eq 'Unlimited') { $PlanMaxRecipients - } - else { + } else { $Mailbox.RecipientLimits } @@ -96,15 +99,13 @@ function Invoke-CIPPStandardMailboxRecipientLimits { PlanLimit = $PlanMaxRecipients PlanName = $Plan.DisplayName } - } - elseif ($CurrentLimit -ne $Settings.RecipientLimit) { + } elseif ($CurrentLimit -ne $Settings.RecipientLimit) { [PSCustomObject]@{ Type = 'ToUpdate' Mailbox = $Mailbox } } - } - elseif ($Mailbox.RecipientLimits -ne $Settings.RecipientLimit) { + } elseif ($Mailbox.RecipientLimits -ne $Settings.RecipientLimit) { [PSCustomObject]@{ Type = 'ToUpdate' Mailbox = $Mailbox @@ -139,11 +140,11 @@ function Invoke-CIPPStandardMailboxRecipientLimits { $MailboxChanges = $MailboxesToUpdate | ForEach-Object { $CurrentLimit = if ($_.RecipientLimits -eq 'Unlimited') { 'Unlimited' } else { $_.RecipientLimits } @{ - Identity = $_.Identity - DisplayName = $_.DisplayName + Identity = $_.Identity + DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress - CurrentLimit = $CurrentLimit - NewLimit = $Settings.RecipientLimit + CurrentLimit = $CurrentLimit + NewLimit = $Settings.RecipientLimit } } @@ -165,13 +166,11 @@ function Invoke-CIPPStandardMailboxRecipientLimits { # Execute batch update $null = New-ExoBulkRequest -tenantid $Tenant -cmdletArray $UpdateRequests Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully applied recipient limits to $($MailboxesToUpdate.Count) mailboxes" -sev Info - } - catch { + } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set recipient limits. $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage } - } - else { + } else { Write-LogMessage -API 'Standards' -tenant $Tenant -message "All mailboxes already have the correct recipient limit of $($Settings.RecipientLimit)" -sev Info } } @@ -180,12 +179,11 @@ function Invoke-CIPPStandardMailboxRecipientLimits { if ($Settings.alert -eq $true) { if ($MailboxesToUpdate.Count -eq 0 -and $MailboxesWithPlanIssues.Count -eq 0) { Write-LogMessage -API 'Standards' -tenant $Tenant -message "All mailboxes have the correct recipient limit of $($Settings.RecipientLimit)" -sev Info - } - else { + } else { # Create structured alert data $AlertData = @{ - RequestedLimit = $Settings.RecipientLimit - MailboxesToUpdate = @() + RequestedLimit = $Settings.RecipientLimit + MailboxesToUpdate = @() MailboxesWithPlanIssues = @() } @@ -197,11 +195,11 @@ function Invoke-CIPPStandardMailboxRecipientLimits { $AlertData.MailboxesToUpdate = $MailboxesToUpdate | ForEach-Object { $CurrentLimit = if ($_.RecipientLimits -eq 'Unlimited') { 'Unlimited' } else { $_.RecipientLimits } @{ - Identity = $_.Identity - DisplayName = $_.DisplayName + Identity = $_.Identity + DisplayName = $_.DisplayName PrimarySmtpAddress = $_.PrimarySmtpAddress - CurrentLimit = $CurrentLimit - RequiredLimit = $Settings.RecipientLimit + CurrentLimit = $CurrentLimit + RequiredLimit = $Settings.RecipientLimit } } # Add to alert objects list efficiently @@ -214,10 +212,10 @@ function Invoke-CIPPStandardMailboxRecipientLimits { if ($MailboxesWithPlanIssues.Count -gt 0) { $AlertData.MailboxesWithPlanIssues = $MailboxesWithPlanIssues | ForEach-Object { @{ - Identity = $_.Identity - CurrentLimit = $_.CurrentLimit - PlanLimit = $_.PlanLimit - PlanName = $_.PlanName + Identity = $_.Identity + CurrentLimit = $_.CurrentLimit + PlanLimit = $_.PlanLimit + PlanName = $_.PlanName RequestedLimit = $Settings.RecipientLimit } } @@ -249,8 +247,7 @@ function Invoke-CIPPStandardMailboxRecipientLimits { if ($MailboxesToUpdate.Count -eq 0 -and $MailboxesWithPlanIssues.Count -eq 0) { $FieldValue = $true - } - else { + } else { $FieldValue = $ReportData } Set-CIPPStandardsCompareField -FieldName 'standards.MailboxRecipientLimits' -FieldValue $FieldValue -Tenant $Tenant From 7eba580538987c3256071a76dab3d4bf0080457a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:04:43 +0200 Subject: [PATCH 109/125] done --- .../Invoke-CIPPStandardcalDefault.ps1 | 76 ++++++++++--------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 index 11f3f95605db..bce7cc1b2fe6 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 @@ -39,10 +39,10 @@ function Invoke-CIPPStandardcalDefault { # Input validation if ([string]::IsNullOrWhiteSpace($permissionLevel) -or $permissionLevel -eq 'Select a value') { Write-LogMessage -API 'Standards' -tenant $tenant -message 'calDefault: Invalid permissionLevel parameter set' -sev Error - Return + return } - If ($Settings.remediate -eq $true) { + if ($Settings.remediate -eq $true) { $Mailboxes = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' | Sort-Object UserPrincipalName $TotalMailboxes = $Mailboxes.Count Write-LogMessage -API 'Standards' -tenant $Tenant -message "Started setting default calendar permissions for $($TotalMailboxes) mailboxes." -sev Info @@ -67,44 +67,48 @@ function Invoke-CIPPStandardcalDefault { $Mailbox = $_ try { New-ExoRequest -tenantid $Tenant -cmdlet 'Get-MailboxFolderStatistics' -cmdParams @{identity = $Mailbox.UserPrincipalName; FolderScope = 'Calendar' } -Anchor $Mailbox.UserPrincipalName | Where-Object { $_.FolderType -eq 'Calendar' } | - ForEach-Object { - try { - New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MailboxFolderPermission' -cmdParams @{Identity = "$($Mailbox.UserPrincipalName):$($_.FolderId)"; User = 'Default'; AccessRights = $permissionLevel } -Anchor $Mailbox.UserPrincipalName - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set default folder permission for $($Mailbox.UserPrincipalName):\$($_.Name) to $permissionLevel" -sev Debug - $SuccessCounter++ - } catch { - $ErrorMessage = Get-CippException -Exception $_ - Write-Host "Setting cal failed: $ErrorMessage" - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage - } + ForEach-Object { + try { + New-ExoRequest -tenantid $Tenant -cmdlet 'Set-MailboxFolderPermission' -cmdParams @{Identity = "$($Mailbox.UserPrincipalName):$($_.FolderId)"; User = 'Default'; AccessRights = $permissionLevel } -Anchor $Mailbox.UserPrincipalName + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Set default folder permission for $($Mailbox.UserPrincipalName):\$($_.Name) to $permissionLevel" -sev Debug + $SuccessCounter++ + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-Host "Setting cal failed: $ErrorMessage" + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage } - } catch { - $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage - } - $processedMailboxes++ - if ($processedMailboxes % 25 -eq 0) { - $LastRun = @{ - RowKey = 'calDefaults' - PartitionKey = $Tenant - totalMailboxes = $TotalMailboxes - processedMailboxes = $processedMailboxes - currentSuccessCount = $SuccessCounter - } - Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force - Write-Host "Processed $processedMailboxes mailboxes" } + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not set default calendar permissions for $($Mailbox.UserPrincipalName). Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage } - - $LastRun = @{ - RowKey = 'calDefaults' - PartitionKey = $Tenant - totalMailboxes = $TotalMailboxes - processedMailboxes = $processedMailboxes - currentSuccessCount = $SuccessCounter + $processedMailboxes++ + if ($processedMailboxes % 25 -eq 0) { + $LastRun = @{ + RowKey = 'calDefaults' + PartitionKey = $Tenant + totalMailboxes = $TotalMailboxes + processedMailboxes = $processedMailboxes + currentSuccessCount = $SuccessCounter + } + Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + Write-Host "Processed $processedMailboxes mailboxes" } - Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + } - Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully set default calendar permissions for $SuccessCounter out of $TotalMailboxes mailboxes." -sev Info + $LastRun = @{ + RowKey = 'calDefaults' + PartitionKey = $Tenant + totalMailboxes = $TotalMailboxes + processedMailboxes = $processedMailboxes + currentSuccessCount = $SuccessCounter } + Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force + + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Successfully set default calendar permissions for $SuccessCounter out of $TotalMailboxes mailboxes." -sev Info + } + if ($Settings.report -eq $true) { + #This script always returns true, as it only disables the Safe Senders list + Set-CIPPStandardsCompareField -FieldName 'standards.SafeSendersDisable' -FieldValue $true -Tenant $Tenant } +} From b50118e0a1a38681bd85114cb20829f970a08a4e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:42:22 +0200 Subject: [PATCH 110/125] Fix reporting --- .../Invoke-CIPPStandardExternalMFATrusted.ps1 | 5 +++-- .../Invoke-CIPPStandardGroupTemplate.ps1 | 22 +++++++++++++++++-- ...oke-CIPPStandardMailboxRecipientLimits.ps1 | 3 +-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 index 99dcf906b2d8..05320c8fbb03 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExternalMFATrusted.ps1 @@ -43,7 +43,7 @@ function Invoke-CIPPStandardExternalMFATrusted { # Input validation if (([string]::IsNullOrWhiteSpace($state) -or $state -eq 'Select a value') -and ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { Write-LogMessage -API 'Standards' -tenant $Tenant -message 'ExternalMFATrusted: Invalid state parameter set' -sev Error - Return + return } if ($Settings.remediate -eq $true) { @@ -66,7 +66,8 @@ function Invoke-CIPPStandardExternalMFATrusted { } if ($Settings.report -eq $true) { $state = $ExternalMFATrusted.inboundTrust.isMfaAccepted ? $true : $ExternalMFATrusted.inboundTrust - Set-CIPPStandardsCompareField -FieldName 'standards.ExternalMFATrusted' -FieldValue $ExternalMFATrusted.inboundTrust.isMfaAccepted -TenantFilter $Tenant + $ReportState = $ExternalMFATrusted.inboundTrust.isMfaAccepted -eq $WantedState + Set-CIPPStandardsCompareField -FieldName 'standards.ExternalMFATrusted' -FieldValue $ReportState -TenantFilter $Tenant Add-CIPPBPAField -FieldName 'ExternalMFATrusted' -FieldValue $ExternalMFATrusted.inboundTrust.isMfaAccepted -StoreAs bool -Tenant $Tenant } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 index 0fed0c046031..7b8580ec9b7c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 @@ -29,7 +29,7 @@ function Invoke-CIPPStandardGroupTemplate { #> param($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'GroupTemplate' - + $existingGroups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenant if ($Settings.remediate -eq $true) { #Because the list name changed from TemplateList to groupTemplate by someone :@, we'll need to set it back to TemplateList $Settings.groupTemplate ? ($Settings | Add-Member -NotePropertyName 'TemplateList' -NotePropertyValue $Settings.groupTemplate) : $null @@ -40,7 +40,7 @@ function Invoke-CIPPStandardGroupTemplate { $Filter = "PartitionKey eq 'GroupTemplate' and RowKey eq '$($Template.value)'" $groupobj = (Get-AzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json $email = if ($groupobj.domain) { "$($groupobj.username)@$($groupobj.domain)" } else { "$($groupobj.username)@$($Tenant)" } - $CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenant | Where-Object -Property displayName -EQ $groupobj.displayname + $CheckExististing = $existingGroups | Where-Object -Property displayName -EQ $groupobj.displayname $BodyToship = [pscustomobject] @{ 'displayName' = $groupobj.Displayname 'description' = $groupobj.Description @@ -114,4 +114,22 @@ function Invoke-CIPPStandardGroupTemplate { } } } + if ($Settings.report -eq $true) { + $Groups = $Settings.groupTemplate.JSON | ConvertFrom-Json -Depth 10 + #check if all groups.displayName are in the existingGroups, if not $fieldvalue should contain all missing groups, else it should be true. + $MissingGroups = foreach ($Group in $Groups) { + $CheckExististing = $existingGroups | Where-Object -Property displayName -EQ $Group.displayname + if (!$CheckExististing) { + $Group.displayname + } + } + + if ($MissingGroups.Count -eq 0) { + $fieldValue = $true + } else { + $fieldValue = $MissingGroups -join ', ' + } + + Set-CIPPStandardsCompareField -FieldName 'standards.SafeSendersDisable' -FieldValue $fieldValue -Tenant $Tenant + } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 index 5b2dca61d818..d78b7dea48ce 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 @@ -71,8 +71,7 @@ function Invoke-CIPPStandardMailboxRecipientLimits { $MailboxResults = @($Mailboxes) | ForEach-Object { $Mailbox = $_ - #if the mailbox username contains a guid, we can assume it's a system mailbox and skip it - if ($Mailbox.UserPrincipalName -match '^[^@]+@[^.]+\.[^.]+$') { + if ($Mailbox.UserPrincipalName -like 'DiscoverySearchMailbox*' -or $Mailbox.UserPrincipalName -like 'SystemMailbox*') { return } # Safe hashtable lookup - check if MailboxPlanId exists and is not null From c3204ee772c303fe07ad5e49ac0bd3f5e2e7e327 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 18:48:50 +0200 Subject: [PATCH 111/125] grouptemplate --- .../Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 index 7b8580ec9b7c..f38b0c36f194 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 @@ -130,6 +130,6 @@ function Invoke-CIPPStandardGroupTemplate { $fieldValue = $MissingGroups -join ', ' } - Set-CIPPStandardsCompareField -FieldName 'standards.SafeSendersDisable' -FieldValue $fieldValue -Tenant $Tenant + Set-CIPPStandardsCompareField -FieldName 'standards.GroupTemplate' -FieldValue $fieldValue -Tenant $Tenant } } From 0b7def3d13c69c63ae3cb3a01aefbab0bb0b6bdd Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 22:44:24 +0200 Subject: [PATCH 112/125] fix reporting --- .../Invoke-CIPPStandardDefaultSharingLink.ps1 | 4 ++-- ...voke-CIPPStandardTransportRuleTemplate.ps1 | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 index ed5be966e106..84dfa38a831a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 @@ -43,11 +43,11 @@ function Invoke-CIPPStandardDefaultSharingLink { $DesiredSharingLinkTypeValue = $SharingLinkTypeMap[$DesiredSharingLinkType] $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | - Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType, DefaultLinkPermission + Select-Object -Property _ObjectIdentity_, TenantFilter, DefaultSharingLinkType, DefaultLinkPermission # Check if the current state matches the desired configuration $StateIsCorrect = ($CurrentState.DefaultSharingLinkType -eq $DesiredSharingLinkTypeValue) -and ($CurrentState.DefaultLinkPermission -eq 1) - + Write-Host "currentstate: $($CurrentState.DefaultSharingLinkType), $($CurrentState.DefaultLinkPermission). Desired: $DesiredSharingLinkTypeValue, 1" if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Default sharing link settings are already configured correctly (Type: $DesiredSharingLinkType, Permission: View)" -Sev Info diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 index 79e154dbe8e0..8b6f21dbaf65 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 @@ -27,8 +27,8 @@ function Invoke-CIPPStandardTransportRuleTemplate { #> param($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TransportRuleTemplate' - - If ($Settings.remediate -eq $true) { + $existingRules = New-ExoRequest -ErrorAction SilentlyContinue -tenantid $Tenant -cmdlet 'Get-TransportRule' -useSystemMailbox $true + if ($Settings.remediate -eq $true) { Write-Host "Settings: $($Settings | ConvertTo-Json)" $Settings.transportRuleTemplate ? ($Settings | Add-Member -NotePropertyName 'TemplateList' -NotePropertyValue $Settings.transportRuleTemplate) : $null foreach ($Template in $Settings.TemplateList) { @@ -36,8 +36,7 @@ function Invoke-CIPPStandardTransportRuleTemplate { $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'TransportTemplate' and RowKey eq '$($Template.value)'" $RequestParams = (Get-AzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json - $Existing = New-ExoRequest -ErrorAction SilentlyContinue -tenantid $Tenant -cmdlet 'Get-TransportRule' -useSystemMailbox $true | Where-Object -Property Identity -EQ $RequestParams.name - + $Existing = $existingRules | Where-Object -Property Identity -EQ $RequestParams.name try { if ($Existing) { @@ -58,4 +57,21 @@ function Invoke-CIPPStandardTransportRuleTemplate { } } } + if ($Settings.report -eq $true) { + $rules = $Settings.transportRuleTemplate.JSON | ConvertFrom-Json -Depth 10 + $MissingRules = foreach ($rule in $rules) { + $CheckExististing = $existingRules | Where-Object -Property identity -EQ $rule.displayname + if (!$CheckExististing) { + $rule.displayname + } + } + + if ($MissingRules.Count -eq 0) { + $fieldValue = $true + } else { + $fieldValue = $MissingRules -join ', ' + } + + Set-CIPPStandardsCompareField -FieldName 'standards.TransportRuleTemplate' -FieldValue $fieldValue -Tenant $Tenant + } } From f580978022fe308a702f0f4cfd55b2e8fd357139 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:34:20 +0200 Subject: [PATCH 113/125] add alignment alert --- .../Get-CIPPAlertLowTenantAlignment.ps1 | 43 +++ .../Standards/Invoke-ListTenantAlignment.ps1 | 249 ++---------------- .../Functions/Get-CIPPTenantAlignment.ps1 | 247 +++++++++++++++++ 3 files changed, 315 insertions(+), 224 deletions(-) create mode 100644 Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 create mode 100644 Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 new file mode 100644 index 000000000000..9430008454d1 --- /dev/null +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 @@ -0,0 +1,43 @@ +function Get-CIPPAlertLowTenantAlignment { + <# + .SYNOPSIS + Alert for low tenant alignment percentage + .DESCRIPTION + This alert checks tenant alignment scores against standards templates and alerts when the alignment percentage falls below the specified threshold. + .PARAMETER TenantFilter + The tenant to check alignment for + .PARAMETER InputValue + The minimum alignment percentage threshold (0-100). Default is 80. + .FUNCTIONALITY + Entrypoint + .EXAMPLE + Get-CIPPAlertLowTenantAlignment -TenantFilter "contoso.onmicrosoft.com" -InputValue 75 + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory)] + $TenantFilter, + [Alias('input')] + [ValidateRange(0, 100)] + [int]$InputValue = 99 + ) + + try { + # Get tenant alignment data using the new function + $AlignmentData = Get-CIPPTenantAlignment -TenantFilter $TenantFilter + + if (-not $AlignmentData) { + Write-AlertMessage -tenant $TenantFilter -message "No alignment data found for tenant $TenantFilter. This may indicate no standards templates are configured or applied to this tenant." + return + } + + $LowAlignmentAlerts = $AlignmentData | Where-Object { $_.AlignmentScore -lt $InputValue } + + if ($LowAlignmentAlerts.Count -gt 0) { + Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $LowAlignmentAlerts + } + + } catch { + Write-AlertMessage -tenant $TenantFilter -message "Could not get tenant alignment data for $TenantFilter`: $(Get-NormalizedError -message $_.Exception.message)" + } +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 index b563b2229bc4..19ea74729fad 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 @@ -13,229 +13,30 @@ function Invoke-ListTenantAlignment { $APIName = $Request.Params.CIPPEndpoint $Headers = $Request.Headers - # Get all standard templates - $TemplateTable = Get-CippTable -tablename 'templates' - $TemplateFilter = "PartitionKey eq 'StandardsTemplateV2'" - $Templates = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $TemplateFilter) | ForEach-Object { - $JSON = $_.JSON -replace '"Action":', '"action":' - try { - $RowKey = $_.RowKey - $Data = $JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue - } catch { - Write-Host "$($RowKey) standard could not be loaded: $($_.Exception.Message)" - return - } - if ($Data) { - $Data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force - $Data - } - } - - # Get standards comparison data using the same pattern as ListStandardsCompare - $StandardsTable = Get-CIPPTable -TableName 'CippStandardsReports' - $Standards = Get-CIPPAzDataTableEntity @StandardsTable - - # Build tenant standards data structure like in ListStandardsCompare - $TenantStandards = @{} - foreach ($Standard in $Standards) { - $FieldName = $Standard.RowKey - $FieldValue = $Standard.Value - $Tenant = $Standard.PartitionKey - - # Process field value like in ListStandardsCompare - if ($FieldValue -is [System.Boolean]) { - $FieldValue = [bool]$FieldValue - } elseif ($FieldValue -like '*{*') { - $FieldValue = ConvertFrom-Json -InputObject $FieldValue -ErrorAction SilentlyContinue - } else { - $FieldValue = [string]$FieldValue - } - - if (-not $TenantStandards.ContainsKey($Tenant)) { - $TenantStandards[$Tenant] = @{} - } - $TenantStandards[$Tenant][$FieldName] = @{ - Value = $FieldValue - LastRefresh = $Standard.TimeStamp.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') - } - } - - $Results = [System.Collections.Generic.List[object]]::new() - - # Process each template against all tenants - foreach ($Template in $Templates) { - $TemplateStandards = $Template.standards - if (-not $TemplateStandards) { - continue - } - - # Check if template has tenant assignments (scope) - $TemplateAssignedTenants = @() - $AppliestoAllTenants = $false - - if ($Template.tenantFilter -and $Template.tenantFilter.Count -gt 0) { - # Extract tenant values from the tenantFilter array - $TenantValues = $Template.tenantFilter | ForEach-Object { $_.value } - - if ($TenantValues -contains 'AllTenants') { - $AppliestoAllTenants = $true - Write-Host "Template '$($Template.templateName)' applies to all tenants (AllTenants)" - } else { - $TemplateAssignedTenants = $TenantValues - Write-Host "Template '$($Template.templateName)' is assigned to specific tenants: $($TemplateAssignedTenants -join ', ')" - } - } else { - $AppliestoAllTenants = $true - Write-Host "Template '$($Template.templateName)' applies to all tenants (no tenantFilter)" - } - - $AllStandards = [System.Collections.Generic.List[string]]::new() - $ReportingEnabledStandards = [System.Collections.Generic.List[string]]::new() - $ReportingDisabledStandards = [System.Collections.Generic.List[string]]::new() - - foreach ($StandardKey in $TemplateStandards.PSObject.Properties.Name) { - $StandardConfig = $TemplateStandards.$StandardKey - $StandardId = "standards.$StandardKey" - - - $Actions = @() - if ($StandardConfig.action) { - $Actions = $StandardConfig.action - } elseif ($StandardConfig.Action) { - $Actions = $StandardConfig.Action - } elseif ($StandardConfig.PSObject.Properties['action']) { - $Actions = $StandardConfig.PSObject.Properties['action'].Value - } - - $ReportingEnabled = $false - if ($Actions -and $Actions.Count -gt 0) { - $ReportingEnabled = ($Actions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 - } - - $AllStandards.Add($StandardId) - - if ($ReportingEnabled) { - $ReportingEnabledStandards.Add($StandardId) - } else { - $ReportingDisabledStandards.Add($StandardId) - } - - if ($StandardKey -eq 'IntuneTemplate' -and $StandardConfig -is [array]) { - $AllStandards.Remove($StandardId) - if ($ReportingEnabled) { - $ReportingEnabledStandards.Remove($StandardId) - } else { - $ReportingDisabledStandards.Remove($StandardId) - } - - foreach ($IntuneTemplate in $StandardConfig) { - if ($IntuneTemplate.TemplateList.value) { - $IntuneStandardId = "standards.IntuneTemplate.$($IntuneTemplate.TemplateList.value)" - - $IntuneActions = if ($IntuneTemplate.action) { $IntuneTemplate.action } else { @() } - $IntuneReportingEnabled = ($IntuneActions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 - - $AllStandards.Add($IntuneStandardId) - - if ($IntuneReportingEnabled) { - $ReportingEnabledStandards.Add($IntuneStandardId) - } else { - $ReportingDisabledStandards.Add($IntuneStandardId) - } - } - } - } - } - - foreach ($TenantName in $TenantStandards.Keys) { - if (-not $AppliestoAllTenants -and $TenantName -notin $TemplateAssignedTenants) { - Write-Host "Skipping tenant '$TenantName' for template '$($Template.templateName)' - not in assigned tenant list" - continue - } - $AllCount = $AllStandards.Count - - $CompliantStandards = 0 - $NonCompliantStandards = 0 - $ReportingDisabledStandardsCount = 0 - $LatestDataCollection = $null - $ComparisonTable = @() - - foreach ($StandardKey in $AllStandards) { - $IsReportingDisabled = $ReportingDisabledStandards -contains $StandardKey - - if ($TenantStandards[$TenantName].ContainsKey($StandardKey)) { - $StandardObject = $TenantStandards[$TenantName][$StandardKey] - $Value = $StandardObject.Value - - if ($StandardObject.LastRefresh) { - $RefreshTime = [DateTime]::Parse($StandardObject.LastRefresh) - if (-not $LatestDataCollection -or $RefreshTime -gt $LatestDataCollection) { - $LatestDataCollection = $RefreshTime - } - } - - $IsCompliant = ($Value -eq $true) - - if ($IsReportingDisabled) { - $ReportingDisabledStandardsCount++ - $ComplianceStatus = 'Reporting Disabled' - } elseif ($IsCompliant) { - $CompliantStandards++ - $ComplianceStatus = 'Compliant' - } else { - $NonCompliantStandards++ - $ComplianceStatus = 'Non-Compliant' - } - - $ComparisonTable += [PSCustomObject]@{ - StandardName = $StandardKey - Compliant = $IsCompliant - StandardValue = ($Value | ConvertTo-Json -Compress) - ComplianceStatus = $ComplianceStatus - ReportingDisabled = $IsReportingDisabled - } - } else { - if ($IsReportingDisabled) { - $ReportingDisabledStandardsCount++ - $ComplianceStatus = 'Reporting Disabled' - } else { - $NonCompliantStandards++ - $ComplianceStatus = 'Non-Compliant' - } - - $ComparisonTable += [PSCustomObject]@{ - StandardName = $StandardKey - Compliant = $false - StandardValue = 'NOT FOUND' - ComplianceStatus = $ComplianceStatus - ReportingDisabled = $IsReportingDisabled - } - } - } - - - $AlignmentPercentage = if (($AllCount - $ReportingDisabledStandardsCount) -gt 0) { - [Math]::Round(($CompliantStandards / ($AllCount - $ReportingDisabledStandardsCount)) * 100) - } else { - 0 - } - - $TenantOnlyStandards = $TenantStandards[$TenantName].Keys | Where-Object { $_ -notin $AllStandards } - - $Result = [PSCustomObject]@{ - tenantFilter = $TenantName - standardName = $Template.templateName - standardId = $Template.GUID - alignmentScore = $AlignmentPercentage - latestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null } - } - - $Results.Add($Result) - } + try { + # Use the new Get-CIPPTenantAlignment function to get alignment data + $AlignmentData = Get-CIPPTenantAlignment + + # Transform the data to match the expected API response format + $Results = $AlignmentData | ForEach-Object { + [PSCustomObject]@{ + tenantFilter = $_.TenantFilter + standardName = $_.StandardName + standardId = $_.StandardId + alignmentScore = $_.AlignmentScore + latestDataCollection = $_.LatestDataCollection + } + } + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($Results) + }) + } catch { + Write-LogMessage -API $APIName -message "Failed to get tenant alignment data: $($_.Exception.Message)" -sev Error + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::InternalServerError + Body = @{ error = "Failed to get tenant alignment data: $($_.Exception.Message)" } + }) } - - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = @($Results) - }) } diff --git a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 new file mode 100644 index 000000000000..ea2d35cf240d --- /dev/null +++ b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 @@ -0,0 +1,247 @@ +function Get-CIPPTenantAlignment { + <# + .SYNOPSIS + Gets tenant alignment data for standards compliance + .DESCRIPTION + This function calculates tenant alignment percentages against standards templates. + It processes all standard templates and compares them against tenant standards data. + .PARAMETER TenantFilter + The tenant to get alignment data for. If not specified, processes all tenants. + .PARAMETER TemplateId + Optional specific template GUID to check alignment for. If not specified, processes all templates. + .FUNCTIONALITY + Internal + .EXAMPLE + Get-CIPPTenantAlignment -TenantFilter "contoso.onmicrosoft.com" + .EXAMPLE + Get-CIPPTenantAlignment -TenantFilter "contoso.onmicrosoft.com" -TemplateId "12345-67890-abcdef" + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $false)] + [string]$TenantFilter, + + [Parameter(Mandatory = $false)] + [string]$TemplateId + ) + + try { + # Get all standard templates + $TemplateTable = Get-CippTable -tablename 'templates' + $TemplateFilter = "PartitionKey eq 'StandardsTemplateV2'" + + $Templates = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $TemplateFilter) | ForEach-Object { + $JSON = $_.JSON -replace '"Action":', '"action":' + try { + $RowKey = $_.RowKey + $Data = $JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue + } catch { + Write-Warning "$($RowKey) standard could not be loaded: $($_.Exception.Message)" + return + } + if ($Data) { + $Data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force + $Data + } + } + + if (-not $Templates) { + Write-Warning 'No templates found matching the criteria' + return @() + } + + # Get standards comparison data + $StandardsTable = Get-CIPPTable -TableName 'CippStandardsReports' + $AllStandards = Get-CIPPAzDataTableEntity @StandardsTable + + # Filter by tenant if specified + $Standards = if ($TenantFilter) { + $AllStandards | Where-Object { $_.PartitionKey -eq $TenantFilter } + } else { + $AllStandards + } + + # Build tenant standards data structure + $TenantStandards = @{} + foreach ($Standard in $Standards) { + $FieldName = $Standard.RowKey + $FieldValue = $Standard.Value + $Tenant = $Standard.PartitionKey + + # Process field value + if ($FieldValue -is [System.Boolean]) { + $FieldValue = [bool]$FieldValue + } elseif ($FieldValue -like '*{*') { + $FieldValue = ConvertFrom-Json -InputObject $FieldValue -ErrorAction SilentlyContinue + } else { + $FieldValue = [string]$FieldValue + } + + if (-not $TenantStandards.ContainsKey($Tenant)) { + $TenantStandards[$Tenant] = @{} + } + $TenantStandards[$Tenant][$FieldName] = @{ + Value = $FieldValue + LastRefresh = $Standard.TimeStamp.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + } + } + + $Results = [System.Collections.Generic.List[object]]::new() + + # Process each template against all tenants + foreach ($Template in $Templates) { + $TemplateStandards = $Template.standards + if (-not $TemplateStandards) { + continue + } + + # Check if template has tenant assignments (scope) + $TemplateAssignedTenants = @() + $AppliestoAllTenants = $false + + if ($Template.tenantFilter -and $Template.tenantFilter.Count -gt 0) { + # Extract tenant values from the tenantFilter array + $TenantValues = $Template.tenantFilter | ForEach-Object { $_.value } + + if ($TenantValues -contains 'AllTenants') { + $AppliestoAllTenants = $true + } else { + $TemplateAssignedTenants = $TenantValues + } + } else { + $AppliestoAllTenants = $true + } + + $StandardsData = foreach ($StandardKey in $TemplateStandards.PSObject.Properties.Name) { + $StandardConfig = $TemplateStandards.$StandardKey + $StandardId = "standards.$StandardKey" + + $Actions = @() + if ($StandardConfig.action) { + $Actions = $StandardConfig.action + } elseif ($StandardConfig.Action) { + $Actions = $StandardConfig.Action + } elseif ($StandardConfig.PSObject.Properties['action']) { + $Actions = $StandardConfig.PSObject.Properties['action'].Value + } + + $ReportingEnabled = $false + if ($Actions -and $Actions.Count -gt 0) { + $ReportingEnabled = ($Actions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 + } + + # Handle Intune templates specially + if ($StandardKey -eq 'IntuneTemplate' -and $StandardConfig -is [array]) { + foreach ($IntuneTemplate in $StandardConfig) { + if ($IntuneTemplate.TemplateList.value) { + $IntuneStandardId = "standards.IntuneTemplate.$($IntuneTemplate.TemplateList.value)" + $IntuneActions = if ($IntuneTemplate.action) { $IntuneTemplate.action } else { @() } + $IntuneReportingEnabled = ($IntuneActions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0 + + [PSCustomObject]@{ + StandardId = $IntuneStandardId + ReportingEnabled = $IntuneReportingEnabled + } + } + } + } else { + [PSCustomObject]@{ + StandardId = $StandardId + ReportingEnabled = $ReportingEnabled + } + } + } + + $AllStandards = $StandardsData.StandardId + $ReportingEnabledStandards = ($StandardsData | Where-Object { $_.ReportingEnabled }).StandardId + $ReportingDisabledStandards = ($StandardsData | Where-Object { -not $_.ReportingEnabled }).StandardId + + foreach ($TenantName in $TenantStandards.Keys) { + if (-not $AppliestoAllTenants -and $TenantName -notin $TemplateAssignedTenants) { + continue + } + + $AllCount = $AllStandards.Count + $LatestDataCollection = $null + + $ComparisonTable = foreach ($StandardKey in $AllStandards) { + $IsReportingDisabled = $ReportingDisabledStandards -contains $StandardKey + + if ($TenantStandards[$TenantName].ContainsKey($StandardKey)) { + $StandardObject = $TenantStandards[$TenantName][$StandardKey] + $Value = $StandardObject.Value + + if ($StandardObject.LastRefresh) { + $RefreshTime = [DateTime]::Parse($StandardObject.LastRefresh) + if (-not $LatestDataCollection -or $RefreshTime -gt $LatestDataCollection) { + $LatestDataCollection = $RefreshTime + } + } + + $IsCompliant = ($Value -eq $true) + + if ($IsReportingDisabled) { + $ComplianceStatus = 'Reporting Disabled' + } elseif ($IsCompliant) { + $ComplianceStatus = 'Compliant' + } else { + $ComplianceStatus = 'Non-Compliant' + } + + [PSCustomObject]@{ + StandardName = $StandardKey + Compliant = $IsCompliant + StandardValue = ($Value | ConvertTo-Json -Compress) + ComplianceStatus = $ComplianceStatus + ReportingDisabled = $IsReportingDisabled + } + } else { + if ($IsReportingDisabled) { + $ComplianceStatus = 'Reporting Disabled' + } else { + $ComplianceStatus = 'Non-Compliant' + } + + [PSCustomObject]@{ + StandardName = $StandardKey + Compliant = $false + StandardValue = 'NOT FOUND' + ComplianceStatus = $ComplianceStatus + ReportingDisabled = $IsReportingDisabled + } + } + } + + $CompliantStandards = ($ComparisonTable | Where-Object { $_.ComplianceStatus -eq 'Compliant' }).Count + $NonCompliantStandards = ($ComparisonTable | Where-Object { $_.ComplianceStatus -eq 'Non-Compliant' }).Count + $ReportingDisabledStandardsCount = ($ComparisonTable | Where-Object { $_.ReportingDisabled }).Count + + $AlignmentPercentage = if (($AllCount - $ReportingDisabledStandardsCount) -gt 0) { + [Math]::Round(($CompliantStandards / ($AllCount - $ReportingDisabledStandardsCount)) * 100) + } else { + 0 + } + + $Result = [PSCustomObject]@{ + TenantFilter = $TenantName + StandardName = $Template.templateName + StandardId = $Template.GUID + AlignmentScore = $AlignmentPercentage + CompliantStandards = $CompliantStandards + NonCompliantStandards = $NonCompliantStandards + TotalStandards = $AllCount + ReportingDisabledCount = $ReportingDisabledStandardsCount + LatestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null } + ComparisonDetails = $ComparisonTable + } + + $Results.Add($Result) + } + } + + return $Results + } catch { + Write-Error "Error getting tenant alignment data: $($_.Exception.Message)" + throw + } +} From 361a68ff363a0735e3aac079f1a1a150496b2adf Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 00:13:22 +0200 Subject: [PATCH 114/125] fixes --- .../Get-CIPPAlertLowTenantAlignment.ps1 | 13 ++++++-- .../Standards/Invoke-ListTenantAlignment.ps1 | 11 +++---- .../Functions/Get-CIPPTenantAlignment.ps1 | 30 +++++++++++++------ ...-CIPPStandardConditionalAccessTemplate.ps1 | 23 ++++++++++---- 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 index 9430008454d1..bc8c5a04672d 100644 --- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertLowTenantAlignment.ps1 @@ -14,7 +14,7 @@ function Get-CIPPAlertLowTenantAlignment { Get-CIPPAlertLowTenantAlignment -TenantFilter "contoso.onmicrosoft.com" -InputValue 75 #> [CmdletBinding()] - Param ( + param ( [Parameter(Mandatory)] $TenantFilter, [Alias('input')] @@ -31,7 +31,16 @@ function Get-CIPPAlertLowTenantAlignment { return } - $LowAlignmentAlerts = $AlignmentData | Where-Object { $_.AlignmentScore -lt $InputValue } + $LowAlignmentAlerts = $AlignmentData | Where-Object { $_.AlignmentScore -lt $InputValue } | ForEach-Object { + [PSCustomObject]@{ + TenantFilter = $_.TenantFilter + StandardName = $_.StandardName + StandardId = $_.StandardId + AlignmentScore = $_.AlignmentScore + LicenseMissingPercentage = $_.LicenseMissingPercentage + LatestDataCollection = $_.LatestDataCollection + } + } if ($LowAlignmentAlerts.Count -gt 0) { Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $LowAlignmentAlerts diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 index 19ea74729fad..65c644a24593 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 @@ -20,11 +20,12 @@ function Invoke-ListTenantAlignment { # Transform the data to match the expected API response format $Results = $AlignmentData | ForEach-Object { [PSCustomObject]@{ - tenantFilter = $_.TenantFilter - standardName = $_.StandardName - standardId = $_.StandardId - alignmentScore = $_.AlignmentScore - latestDataCollection = $_.LatestDataCollection + tenantFilter = $_.TenantFilter + standardName = $_.StandardName + standardId = $_.StandardId + alignmentScore = $_.AlignmentScore + LicenseMissingPercentage = $_.LicenseMissingPercentage + latestDataCollection = $_.LatestDataCollection } } diff --git a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 index ea2d35cf240d..be4d2740c85e 100644 --- a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 @@ -179,11 +179,14 @@ function Get-CIPPTenantAlignment { } $IsCompliant = ($Value -eq $true) + $IsLicenseMissing = ($Value -is [string] -and $Value -like "License Missing:*") if ($IsReportingDisabled) { $ComplianceStatus = 'Reporting Disabled' } elseif ($IsCompliant) { $ComplianceStatus = 'Compliant' + } elseif ($IsLicenseMissing) { + $ComplianceStatus = 'License Missing' } else { $ComplianceStatus = 'Non-Compliant' } @@ -214,6 +217,7 @@ function Get-CIPPTenantAlignment { $CompliantStandards = ($ComparisonTable | Where-Object { $_.ComplianceStatus -eq 'Compliant' }).Count $NonCompliantStandards = ($ComparisonTable | Where-Object { $_.ComplianceStatus -eq 'Non-Compliant' }).Count + $LicenseMissingStandards = ($ComparisonTable | Where-Object { $_.ComplianceStatus -eq 'License Missing' }).Count $ReportingDisabledStandardsCount = ($ComparisonTable | Where-Object { $_.ReportingDisabled }).Count $AlignmentPercentage = if (($AllCount - $ReportingDisabledStandardsCount) -gt 0) { @@ -222,17 +226,25 @@ function Get-CIPPTenantAlignment { 0 } + $LicenseMissingPercentage = if ($AllCount -gt 0) { + [Math]::Round(($LicenseMissingStandards / $AllCount) * 100) + } else { + 0 + } + $Result = [PSCustomObject]@{ - TenantFilter = $TenantName - StandardName = $Template.templateName - StandardId = $Template.GUID - AlignmentScore = $AlignmentPercentage - CompliantStandards = $CompliantStandards - NonCompliantStandards = $NonCompliantStandards - TotalStandards = $AllCount + TenantFilter = $TenantName + StandardName = $Template.templateName + StandardId = $Template.GUID + AlignmentScore = $AlignmentPercentage + LicenseMissingPercentage = $LicenseMissingPercentage + CompliantStandards = $CompliantStandards + NonCompliantStandards = $NonCompliantStandards + LicenseMissingStandards = $LicenseMissingStandards + TotalStandards = $AllCount ReportingDisabledCount = $ReportingDisabledStandardsCount - LatestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null } - ComparisonDetails = $ComparisonTable + LatestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null } + ComparisonDetails = $ComparisonTable } $Results.Add($Result) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 index 6e6e34788e12..390d80c25f71 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 @@ -31,11 +31,10 @@ function Invoke-CIPPStandardConditionalAccessTemplate { param($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'ConditionalAccess' - If ($Settings.remediate -eq $true) { - + if ($Settings.remediate -eq $true) { + $AllCAPolicies = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies?$top=999' -tenantid $Tenant foreach ($Setting in $Settings) { try { - $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'CATemplate' and RowKey eq '$($Setting.TemplateList.value)'" $JSONObj = (Get-CippAzDataTableEntity @Table -Filter $Filter).JSON @@ -45,7 +44,21 @@ function Invoke-CIPPStandardConditionalAccessTemplate { Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update conditional access rule $($JSONObj.displayName). Error: $ErrorMessage" -sev 'Error' } } - - + } + if ($Settings.report -eq $true) { + $Policies = $Settings.TemplateList.JSON | ConvertFrom-Json -Depth 10 + #check if all groups.displayName are in the existingGroups, if not $fieldvalue should contain all missing groups, else it should be true. + $MissingPolicies = foreach ($policy in $Policies) { + $CheckExististing = $AllCAPolicies | Where-Object -Property displayName -EQ $policy.displayname + if (!$CheckExististing) { + $policy.displayname + } + } + if ($MissingPolicies.Count -eq 0) { + $fieldValue = $true + } else { + $fieldValue = $MissingPolicies -join ', ' + } + Set-CIPPStandardsCompareField -FieldName 'standards.GroupTemplate' -FieldValue $fieldValue -Tenant $Tenant } } From 2439438012d74feebecc404e829829d9159ffbdd Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:40:04 +0200 Subject: [PATCH 115/125] license check changes --- .../Functions/Test-CIPPStandardLicense.ps1 | 55 +++++++++++++++++++ .../Public/Get-CIPPTenantCapabilities.ps1 | 7 +-- .../Invoke-CIPPStandardAntiPhishPolicy.ps1 | 1 + .../Invoke-CIPPStandardAntiSpamSafeList.ps1 | 1 + .../Invoke-CIPPStandardAtpPolicyForO365.ps1 | 1 + .../Standards/Invoke-CIPPStandardAuditLog.ps1 | 1 + .../Invoke-CIPPStandardAutoExpandArchive.ps1 | 1 + .../Standards/Invoke-CIPPStandardBookings.ps1 | 1 + .../Invoke-CIPPStandardCloudMessageRecall.ps1 | 1 + ...-CIPPStandardConditionalAccessTemplate.ps1 | 3 +- .../Invoke-CIPPStandardDelegateSentItems.ps1 | 1 + ...oke-CIPPStandardDeployContactTemplates.ps1 | 1 + .../Invoke-CIPPStandardDeployMailContact.ps1 | 1 + ...ndardDisableAdditionalStorageProviders.ps1 | 1 + ...nvoke-CIPPStandardDisableBasicAuthSMTP.ps1 | 1 + ...tandardDisableExchangeOnlinePowerShell.ps1 | 1 + ...StandardDisableExternalCalendarSharing.ps1 | 1 + ...nvoke-CIPPStandardDisableOutlookAddins.ps1 | 1 + ...oke-CIPPStandardDisableResourceMailbox.ps1 | 1 + .../Invoke-CIPPStandardDisableTNEF.ps1 | 2 +- ...e-CIPPStandardEXODisableAutoForwarding.ps1 | 1 + ...voke-CIPPStandardEXOOutboundSpamLimits.ps1 | 1 + ...voke-CIPPStandardEnableCustomerLockbox.ps1 | 1 + ...nvoke-CIPPStandardEnableLitigationHold.ps1 | 1 + .../Invoke-CIPPStandardEnableMailTips.ps1 | 1 + ...voke-CIPPStandardEnableMailboxAuditing.ps1 | 1 + ...voke-CIPPStandardEnableOnlineArchiving.ps1 | 1 + .../Invoke-CIPPStandardExConnector.ps1 | 1 + .../Invoke-CIPPStandardFocusedInbox.ps1 | 1 + ...PStandardGlobalQuarantineNotifications.ps1 | 2 +- .../Invoke-CIPPStandardGroupTemplate.ps1 | 1 + ...oke-CIPPStandardMailboxRecipientLimits.ps1 | 1 + ...Invoke-CIPPStandardMalwareFilterPolicy.ps1 | 1 + .../Invoke-CIPPStandardMessageExpiration.ps1 | 1 + .../Invoke-CIPPStandardOutBoundSpamAlert.ps1 | 1 + ...-CIPPStandardPhishSimSpoofIntelligence.ps1 | 1 + ...Invoke-CIPPStandardPhishingSimulations.ps1 | 1 + .../Invoke-CIPPStandardProfilePhotos.ps1 | 1 + .../Invoke-CIPPStandardQuarantineTemplate.ps1 | 1 + .../Invoke-CIPPStandardRetentionPolicyTag.ps1 | 1 + .../Invoke-CIPPStandardRotateDKIM.ps1 | 1 + ...nvoke-CIPPStandardSafeAttachmentPolicy.ps1 | 1 + .../Invoke-CIPPStandardSafeLinksPolicy.ps1 | 1 + ...ke-CIPPStandardSafeLinksTemplatePolicy.ps1 | 1 + .../Invoke-CIPPStandardSafeSendersDisable.ps1 | 1 + .../Invoke-CIPPStandardSendFromAlias.ps1 | 3 +- ...oke-CIPPStandardSendReceiveLimitTenant.ps1 | 1 + .../Invoke-CIPPStandardShortenMeetings.ps1 | 1 + .../Invoke-CIPPStandardSpamFilterPolicy.ps1 | 1 + .../Invoke-CIPPStandardSpoofWarn.ps1 | 1 + ...oke-CIPPStandardTeamsMeetingsByDefault.ps1 | 1 + ...voke-CIPPStandardTransportRuleTemplate.ps1 | 1 + ...ke-CIPPStandardTwoClickEmailProtection.ps1 | 1 + .../Invoke-CIPPStandardUserSubmissions.ps1 | 1 + .../Invoke-CIPPStandardcalDefault.ps1 | 1 + fix-license-test.ps1 | 49 +++++++++++++++++ update-license-test.ps1 | 52 ++++++++++++++++++ 57 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 create mode 100644 fix-license-test.ps1 create mode 100644 update-license-test.ps1 diff --git a/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 b/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 new file mode 100644 index 000000000000..68f434cbf5f3 --- /dev/null +++ b/Modules/CIPPCore/Public/Functions/Test-CIPPStandardLicense.ps1 @@ -0,0 +1,55 @@ +function Test-CIPPStandardLicense { + <# + .SYNOPSIS + Tests if a tenant has the required license capabilities for a specific standard + .DESCRIPTION + This function checks if a tenant has the necessary license capabilities to run a specific standard. + If the license is missing, it logs an error and sets the comparison field appropriately. + .PARAMETER StandardName + The name of the standard to check licensing for + .PARAMETER TenantFilter + The tenant to check licensing for + .PARAMETER RequiredCapabilities + Array of required capabilities 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') + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$StandardName, + + [Parameter(Mandatory = $true)] + [string]$TenantFilter, + + [Parameter(Mandatory = $true)] + [string[]]$RequiredCapabilities + ) + + try { + $TenantCapabilities = Get-CIPPTenantCapabilities -TenantFilter $TenantFilter + + $Capabilities = foreach ($Capability in $RequiredCapabilities) { + Write-Host "Checking capability: $Capability" + if ($TenantCapabilities.$Capability -eq $true) { + $Capability + } + } + + if ($Capabilities.Count -le 0) { + Write-LogMessage -API 'Standards' -tenant $TenantFilter -message "Tenant does not have the required capability to run standard $StandardName`: The tenant needs one of the following service plans: $($RequiredCapabilities -join ',')" -sev Error + Set-CIPPStandardsCompareField -FieldName "standards.$StandardName" -FieldValue "License Missing: This tenant is not licensed for the following capabilities: $($RequiredCapabilities -join ',')" -Tenant $TenantFilter + Write-Host "Tenant does not have the required capability to run standard $StandardName - $($RequiredCapabilities -join ','). Exiting" + exit 0 + } + Write-Host "Tenant has the required capabilities for standard $StandardName" + } catch { + Write-LogMessage -API 'Standards' -tenant $TenantFilter -message "Error checking license capabilities for standard $StandardName`: $($_.Exception.Message)" -sev Error + Set-CIPPStandardsCompareField -FieldName "standards.$StandardName" -FieldValue "License Missing: Error checking license capabilities - $($_.Exception.Message)" -Tenant $TenantFilter + exit 0 + } +} diff --git a/Modules/CIPPCore/Public/Get-CIPPTenantCapabilities.ps1 b/Modules/CIPPCore/Public/Get-CIPPTenantCapabilities.ps1 index 5b44f04737e6..23c57432005a 100644 --- a/Modules/CIPPCore/Public/Get-CIPPTenantCapabilities.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPTenantCapabilities.ps1 @@ -7,12 +7,11 @@ function Get-CIPPTenantCapabilities { $Headers ) - $Org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $TenantFilter - $Plans = $Org.assignedPlans | Where-Object { $_.capabilityStatus -eq 'Enabled' } | Sort-Object -Property service -Unique | Select-Object capabilityStatus, service - + $Org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $TenantFilter + $Plans = $Org.servicePlans | Where-Object { $_.provisioningStatus -eq 'Success' } | Sort-Object -Property serviceplanName -Unique | Select-Object servicePlanName, provisioningStatus $Results = @{} foreach ($Plan in $Plans) { - $Results."$($Plan.service)" = $Plan.capabilityStatus -eq 'Enabled' + $Results."$($Plan.servicePlanName)" = $Plan.provisioningStatus -eq 'Success' } [PSCustomObject]$Results } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index b01f7b252593..2eff3aed63de 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -50,6 +50,7 @@ function Invoke-CIPPStandardAntiPhishPolicy { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'AntiPhishPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AntiPhishPolicy' $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 index ccfb85dcff9d..5ef7bd58401b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiSpamSafeList.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardAntiSpamSafeList { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'AntiSpamSafeList' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AntiSpamSafeList' try { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 index bed3d5c48109..b8c8b0e10a26 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardAtpPolicyForO365 { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'AtpPolicyForO365' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AtpPolicyForO365' try { $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AtpPolicyForO365' | diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 index e7a19162c0fb..f0963a92e1bf 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAuditLog.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardAuditLog { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'AuditLog' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AuditLog' Write-Host ($Settings | ConvertTo-Json) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 index 14d1b614fc2b..f78e231977c3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutoExpandArchive.ps1 @@ -28,6 +28,7 @@ function Invoke-CIPPStandardAutoExpandArchive { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'AutoExpandArchive' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AutoExpandArchive' $CurrentState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').AutoExpandingArchiveEnabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 index 67d733214ee7..8cd144e22988 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBookings.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardBookings { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'Bookings' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'Bookings' # Get state value using null-coalescing operator diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 index e27cfd2b32ee..ab5e26c6fbce 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardCloudMessageRecall.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardCloudMessageRecall { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'CloudMessageRecall' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'CloudMessageRecall' # Get state value using null-coalescing operator diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 index 390d80c25f71..880cce989772 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardConditionalAccessTemplate { #> param($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'ConditionalAccess' + Test-CIPPStandardLicense -StandardName 'ConditionalAccessTemplate' -TenantFilter $Tenant -RequiredCapabilities @('AAD_PREMIUM', 'AAD_PREMIUM_P2') if ($Settings.remediate -eq $true) { $AllCAPolicies = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies?$top=999' -tenantid $Tenant @@ -59,6 +60,6 @@ function Invoke-CIPPStandardConditionalAccessTemplate { } else { $fieldValue = $MissingPolicies -join ', ' } - Set-CIPPStandardsCompareField -FieldName 'standards.GroupTemplate' -FieldValue $fieldValue -Tenant $Tenant + Set-CIPPStandardsCompareField -FieldName 'standards.ConditionalAccessTemplate' -FieldValue $fieldValue -Tenant $Tenant } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 index 322dce4b8c8b..309dec178cc5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDelegateSentItems.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardDelegateSentItems { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DelegateSentItems' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access #$Rerun -Type Standard -Tenant $Tenant -API 'DelegateSentItems' -Settings $Settings diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 index 26a3f61a0d12..0b2a65d1b82a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployContactTemplates.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardDeployContactTemplates { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DeployContactTemplates' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $APIName = 'Standards' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 index 4f991c25be1f..df818e2903e2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeployMailContact.ps1 @@ -33,6 +33,7 @@ function Invoke-CIPPStandardDeployMailContact { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DeployMailContact' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access # Input validation if ([string]::IsNullOrWhiteSpace($Settings.DisplayName)) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 index 74ca49628813..e7d2c22ceb70 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAdditionalStorageProviders.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardDisableAdditionalStorageProviders { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableAdditionalStorageProviders' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableAdditionalStorageProviders' $AdditionalStorageProvidersState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OwaMailboxPolicy' -cmdParams @{Identity = 'OwaMailboxPolicy-Default' } -Select 'Identity, AdditionalStorageProvidersAvailable' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 index 9dba24fe66bb..5438ece03e33 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableBasicAuthSMTP.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardDisableBasicAuthSMTP { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableBasicAuthSMTP' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableBasicAuthSMTP' $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 index 33b0ab22ed63..cf319d6a3410 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExchangeOnlinePowerShell.ps1 @@ -33,6 +33,7 @@ function Invoke-CIPPStandardDisableExchangeOnlinePowerShell { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableExchangeOnlinePowerShell' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableExchangeOnlinePowerShell' try { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 index 71024b35dad2..4fb615e4de48 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableExternalCalendarSharing.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardDisableExternalCalendarSharing { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableExternalCalendarSharing' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableExternalCalendarSharing' $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-SharingPolicy' | Where-Object { $_.Default -eq $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 index f098710e5e57..3f63b9d87948 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableOutlookAddins.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardDisableOutlookAddins { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableOutlookAddins' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableOutlookAddins' $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-RoleAssignmentPolicy' | Where-Object { $_.IsDefault -eq $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 index dca3fceecf40..2dcbc902afd0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableResourceMailbox.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardDisableResourceMailbox { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableResourceMailbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableResourceMailbox' # Get all users that are able to be diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 index be6f3437ba1e..e1162ce67cb9 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableTNEF.ps1 @@ -30,7 +30,7 @@ function Invoke-CIPPStandardDisableTNEF { param ($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableTNEF' - + Test-CIPPStandardLicense -StandardName 'DisableTNEF' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-RemoteDomain' -cmdParams @{Identity = 'Default' } if ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 index e75909d94c41..e9a7ebbbe710 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 @@ -33,6 +33,7 @@ function Invoke-CIPPStandardEXODisableAutoForwarding { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'EXODisableAutoForwarding' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'EXODisableAutoForwarding' $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-HostedOutboundSpamFilterPolicy' -cmdParams @{Identity = 'Default' } -useSystemMailbox $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 index e596c065199b..ca9e7b77be9e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXOOutboundSpamLimits.ps1 @@ -35,6 +35,7 @@ function Invoke-CIPPStandardEXOOutboundSpamLimits { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'EXOOutboundSpamLimits' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access # Make sure it handles the frontend being both autocomplete and a text field $ActionWhenThresholdReached = $Settings.ActionWhenThresholdReached.value ?? $Settings.ActionWhenThresholdReached diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 index 729c765dcc9f..dc3a11f960ef 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableCustomerLockbox.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardEnableCustomerLockbox { param($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'EnableCustomerLockbox' + Test-CIPPStandardLicense -StandardName 'EnableCustomerLockbox' -TenantFilter $Tenant -RequiredCapabilities @('CustomerLockbox') $CustomerLockboxStatus = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').CustomerLockboxEnabled if ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 index 4fe60d3f807d..1ad012205c4c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableLitigationHold.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardEnableLitigationHold { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'EnableLitigationHold' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'EnableLitigationHold' $MailboxesNoLitHold = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-Mailbox' -cmdParams @{ Filter = 'LitigationHoldEnabled -eq "False"' } -Select 'UserPrincipalName,PersistedCapabilities,LitigationHoldEnabled' | Where-Object { $_.PersistedCapabilities -contains 'BPOS_S_DlpAddOn' -or $_.PersistedCapabilities -contains 'BPOS_S_Enterprise' } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 index 9c948ccfda1a..8f8bffe8c893 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailTips.ps1 @@ -33,6 +33,7 @@ function Invoke-CIPPStandardEnableMailTips { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'EnableMailTips' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'EnableMailTips' $MailTipsState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig' | Select-Object MailTipsAllTipsEnabled, MailTipsExternalRecipientsTipsEnabled, MailTipsGroupMetricsEnabled, MailTipsLargeAudienceThreshold diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 index e23baaf3aeb7..a700051aa4d5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableMailboxAuditing.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardEnableMailboxAuditing { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'EnableMailboxAuditing' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'EnableMailboxAuditing' $AuditState = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').AuditDisabled diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 index 4ec2259debd7..40557d9e72ab 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEnableOnlineArchiving.ps1 @@ -28,6 +28,7 @@ function Invoke-CIPPStandardEnableOnlineArchiving { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'EnableOnlineArchiving' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'EnableOnlineArchiving' $MailboxPlans = @( 'ExchangeOnline', 'ExchangeOnlineEnterprise' ) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExConnector.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExConnector.ps1 index d32d5008f175..5cd6d0252cab 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExConnector.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExConnector.ps1 @@ -4,6 +4,7 @@ function Invoke-CIPPStandardExConnector { Internal #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'ExConnector' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'ExConnector' If ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 index d15885b99102..490799dba5f9 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardFocusedInbox.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardFocusedInbox { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'FocusedInbox' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'FocusedInbox' # Get state value using null-coalescing operator diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 index 9f93f49538c5..4e461388872e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGlobalQuarantineNotifications.ps1 @@ -30,7 +30,7 @@ function Invoke-CIPPStandardGlobalQuarantineNotifications { param ($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'GlobalQuarantineNotifications' - + Test-CIPPStandardLicense -StandardName 'GlobalQuarantineNotifications' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-QuarantinePolicy' -cmdParams @{ QuarantinePolicyType = 'GlobalQuarantinePolicy' } | Select-Object -ExcludeProperty '*data.type' # This might take the cake on ugly hacky stuff i've done, diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 index f38b0c36f194..b3f0dec77263 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardGroupTemplate.ps1 @@ -28,6 +28,7 @@ function Invoke-CIPPStandardGroupTemplate { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'GroupTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'GroupTemplate' $existingGroups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenant if ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 index d78b7dea48ce..6837f85b456f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMailboxRecipientLimits.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardMailboxRecipientLimits { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'MailboxRecipientLimits' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access # Input validation if ([Int32]$Settings.RecipientLimit -lt 0 -or [Int32]$Settings.RecipientLimit -gt 10000) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 index 42e34db49f3a..a450b1cfcb5d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -40,6 +40,7 @@ function Invoke-CIPPStandardMalwareFilterPolicy { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'MalwareFilterPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'MalwareFilterPolicy' $PolicyList = @('CIPP Default Malware Policy', 'Default Malware Policy') diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 index 4a46e10ac85c..31d6e06b558f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMessageExpiration.ps1 @@ -28,6 +28,7 @@ function Invoke-CIPPStandardMessageExpiration { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'MessageExpiration' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'MessageExpiration' $MessageExpiration = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-TransportConfig').messageExpiration diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 index bcf78c7d5f73..dcef35ba6fc7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOutBoundSpamAlert.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardOutBoundSpamAlert { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'OutBoundSpamAlert' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-HostedOutboundSpamFilterPolicy' -cmdParams @{ Identity = 'Default' } -useSystemMailbox $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 index 12bcc2280571..3544fa6f3b6f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishSimSpoofIntelligence.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardPhishSimSpoofIntelligence { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'PhishSimSpoofIntelligence' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access # Fetch current Phishing Simulations Spoof Intelligence domains and ensure it is correctly configured $DomainState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-TenantAllowBlockListSpoofItems' | Select-Object -Property Identity,SendingInfrastructure diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 index 742871deffa8..8d59019bd04c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishingSimulations.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardPhishingSimulations { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'PhishingSimulations' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $PolicyName = 'CIPPPhishSim' # Fetch current Phishing Simulations Policy settings and ensure it is correctly configured diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 index 2eb1b6222131..85e1d560ba4b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardProfilePhotos.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardProfilePhotos { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'ProfilePhotos' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access # Get state value using null-coalescing operator $StateValue = $Settings.state.value ?? $Settings.state diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 index 4f76c8f4378c..263cc6267802 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineTemplate.ps1 @@ -40,6 +40,7 @@ function Invoke-CIPPStandardQuarantineTemplate { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'QuarantineTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $APIName = 'Standards' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 index 77587b18e6ab..942757d4b6d1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRetentionPolicyTag.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardRetentionPolicyTag { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'RetentionPolicyTag' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $PolicyName = 'CIPP Deleted Items' $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-RetentionPolicyTag' | diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 index 178f2fe033c4..629271841f61 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRotateDKIM.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardRotateDKIM { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'RotateDKIM' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $DKIM = (New-ExoRequest -tenantid $tenant -cmdlet 'Get-DkimSigningConfig') | Where-Object { $_.Selector1KeySize -eq 1024 -and $_.Enabled -eq $true } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 index edd59d6cbaa4..06788c6859f1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeAttachmentPolicy.ps1 @@ -37,6 +37,7 @@ function Invoke-CIPPStandardSafeAttachmentPolicy { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SafeAttachmentPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant $ServicePlans = $ServicePlans.servicePlans.servicePlanName diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 index e360efddabf2..9150e3dd5437 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksPolicy.ps1 @@ -36,6 +36,7 @@ function Invoke-CIPPStandardSafeLinksPolicy { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SafeLinksPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $ServicePlans = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus?$select=servicePlans' -tenantid $Tenant $ServicePlans = $ServicePlans.servicePlans.servicePlanName diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 index 00d71e371b2b..1c570ee3518b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeLinksTemplatePolicy.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardSafeLinksTemplatePolicy { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SafeLinksTemplatePolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access Write-LogMessage -API 'Standards' -tenant $Tenant -message "Processing SafeLinks template with settings: $($Settings | ConvertTo-Json -Compress)" -sev Debug diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 index 748a62f8ce9c..736f7dcee5c3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSafeSendersDisable.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardSafeSendersDisable { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SafeSendersDisable' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access if ($Settings.remediate -eq $true) { try { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 index 8577b4368988..9f4b73d5bf89 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendFromAlias.ps1 @@ -29,11 +29,12 @@ function Invoke-CIPPStandardSendFromAlias { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SendFromAlias' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $CurrentInfo = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-OrganizationConfig').SendFromAliasEnabled if ($Settings.remediate -eq $true) { - if ($CurrentInfo -eq $false) { + if ($CurrentInfo -ne $true) { try { New-ExoRequest -tenantid $Tenant -cmdlet 'Set-OrganizationConfig' -cmdParams @{ SendFromAliasEnabled = $true } Write-LogMessage -API 'Standards' -tenant $tenant -message 'Send from alias enabled.' -sev Info diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 index 344f83bc00bc..eac18fe134af 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSendReceiveLimitTenant.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardSendReceiveLimitTenant { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SendReceiveLimitTenant' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access # Input validation if ([Int32]$Settings.SendLimit -lt 1 -or [Int32]$Settings.SendLimit -gt 150) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 index a92efc0b4b32..6575803545b6 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardShortenMeetings.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardShortenMeetings { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'ShortenMeetings' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access Write-Host "ShortenMeetings: $($Settings | ConvertTo-Json -Compress)" # Get state value using null-coalescing operator $scopeDefault = $Settings.ShortenEventScopeDefault.value ? $Settings.ShortenEventScopeDefault.value : $Settings.ShortenEventScopeDefault diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index c650c95ef858..e0b8f403788e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -51,6 +51,7 @@ function Invoke-CIPPStandardSpamFilterPolicy { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SpamFilterPolicy' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $PolicyName = 'CIPP Default Spam Filter Policy' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 index 076829cac9f3..2b3e72b02a66 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpoofWarn.ps1 @@ -33,6 +33,7 @@ function Invoke-CIPPStandardSpoofWarn { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SpoofWarn' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $CurrentInfo = (New-ExoRequest -tenantid $Tenant -cmdlet 'Get-ExternalInOutlook') diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 index 3353d39162a1..94a5964cff35 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsMeetingsByDefault' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsMeetingsByDefault' # Get state value using null-coalescing operator diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 index 8b6f21dbaf65..a9d06c550248 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTransportRuleTemplate.ps1 @@ -26,6 +26,7 @@ function Invoke-CIPPStandardTransportRuleTemplate { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TransportRuleTemplate' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TransportRuleTemplate' $existingRules = New-ExoRequest -ErrorAction SilentlyContinue -tenantid $Tenant -cmdlet 'Get-TransportRule' -useSystemMailbox $true if ($Settings.remediate -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 index 13cbb9562d82..29a8c8ce6267 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTwoClickEmailProtection.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardTwoClickEmailProtection { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TwoClickEmailProtection' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TwoClickEmailProtection' # Get state value using null-coalescing operator diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 index e1c2ee7ca8ac..75f1036c7d8f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardUserSubmissions { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'UserSubmissions' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'UserSubmissions' # Get state value using null-coalescing operator diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 index bce7cc1b2fe6..d40860653b59 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardcalDefault.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardcalDefault { param($Tenant, $Settings, $QueueItem) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'calDefault' + Test-CIPPStandardLicense -StandardName 'calDefault' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access # Get permissionLevel value using null-coalescing operator $permissionLevel = $Settings.permissionLevel.value ?? $Settings.permissionLevel diff --git a/fix-license-test.ps1 b/fix-license-test.ps1 new file mode 100644 index 000000000000..bb6e10576732 --- /dev/null +++ b/fix-license-test.ps1 @@ -0,0 +1,49 @@ +# Script to fix the Test-CIPPStandardLicense line in all CIPPStandard files +$StandardsPath = 'Modules/CIPPCore/Public/Standards' + +# Get all Invoke-CIPPStandard*.ps1 files +$AllStandardFiles = Get-ChildItem -Path $StandardsPath -Name 'Invoke-CIPPStandard*.ps1' +$ProcessedFiles = @() + +foreach ($File in $AllStandardFiles) { + $FilePath = Join-Path $StandardsPath $File + $Content = Get-Content $FilePath -Raw + + # Extract the standard name from the filename (remove "Invoke-CIPPStandard" and ".ps1") + $StandardName = $File -replace '^Invoke-CIPPStandard', '' -replace '\.ps1$', '' + + # Check if file has the incorrect Test-CIPPStandardLicense line + if ($Content -match 'Test-CIPPStandardLicense.*SendFromAlias') { + Write-Host "Fixing: $File (StandardName: $StandardName)" + + # Read the file content as lines + $Lines = Get-Content $FilePath + $NewLines = @() + + for ($i = 0; $i -lt $Lines.Count; $i++) { + $Line = $Lines[$i] + + # Replace the incorrect line with the correct one + if ($Line -match 'Test-CIPPStandardLicense.*SendFromAlias') { + $CorrectLine = " Test-CIPPStandardLicense -StandardName '$StandardName' -TenantFilter `$Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access" + $NewLines += $CorrectLine + Write-Host " Replaced line: $Line" + Write-Host " With: $CorrectLine" + } else { + $NewLines += $Line + } + } + + # Write the updated content back to the file + $NewLines | Set-Content $FilePath -Encoding UTF8 + $ProcessedFiles += $File + Write-Host " Updated: $File" + Write-Host "" + } +} + +Write-Host "Summary:" +Write-Host "Successfully fixed files: $($ProcessedFiles.Count)" +Write-Host "" +Write-Host "Fixed files:" +$ProcessedFiles | ForEach-Object { Write-Host " $_" } diff --git a/update-license-test.ps1 b/update-license-test.ps1 new file mode 100644 index 000000000000..ca33333c7e72 --- /dev/null +++ b/update-license-test.ps1 @@ -0,0 +1,52 @@ +# Script to add Test-CIPPStandardLicense line to all CIPPStandard files that use New-ExoRequest +$StandardsPath = 'Modules/CIPPCore/Public/Standards' +$LicenseTestLine = ' Test-CIPPStandardLicense -StandardName ''SendFromAlias'' -TenantFilter $Tenant -RequiredCapabilities @(''EXCHANGE_S_STANDARD'', ''EXCHANGE_S_ENTERPRISE'', ''EXCHANGE_LITE'') #No Foundation because that does not allow powershell access' + +# Get all Invoke-CIPPStandard*.ps1 files +$AllStandardFiles = Get-ChildItem -Path $StandardsPath -Name 'Invoke-CIPPStandard*.ps1' +$FilesToProcess = @() +$ProcessedFiles = @() + +foreach ($File in $AllStandardFiles) { + $FilePath = Join-Path $StandardsPath $File + $Content = Get-Content $FilePath -Raw + + # Check if file uses New-ExoRequest and doesn't already have Test-CIPPStandardLicense + if ($Content -match 'New-ExoRequest' -and $Content -notmatch 'Test-CIPPStandardLicense') { + $FilesToProcess += $FilePath + Write-Host "Processing: $File" + + # Read the file content as lines + $Lines = Get-Content $FilePath + $NewLines = @() + $ParamFound = $false + + for ($i = 0; $i -lt $Lines.Count; $i++) { + $Line = $Lines[$i] + $NewLines += $Line + + # Look for the param line and add license test after it + if ($Line -match '^\s*param\(\$Tenant,\s*\$Settings\)' -and -not $ParamFound) { + $NewLines += $LicenseTestLine + $ParamFound = $true + } + } + + if ($ParamFound) { + # Write the updated content back to the file + $NewLines | Set-Content $FilePath -Encoding UTF8 + $ProcessedFiles += $File + Write-Host "Updated: $File" + } else { + Write-Host "Warning: Could not find param line in $File" + } + } +} + +Write-Host "" +Write-Host "Summary:" +Write-Host "Total files that needed processing: $($FilesToProcess.Count)" +Write-Host "Successfully processed files: $($ProcessedFiles.Count)" +Write-Host "" +Write-Host "Processed files:" +$ProcessedFiles | ForEach-Object { Write-Host " $_" } From e3b8e7adcfd8ee4f577ed3bf5abe682028059540 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:52:45 +0200 Subject: [PATCH 116/125] Sharepoint license checks --- .../Invoke-CIPPStandardAtpPolicyForO365.ps1 | 6 ++--- .../Invoke-CIPPStandardDefaultSharingLink.ps1 | 1 + ...voke-CIPPStandardDeletedUserRentention.ps1 | 1 + ...PStandardDisableAddShortcutsToOneDrive.ps1 | 1 + ...voke-CIPPStandardDisableM365GroupUsers.ps1 | 1 + .../Invoke-CIPPStandardDisableReshare.ps1 | 1 + ...IPPStandardDisableSharePointLegacyAuth.ps1 | 1 + ...voke-CIPPStandardDisableUserSiteCreate.ps1 | 1 + .../Invoke-CIPPStandardExcludedfileExt.ps1 | 1 + ...ndardRestrictThirdPartyStorageServices.ps1 | 5 ++-- .../Invoke-CIPPStandardSPAzureB2B.ps1 | 1 + .../Invoke-CIPPStandardSPDirectSharing.ps1 | 1 + ...e-CIPPStandardSPDisableLegacyWorkflows.ps1 | 1 + ...ke-CIPPStandardSPDisallowInfectedFiles.ps1 | 1 + .../Invoke-CIPPStandardSPEmailAttestation.ps1 | 1 + ...e-CIPPStandardSPExternalUserExpiration.ps1 | 1 + .../Invoke-CIPPStandardSPSyncButtonState.ps1 | 1 + ...IPPStandardSharePointMassDeletionAlert.ps1 | 25 ++++++++++--------- ...voke-CIPPStandardTenantDefaultTimezone.ps1 | 1 + .../Invoke-CIPPStandarddisableMacSync.ps1 | 1 + .../Invoke-CIPPStandardsharingCapability.ps1 | 1 + ...e-CIPPStandardsharingDomainRestriction.ps1 | 1 + .../Invoke-CIPPStandardunmanagedSync.ps1 | 1 + 23 files changed, 39 insertions(+), 17 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 index b8c8b0e10a26..2c0009db686b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAtpPolicyForO365.ps1 @@ -31,7 +31,7 @@ function Invoke-CIPPStandardAtpPolicyForO365 { #> param($Tenant, $Settings) - Test-CIPPStandardLicense -StandardName 'AtpPolicyForO365' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access + Test-CIPPStandardLicense -StandardName 'AtpPolicyForO365' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AtpPolicyForO365' try { $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AtpPolicyForO365' | @@ -42,8 +42,8 @@ function Invoke-CIPPStandardAtpPolicyForO365 { } } $StateIsCorrect = ($CurrentState.EnableATPForSPOTeamsODB -eq $true) -and - ($CurrentState.EnableSafeDocs -eq $true) -and - ($CurrentState.AllowSafeDocsOpen -eq $Settings.AllowSafeDocsOpen) + ($CurrentState.EnableSafeDocs -eq $true) -and + ($CurrentState.AllowSafeDocsOpen -eq $Settings.AllowSafeDocsOpen) if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 index 84dfa38a831a..25f0900ec3c5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultSharingLink.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardDefaultSharingLink { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DefaultSharingLink' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') # Determine the desired sharing link type (default to Internal if not specified) $DesiredSharingLinkType = $Settings.SharingLinkType.value ?? 'Internal' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 index f6cbe319cab9..4819b976a842 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardDeletedUserRentention { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DeletedUserRentention' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DeletedUserRetention' $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 index cf37d42fa516..ece57aabcd53 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableAddShortcutsToOneDrive.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardDisableAddShortcutsToOneDrive { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableAddShortcutsToOneDrive' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableAddShortcutsToOneDrive' $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object _ObjectIdentity_, TenantFilter, DisableAddToOneDrive diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 index f6fc18ff4b30..b5089fd60555 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableM365GroupUsers.ps1 @@ -28,6 +28,7 @@ function Invoke-CIPPStandardDisableM365GroupUsers { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableM365GroupUsers' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableM365GroupUsers' $CurrentState = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/settings' -tenantid $tenant) | Where-Object -Property displayname -EQ 'Group.unified' diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 index c6aa08a66250..52b81c6fd596 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableReshare.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardDisableReshare { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableReshare' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableReshare' $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 index 5e63504dccea..8d7aea7e0ffd 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardDisableSharePointLegacyAuth { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableSharePointLegacyAuth' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableSharePointLegacyAuth' $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings?$select=isLegacyAuthProtocolsEnabled' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 index 144eec1b1fdf..8e849780f752 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableUserSiteCreate.ps1 @@ -28,6 +28,7 @@ function Invoke-CIPPStandardDisableUserSiteCreate { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DisableUserSiteCreate' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'DisableUserSiteCreate' $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 index bec921cca4cf..c7ebbb9b4285 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardExcludedfileExt.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardExcludedfileExt { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'ExcludedfileExt' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'ExcludedfileExt' $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 index 9ee55cba203d..d67437a3412f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardRestrictThirdPartyStorageServices.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardRestrictThirdPartyStorageServices { param ($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'RestrictThirdPartyStorageServices' + Test-CIPPStandardLicense -StandardName 'ThirdPartyStorageServicesRestricted' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $AppId = 'c1f33bc0-bdb4-4248-ba9b-096807ddb43e' $Uri = "https://graph.microsoft.com/beta/servicePrincipals?`$filter=appId eq '$AppId'" @@ -40,7 +41,7 @@ function Invoke-CIPPStandardRestrictThirdPartyStorageServices { } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'Standards' -tenant $Tenant -message "Could not get current state for Microsoft 365 on the web service principal. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage - Return + return } if ($Settings.remediate -eq $true) { @@ -59,7 +60,7 @@ function Invoke-CIPPStandardRestrictThirdPartyStorageServices { # Normal /servicePrincipal/AppId does not find the service principal, so gotta use the Upsert method. Also handles if the service principal does not exist nicely. # https://learn.microsoft.com/en-us/graph/api/serviceprincipal-upsert?view=graph-rest-beta&tabs=http $UpdateUri = "https://graph.microsoft.com/beta/servicePrincipals(appId='$AppId')" - $null = New-GraphPostRequest -Uri $UpdateUri -Body $DisableBody -TenantID $Tenant -Type PATCH -AddedHeaders @{'Prefer' = 'create-if-missing'} + $null = New-GraphPostRequest -Uri $UpdateUri -Body $DisableBody -TenantID $Tenant -Type PATCH -AddedHeaders @{'Prefer' = 'create-if-missing' } # Refresh the current state after disabling $CurrentState = New-GraphGetRequest -Uri $Uri -tenantid $Tenant | Select-Object displayName, accountEnabled, appId diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 index 4c940025bfca..db4febf857ba 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPAzureB2B.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardSPAzureB2B { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SPAzureB2B' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, EnableAzureADB2BIntegration diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 index 9538b3e34325..8b73d0189817 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDirectSharing.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardSPDirectSharing { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SPDirectSharing' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'This standard has been deprecated in favor of the "Set Default Sharing Link Settings" standard. Please update your standards to use new standard. However this will continue to function.' -Sev Alert diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 index 2ec1c824be30..2e8193c31f37 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisableLegacyWorkflows.ps1 @@ -27,6 +27,7 @@ function Invoke-CIPPStandardSPDisableLegacyWorkflows { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SPDisableLegacyWorkflows' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property * diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 index f6771ea8bb0d..0be5d526cc8d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPDisallowInfectedFiles.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardSPDisallowInfectedFiles { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SPDisallowInfectedFiles' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, DisallowInfectedFileDownload diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 index 507aae2c175e..9d2e3e10f2f0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardSPEmailAttestation { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SPEmailAttestation' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, EmailAttestationReAuthDays, EmailAttestationRequired diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 index 1c34c563ab82..092871783f84 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPExternalUserExpiration.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardSPExternalUserExpiration { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SPExternalUserExpiration' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, ExternalUserExpireInDays, ExternalUserExpirationRequired diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 index ba222bb08412..da7dec54aa6a 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPSyncButtonState.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardSPSyncButtonState { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'SPSyncButtonState' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object _ObjectIdentity_, TenantFilter, HideSyncButtonOnDocLib diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 index cad89dfd0049..7418a0316679 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert { #> param ($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DeletedUserRentention' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $PolicyName = 'CIPP SharePoint mass deletion of files by a user' @@ -42,9 +43,9 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert { $MissingEmailsInSettings = $Settings.NotifyUser.value | Where-Object { $_ -notin $CurrentState.NotifyUser } $StateIsCorrect = ($EmailsOutsideSettings.Count -eq 0) -and - ($MissingEmailsInSettings.Count -eq 0) -and - ($CurrentState.Threshold -eq $Settings.Threshold) -and - ($CurrentState.TimeWindow -eq $Settings.TimeWindow) + ($MissingEmailsInSettings.Count -eq 0) -and + ($CurrentState.Threshold -eq $Settings.Threshold) -and + ($CurrentState.TimeWindow -eq $Settings.TimeWindow) $CompareField = [PSCustomObject]@{ 'Threshold' = $CurrentState.Threshold @@ -52,10 +53,10 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert { 'NotifyUser' = $CurrentState.NotifyUser -join ', ' } - If ($Settings.remediate -eq $true) { - If ($StateIsCorrect -eq $true) { + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'SharePoint mass deletion of files alert is configured correctly' -sev Info - } Else { + } else { $cmdParams = @{ 'NotifyUser' = $Settings.NotifyUser.value 'Category' = 'DataGovernance' @@ -66,7 +67,7 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert { 'TimeWindow' = $Settings.TimeWindow } - If ($CurrentState.Name -eq $PolicyName) { + if ($CurrentState.Name -eq $PolicyName) { try { $cmdParams['Identity'] = $PolicyName New-ExoRequest -TenantId $Tenant -cmdlet 'Set-ProtectionAlert' -Compliance -cmdParams $cmdParams -UseSystemMailbox $true @@ -75,7 +76,7 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to configure SharePoint mass deletion of files alert. Error: $ErrorMessage" -sev Error } - } Else { + } else { try { $cmdParams['name'] = $PolicyName $cmdParams['ThreatType'] = 'Activity' @@ -90,16 +91,16 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert { } } - If ($Settings.alert -eq $true) { - If ($StateIsCorrect -eq $true) { + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'SharePoint mass deletion of files alert is enabled' -sev Info - } Else { + } else { Write-StandardsAlert -message 'SharePoint mass deletion of files alert is disabled' -object $CompareField -tenant $tenant -standardName 'SharePointMassDeletionAlert' -standardId $Settings.standardId Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'SharePoint mass deletion of files alert is disabled' -sev Info } } - If ($Settings.report -eq $true) { + if ($Settings.report -eq $true) { $FieldValue = $StateIsCorrect ? $true : $CompareField Set-CIPPStandardsCompareField -FieldName 'standards.SharePointMassDeletionAlert' -FieldValue $FieldValue -TenantFilter $Tenant Add-CIPPBPAField -FieldName 'SharePointMassDeletionAlert' -FieldValue [bool]$StateIsCorrect -StoreAs bool -Tenant $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 index 1365cdc31f75..d7b7180e149b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTenantDefaultTimezone.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardTenantDefaultTimezone { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TenantDefaultTimezone' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TenantDefaultTimezone' $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 index 6f3c1f886b16..68c3632a8abe 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandarddisableMacSync.ps1 @@ -28,6 +28,7 @@ function Invoke-CIPPStandarddisableMacSync { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'disableMacSync' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'disableMacSync' $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 index c65bf679b010..74db530e5158 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingCapability.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardsharingCapability { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'sharingCapability' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentInfo = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 index 3fa2f071bda4..11d4313f5504 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardsharingDomainRestriction { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'sharingDomainRestriction' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 index 57fe9a533670..028325433a84 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardunmanagedSync { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'unmanagedSync' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'unmanagedSync' $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object _ObjectIdentity_, TenantFilter, ConditionalAccessPolicy From 869b96f6d2ceb1e89d5183b91c79714eb5a7a4ca Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:01:14 +0200 Subject: [PATCH 117/125] Add License checks for intune --- .../Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 | 1 + .../Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 | 1 + .../Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 | 1 + .../CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 | 1 + .../Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 | 1 + .../Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 | 1 + .../Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 | 1 + .../Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 | 1 + .../Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 | 1 + .../Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 | 4 ++-- 10 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 index cc7c578585bf..e5cd404006f2 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDefaultPlatformRestrictions.ps1 @@ -38,6 +38,7 @@ function Invoke-CIPPStandardDefaultPlatformRestrictions { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'DefaultPlatformRestrictions' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') try { $CurrentState = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations?`$expand=assignments&orderBy=priority&`$filter=deviceEnrollmentConfigurationType eq 'SinglePlatformRestriction'" -tenantID $Tenant -AsApp $true | diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 index c1d0bdfe93ca..2d041d1d4efa 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneComplianceSettings.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardIntuneComplianceSettings { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'IntuneComplianceSettings' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/settings' -tenantid $Tenant | Select-Object secureByDefault, deviceComplianceCheckinThresholdDays diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index ac90ae92bc35..ed722cf5f482 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardIntuneTemplate { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'IntuneTemplate' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'intuneTemplate' $Table = Get-CippTable -tablename 'templates' $Filter = "PartitionKey eq 'IntuneTemplate'" diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 index e49351739558..2605df158452 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMDMScope.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardMDMScope { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'MDMScope' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') $CurrentInfo = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/mobileDeviceManagementPolicies/0000000a-0000-0000-c000-000000000000?$expand=includedGroups' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 index e85947eca52c..d7ec04d2c1d9 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardStaleEntraDevices.ps1 @@ -32,6 +32,7 @@ function Invoke-CIPPStandardStaleEntraDevices { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'StaleEntraDevices' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') # Get all Entra devices $AllDevices = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/devices' -tenantid $Tenant | Where-Object { $null -ne $_.approximateLastSignInDateTime } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 index cc0cb204840a..f9c01147a142 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneBrandingProfile.ps1 @@ -38,6 +38,7 @@ function Invoke-CIPPStandardintuneBrandingProfile { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'intuneBrandingProfile' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'intuneBrandingProfile' $CurrentState = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/intuneBrandingProfiles/c3a59481-1bf2-46ce-94b3-66eec07a8d60' -tenantid $Tenant -AsApp $true diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 index eb9e1693da54..7855d7a4295e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceReg.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardintuneDeviceReg { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'intuneDeviceReg' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'intuneDeviceReg' $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 index 77e6e34180f5..4339901ed20d 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneDeviceRetirementDays.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardintuneDeviceRetirementDays { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'intuneDeviceRetirementDays' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'intuneDeviceRetirementDays' $CurrentInfo = (New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/deviceManagement/managedDeviceCleanupRules' -tenantid $Tenant) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 index 43a5da1d2d24..d10df96a7fd5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardintuneRequireMFA.ps1 @@ -27,6 +27,7 @@ function Invoke-CIPPStandardintuneRequireMFA { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'intuneRequireMFA' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'intuneRequireMFA' $PreviousSetting = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/policies/deviceRegistrationPolicy' -tenantid $Tenant diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 index 028325433a84..651a5c22ca85 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardunmanagedSync.ps1 @@ -30,7 +30,7 @@ function Invoke-CIPPStandardunmanagedSync { #> param($Tenant, $Settings) - Test-CIPPStandardLicense -StandardName 'unmanagedSync' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + Test-CIPPStandardLicense -StandardName 'unmanagedSync' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'unmanagedSync' $CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object _ObjectIdentity_, TenantFilter, ConditionalAccessPolicy @@ -39,7 +39,7 @@ function Invoke-CIPPStandardunmanagedSync { $Label = $Settings.state.label ?? 'Block Access' # Default label if not set, for pre v8.0.3 standard compatibility $StateIsCorrect = ($CurrentState.ConditionalAccessPolicy -eq $WantedState) - If ($Settings.remediate -eq $true) { + if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -tenant $Tenant -message "Sync for unmanaged devices is already correctly set to: $Label" -sev Info From cac0ef7fc5e8df299632a8467ca2a1199498ef40 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:15:31 +0200 Subject: [PATCH 118/125] autopilot profiles --- .../Invoke-CIPPStandardAutopilotProfile.ps1 | 5 +++-- .../Invoke-CIPPStandardAutopilotStatusPage.ps1 | 17 +++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 index 041628368288..9639d3c93c5e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotProfile.ps1 @@ -40,6 +40,7 @@ function Invoke-CIPPStandardAutopilotProfile { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'AutopilotProfile' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') # Get the current configuration try { @@ -47,8 +48,8 @@ function Invoke-CIPPStandardAutopilotProfile { $DisplayName = Get-CIPPTextReplacement -Text $Settings.DisplayName -TenantFilter $Tenant $CurrentConfig = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles' -tenantid $Tenant | - Where-Object { $_.displayName -eq $DisplayName } | - Select-Object -Property displayName, description, deviceNameTemplate, language, enableWhiteGlove, extractHardwareHash, outOfBoxExperienceSetting, preprovisioningAllowed + Where-Object { $_.displayName -eq $DisplayName } | + Select-Object -Property displayName, description, deviceNameTemplate, language, enableWhiteGlove, extractHardwareHash, outOfBoxExperienceSetting, preprovisioningAllowed if ($Settings.NotLocalAdmin -eq $true) { $userType = 'Standard' } else { $userType = 'Administrator' } if ($Settings.SelfDeployingMode -eq $true) { $DeploymentMode = 'shared' } else { $DeploymentMode = 'singleUser' } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 index e8957cb84d8c..bd6f0e055860 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAutopilotStatusPage.ps1 @@ -36,6 +36,7 @@ function Invoke-CIPPStandardAutopilotStatusPage { https://docs.cipp.app/user-documentation/tenant/standards/list-standards #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'AutopilotStatusPage' -TenantFilter $Tenant -RequiredCapabilities @('INTUNE_A', 'MDM_Services', 'EMS', 'SCCM', 'MICROSOFTINTUNEPLAN1') # Get current Autopilot enrollment status page configuration try { @@ -43,13 +44,13 @@ function Invoke-CIPPStandardAutopilotStatusPage { Select-Object -Property id, displayName, priority, showInstallationProgress, blockDeviceSetupRetryByUser, allowDeviceResetOnInstallFailure, allowLogCollectionOnInstallFailure, customErrorMessage, installProgressTimeoutInMinutes, allowDeviceUseOnInstallFailure, trackInstallProgressForAutopilotOnly $StateIsCorrect = ($CurrentConfig.installProgressTimeoutInMinutes -eq $Settings.TimeOutInMinutes) -and - ($CurrentConfig.customErrorMessage -eq $Settings.ErrorMessage) -and - ($CurrentConfig.showInstallationProgress -eq $Settings.ShowProgress) -and - ($CurrentConfig.allowLogCollectionOnInstallFailure -eq $Settings.EnableLog) -and - ($CurrentConfig.trackInstallProgressForAutopilotOnly -eq $Settings.OBEEOnly) -and - ($CurrentConfig.blockDeviceSetupRetryByUser -eq !$Settings.BlockDevice) -and - ($CurrentConfig.allowDeviceResetOnInstallFailure -eq $Settings.AllowReset) -and - ($CurrentConfig.allowDeviceUseOnInstallFailure -eq $Settings.AllowFail) + ($CurrentConfig.customErrorMessage -eq $Settings.ErrorMessage) -and + ($CurrentConfig.showInstallationProgress -eq $Settings.ShowProgress) -and + ($CurrentConfig.allowLogCollectionOnInstallFailure -eq $Settings.EnableLog) -and + ($CurrentConfig.trackInstallProgressForAutopilotOnly -eq $Settings.OBEEOnly) -and + ($CurrentConfig.blockDeviceSetupRetryByUser -eq !$Settings.BlockDevice) -and + ($CurrentConfig.allowDeviceResetOnInstallFailure -eq $Settings.AllowReset) -and + ($CurrentConfig.allowDeviceUseOnInstallFailure -eq $Settings.AllowFail) } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to check Autopilot Enrollment Status Page: $ErrorMessage" -sev Error @@ -57,7 +58,7 @@ function Invoke-CIPPStandardAutopilotStatusPage { } # Remediate if the state is not correct - If ($Settings.remediate -eq $true) { + if ($Settings.remediate -eq $true) { try { $Parameters = @{ TenantFilter = $Tenant From 682ff58b903df45c11ba0a806d11475aea29d2c9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:26:55 +0200 Subject: [PATCH 119/125] Teams update --- ...voke-CIPPStandardTeamsEmailIntegration.ps1 | 1 + .../Invoke-CIPPStandardTeamsEnrollUser.ps1 | 1 + ...-CIPPStandardTeamsExternalAccessPolicy.ps1 | 1 + ...e-CIPPStandardTeamsExternalFileSharing.ps1 | 1 + ...PPStandardTeamsFederationConfiguration.ps1 | 1 + ...e-CIPPStandardTeamsGlobalMeetingPolicy.ps1 | 1 + .../Invoke-CIPPStandardTeamsGuestAccess.ps1 | 1 + ...tandardTeamsMeetingRecordingExpiration.ps1 | 1 + ...e-CIPPStandardTeamsMeetingVerification.ps1 | 1 + ...oke-CIPPStandardTeamsMeetingsByDefault.ps1 | 4 +- ...nvoke-CIPPStandardTeamsMessagingPolicy.ps1 | 1 + add-teams-license-test.ps1 | 87 +++++++++++++++++++ teams-standards.txt | 27 ++++++ 13 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 add-teams-license-test.ps1 create mode 100644 teams-standards.txt diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 index d54b70a00796..63c7d1e182a3 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEmailIntegration.ps1 @@ -30,6 +30,7 @@ Function Invoke-CIPPStandardTeamsEmailIntegration { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsEmailIntegration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsEmailIntegration' $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsClientConfiguration' -CmdParams @{Identity = 'Global' } | Select-Object AllowEmailIntoChannel diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 index ffc9d8ddc652..11345c7922a0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsEnrollUser.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardTeamsEnrollUser { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsEnrollUser' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') # Get EnrollUserOverride value using null-coalescing operator $enrollUserOverride = $Settings.EnrollUserOverride.value ?? $Settings.EnrollUserOverride diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 index 0022db523107..6b5e05f56058 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardTeamsExternalAccessPolicy { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsExternalAccessPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsExternalAccessPolicy' $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsExternalAccessPolicy' -CmdParams @{Identity = 'Global' } | Select-Object * diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 index 3230484f898c..b705148469e4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 @@ -34,6 +34,7 @@ function Invoke-CIPPStandardTeamsExternalFileSharing { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsExternalFileSharing' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsExternalFileSharing' Write-Host "TeamsExternalFileSharing: $($Settings | ConvertTo-Json)" $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsClientConfiguration' | Select-Object AllowGoogleDrive, AllowShareFile, AllowBox, AllowDropBox, AllowEgnyte diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 index d52c75cef036..3857fb601196 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsFederationConfiguration.ps1 @@ -31,6 +31,7 @@ function Invoke-CIPPStandardTeamsFederationConfiguration { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsFederationConfiguration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsFederationConfiguration' $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTenantFederationConfiguration' -CmdParams @{Identity = 'Global' } | Select-Object * diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 index 7c949243b8d8..e50817a240c0 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 @@ -34,6 +34,7 @@ function Invoke-CIPPStandardTeamsGlobalMeetingPolicy { ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsGlobalMeetingPolicy' param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsGlobalMeetingPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsMeetingPolicy' -CmdParams @{Identity = 'Global' } | Select-Object AllowAnonymousUsersToJoinMeeting, AllowAnonymousUsersToStartMeeting, AutoAdmittedUsers, AllowPSTNUsersToBypassLobby, MeetingChatEnabledType, DesignatedPresenterRoleMode, AllowExternalParticipantGiveRequestControl $MeetingChatEnabledType = $Settings.MeetingChatEnabledType.value ?? $Settings.MeetingChatEnabledType diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 index 715ef12c3386..cf15105b9a3c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 @@ -29,6 +29,7 @@ function Invoke-CIPPStandardTeamsGuestAccess { #> param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsGuestAccess' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsClientConfiguration' -CmdParams @{Identity = 'Global' } | Select-Object AllowGuestUser diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 index b201ce5477fd..0fc33957ed41 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 @@ -30,6 +30,7 @@ function Invoke-CIPPStandardTeamsMeetingRecordingExpiration { ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsMeetingRecordingExpiration' param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsMeetingRecordingExpiration' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') # Input validation $ExpirationDays = try { [int64]$Settings.ExpirationDays } catch { Write-Warning "Invalid ExpirationDays value provided: $($Settings.ExpirationDays)"; return } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 index c7938c496d19..f91527604974 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingVerification.ps1 @@ -33,6 +33,7 @@ function Invoke-CIPPStandardTeamsMeetingVerification { ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsMeetingVerification' param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsMeetingVerification' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsMeetingPolicy' -CmdParams @{Identity = 'Global' } | Select-Object CaptchaVerificationForMeetingJoin $CaptchaVerificationForMeetingJoin = $Settings.CaptchaVerificationForMeetingJoin.value ?? $Settings.CaptchaVerificationForMeetingJoin $StateIsCorrect = ($CurrentState.CaptchaVerificationForMeetingJoin -eq $CaptchaVerificationForMeetingJoin) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 index 94a5964cff35..c9921642398e 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 @@ -42,7 +42,7 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault { # Input validation if (([string]::IsNullOrWhiteSpace($state) -or $state -eq 'Select a value') -and ($Settings.remediate -eq $true -or $Settings.alert -eq $true)) { Write-LogMessage -API 'Standards' -tenant $Tenant -message 'TeamsMeetingsByDefault: Invalid state parameter set' -sev Error - Return + return } if ($Settings.remediate -eq $true) { @@ -65,7 +65,7 @@ function Invoke-CIPPStandardTeamsMeetingsByDefault { if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -tenant $Tenant -message "The tenant TeamsMeetingsByDefault is set correctly to $state" -sev Info } else { - Write-StandardsAlert -message "The tenant TeamsMeetingsByDefault is not set correctly to $state" -object @{CurrentState = $CurrentState; WantedState = $WantedState} -tenant $Tenant -standardName 'TeamsMeetingsByDefault' -standardId $Settings.standardId + Write-StandardsAlert -message "The tenant TeamsMeetingsByDefault is not set correctly to $state" -object @{CurrentState = $CurrentState; WantedState = $WantedState } -tenant $Tenant -standardName 'TeamsMeetingsByDefault' -standardId $Settings.standardId Write-LogMessage -API 'Standards' -tenant $Tenant -message "The tenant TeamsMeetingsByDefault is not set correctly to $state" -sev Info } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 index a1bf1aa2ad49..f39274fce83b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsMessagingPolicy.ps1 @@ -38,6 +38,7 @@ Function Invoke-CIPPStandardTeamsMessagingPolicy { ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'TeamsMessagingPolicy' param($Tenant, $Settings) + Test-CIPPStandardLicense -StandardName 'TeamsMessagingPolicy' -TenantFilter $Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard') $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsMessagingPolicy' -CmdParams @{Identity = 'Global' } if ($null -eq $Settings.AllowOwnerDeleteMessage) { $Settings.AllowOwnerDeleteMessage = $CurrentState.AllowOwnerDeleteMessage } diff --git a/add-teams-license-test.ps1 b/add-teams-license-test.ps1 new file mode 100644 index 000000000000..52ad80500e0e --- /dev/null +++ b/add-teams-license-test.ps1 @@ -0,0 +1,87 @@ +# Script to add Teams license test to all Teams related standards +$StandardsPath = 'Modules/CIPPCore/Public/Standards' + +# List of all Teams related standards +$TeamsStandards = @( + 'Invoke-CIPPStandardTeamsEmailIntegration.ps1', + 'Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1', + 'Invoke-CIPPStandardTeamsMeetingsByDefault.ps1', + 'Invoke-CIPPStandardTeamsMessagingPolicy.ps1', + 'Invoke-CIPPStandardTeamsMeetingVerification.ps1', + 'Invoke-CIPPStandardTeamsGuestAccess.ps1', + 'Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1', + 'Invoke-CIPPStandardTeamsFederationConfiguration.ps1', + 'Invoke-CIPPStandardTeamsExternalFileSharing.ps1', + 'Invoke-CIPPStandardTeamsEnrollUser.ps1', + 'Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1' +) + +$ProcessedFiles = @() +$SkippedFiles = @() + +foreach ($File in $TeamsStandards) { + $FilePath = Join-Path $StandardsPath $File + + if (-not (Test-Path $FilePath)) { + Write-Host "File not found: $File" -ForegroundColor Yellow + $SkippedFiles += $File + continue + } + + $Content = Get-Content $FilePath -Raw + $StandardName = $File -replace '^Invoke-CIPPStandard', '' -replace '\.ps1$', '' + + # Check if file already has a Test-CIPPStandardLicense line + if ($Content -match 'Test-CIPPStandardLicense') { + Write-Host "Skipping $File - already has license test" -ForegroundColor Yellow + $SkippedFiles += $File + continue + } + + # Check if file has param($Tenant, $Settings) line + if ($Content -notmatch 'param\(\$Tenant,\s*\$Settings\)') { + Write-Host "Skipping $File - no param block found" -ForegroundColor Yellow + $SkippedFiles += $File + continue + } + + Write-Host "Processing: $File (StandardName: $StandardName)" -ForegroundColor Green + + # Read the file content as lines + $Lines = Get-Content $FilePath + $NewLines = @() + + for ($i = 0; $i -lt $Lines.Count; $i++) { + $Line = $Lines[$i] + $NewLines += $Line + + # Add license test line after param($Tenant, $Settings) + if ($Line -match 'param\(\$Tenant,\s*\$Settings\)') { + $LicenseTestLine = " Test-CIPPStandardLicense -StandardName '$StandardName' -TenantFilter `$Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard')" + $NewLines += $LicenseTestLine + Write-Host " Added license test line after param block" -ForegroundColor Cyan + } + } + + # Write the updated content back to the file + $NewLines | Set-Content $FilePath -Encoding UTF8 + $ProcessedFiles += $File + Write-Host " Updated: $File" -ForegroundColor Green + Write-Host "" +} + +Write-Host "Summary:" -ForegroundColor Magenta +Write-Host "Successfully processed files: $($ProcessedFiles.Count)" -ForegroundColor Green +Write-Host "Skipped files: $($SkippedFiles.Count)" -ForegroundColor Yellow +Write-Host "" + +if ($ProcessedFiles.Count -gt 0) { + Write-Host "Processed files:" -ForegroundColor Green + $ProcessedFiles | ForEach-Object { Write-Host " $_" -ForegroundColor Green } + Write-Host "" +} + +if ($SkippedFiles.Count -gt 0) { + Write-Host "Skipped files:" -ForegroundColor Yellow + $SkippedFiles | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow } +} diff --git a/teams-standards.txt b/teams-standards.txt new file mode 100644 index 000000000000..4c1297c2d044 --- /dev/null +++ b/teams-standards.txt @@ -0,0 +1,27 @@ +Teams Related Standards Found: + +Files with "Teams" in filename or content: +- Invoke-CIPPStandardTeamsEmailIntegration.ps1 - Teams Email Integration settings +- Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 - Define Global Meeting Policy for Teams +- Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 - Set Teams Meetings by default state +- Invoke-CIPPStandardTeamsMessagingPolicy.ps1 - Global Messaging Policy for Microsoft Teams +- Invoke-CIPPStandardTeamsMeetingVerification.ps1 - Meeting Verification (ReCaptcha) for Teams +- Invoke-CIPPStandardTeamsGuestAccess.ps1 - Allow guest users in Teams +- Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 - Set Teams Meeting Recording Expiration +- Invoke-CIPPStandardTeamsFederationConfiguration.ps1 - Federation Configuration for Microsoft Teams +- Invoke-CIPPStandardTeamsExternalFileSharing.ps1 - Define approved cloud storage services for external file sharing in Teams +- Invoke-CIPPStandardTeamsEnrollUser.ps1 - Teams voice and face enrollment settings +- Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 - External Access Settings for Microsoft Teams + +Other files that mention Teams in descriptions: +- Invoke-CIPPStandardAtpPolicyForO365.ps1 - Enables Defender for Office 365 for SharePoint, OneDrive and Microsoft Teams +- Invoke-CIPPStandardSafeLinksPolicy.ps1 - Safe Links policy for Email, Office, and Teams +- Invoke-CIPPStandardDisableM365GroupUsers.ps1 - Restricts M365 group creation (affects Teams creation) +- Invoke-CIPPStandardDisableUserSiteCreate.ps1 - Disables ability to fully create teams +- Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 - Breaks ability to create Teams + +These Teams standards require the following license capabilities: +- MCOSTANDARD +- MCOEV +- MCOIMP +- TEAMS1 From 8d45ef1fa25f185d4d70cc6b41b854f8be2314e0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:31:50 +0200 Subject: [PATCH 120/125] combined alignment score --- .../Standards/Invoke-ListTenantAlignment.ps1 | 1 + .../Functions/Get-CIPPTenantAlignment.ps1 | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 index 65c644a24593..4f4a48dd6766 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1 @@ -25,6 +25,7 @@ function Invoke-ListTenantAlignment { standardId = $_.StandardId alignmentScore = $_.AlignmentScore LicenseMissingPercentage = $_.LicenseMissingPercentage + combinedAlignmentScore = $_.CombinedScore latestDataCollection = $_.LatestDataCollection } } diff --git a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 index be4d2740c85e..5671b2a79632 100644 --- a/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 +++ b/Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1 @@ -179,7 +179,7 @@ function Get-CIPPTenantAlignment { } $IsCompliant = ($Value -eq $true) - $IsLicenseMissing = ($Value -is [string] -and $Value -like "License Missing:*") + $IsLicenseMissing = ($Value -is [string] -and $Value -like 'License Missing:*') if ($IsReportingDisabled) { $ComplianceStatus = 'Reporting Disabled' @@ -233,18 +233,19 @@ function Get-CIPPTenantAlignment { } $Result = [PSCustomObject]@{ - TenantFilter = $TenantName - StandardName = $Template.templateName - StandardId = $Template.GUID - AlignmentScore = $AlignmentPercentage + TenantFilter = $TenantName + StandardName = $Template.templateName + StandardId = $Template.GUID + AlignmentScore = $AlignmentPercentage LicenseMissingPercentage = $LicenseMissingPercentage - CompliantStandards = $CompliantStandards - NonCompliantStandards = $NonCompliantStandards - LicenseMissingStandards = $LicenseMissingStandards - TotalStandards = $AllCount - ReportingDisabledCount = $ReportingDisabledStandardsCount - LatestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null } - ComparisonDetails = $ComparisonTable + CombinedScore = $AlignmentPercentage + $LicenseMissingPercentage + CompliantStandards = $CompliantStandards + NonCompliantStandards = $NonCompliantStandards + LicenseMissingStandards = $LicenseMissingStandards + TotalStandards = $AllCount + ReportingDisabledCount = $ReportingDisabledStandardsCount + LatestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null } + ComparisonDetails = $ComparisonTable } $Results.Add($Result) From c8f257f679111f966dc9547ee31267a23ed192e4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:57:06 +0200 Subject: [PATCH 121/125] DKIM --- .../CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 index 8e119023f392..acb159e3f992 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAddDKIM.ps1 @@ -32,7 +32,7 @@ function Invoke-CIPPStandardAddDKIM { param($Tenant, $Settings) #$Rerun -Type Standard -Tenant $Tenant -API 'AddDKIM' -Settings $Settings - + Test-CIPPStandardLicense -StandardName 'AddDKIM' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $DkimRequest = @( @{ @@ -103,7 +103,7 @@ function Invoke-CIPPStandardAddDKIM { $NewDomains = $AllDomains | Where-Object { $DKIM.Domain -notcontains $_ } $SetDomains = $DKIM | Where-Object { $AllDomains -contains $_.Domain -and $_.Enabled -eq $false } - If ($Settings.remediate -eq $true) { + if ($Settings.remediate -eq $true) { if ($null -eq $NewDomains -and $null -eq $SetDomains) { Write-LogMessage -API 'Standards' -tenant $Tenant -message 'DKIM is already enabled for all available domains.' -sev Info From 9a2a8341cf6a70e397f502a1a24ae5640128c44a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:58:14 +0200 Subject: [PATCH 122/125] license update --- .../Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 index 782d847ec8a1..7be58341e296 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardQuarantineRequestAlert.ps1 @@ -29,7 +29,7 @@ function Invoke-CIPPStandardQuarantineRequestAlert { #> param ($Tenant, $Settings) - + Test-CIPPStandardLicense -StandardName 'QuarantineRequestAlert' -TenantFilter $Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access $PolicyName = 'CIPP User requested to release a quarantined message' $CurrentState = New-ExoRequest -TenantId $Tenant -cmdlet 'Get-ProtectionAlert' -Compliance | From 89b53e13cfdf48efea073e63ced634e253875d6f Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:59:29 +0200 Subject: [PATCH 123/125] license update --- .../Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 index 7418a0316679..770dfb0d8d72 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSharePointMassDeletionAlert.ps1 @@ -31,7 +31,7 @@ function Invoke-CIPPStandardSharePointMassDeletionAlert { #> param ($Tenant, $Settings) - Test-CIPPStandardLicense -StandardName 'DeletedUserRentention' -TenantFilter $Tenant -RequiredCapabilities @('SHAREPOINTWAC', 'SHAREPOINTSTANDARD', 'SHAREPOINTENTERPRISE', 'ONEDRIVE_BASIC', 'ONEDRIVE_ENTERPRISE') + Test-CIPPStandardLicense -StandardName 'DeletedUserRentention' -TenantFilter $Tenant -RequiredCapabilities @('RMS_S_PREMIUM2') $PolicyName = 'CIPP SharePoint mass deletion of files by a user' From 3073d32cd46111685ab3b67ac0cdb400b3493b74 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:50:34 +0200 Subject: [PATCH 124/125] delete scratch files and maintence --- .../CIPPCore/Public/New-CIPPBackupTask.ps1 | 2 +- .../Public/New-CIPPIntuneTemplate.ps1 | 1 - add-teams-license-test.ps1 | 87 ------------------- fix-license-test.ps1 | 49 ----------- teams-standards.txt | 27 ------ update-license-test.ps1 | 52 ----------- 6 files changed, 1 insertion(+), 217 deletions(-) delete mode 100644 add-teams-license-test.ps1 delete mode 100644 fix-license-test.ps1 delete mode 100644 teams-standards.txt delete mode 100644 update-license-test.ps1 diff --git a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 index 05f2b0added7..1814abc23fe0 100644 --- a/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPBackupTask.ps1 @@ -45,7 +45,7 @@ function New-CIPPBackupTask { 'https://graph.microsoft.com/beta/deviceManagement/windowsQualityUpdateProfiles' ) - $Policies = foreach ($url in $GraphURLS) { + foreach ($url in $GraphURLS) { try { $Policies = New-GraphGetRequest -uri "$($url)" -tenantid $TenantFilter $URLName = (($url).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' diff --git a/Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1 b/Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1 index 452e81574db6..963b370668ef 100644 --- a/Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPIntuneTemplate.ps1 @@ -7,7 +7,6 @@ function New-CIPPIntuneTemplate { $CIPPURL, $ODataType ) - Write-Host "These are all bound params: $urlname, $id, $TenantFilter, $ActionResults, $CIPPURL, $ODataType" if ($ODataType) { switch -wildcard ($ODataType) { '*CompliancePolicy' { diff --git a/add-teams-license-test.ps1 b/add-teams-license-test.ps1 deleted file mode 100644 index 52ad80500e0e..000000000000 --- a/add-teams-license-test.ps1 +++ /dev/null @@ -1,87 +0,0 @@ -# Script to add Teams license test to all Teams related standards -$StandardsPath = 'Modules/CIPPCore/Public/Standards' - -# List of all Teams related standards -$TeamsStandards = @( - 'Invoke-CIPPStandardTeamsEmailIntegration.ps1', - 'Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1', - 'Invoke-CIPPStandardTeamsMeetingsByDefault.ps1', - 'Invoke-CIPPStandardTeamsMessagingPolicy.ps1', - 'Invoke-CIPPStandardTeamsMeetingVerification.ps1', - 'Invoke-CIPPStandardTeamsGuestAccess.ps1', - 'Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1', - 'Invoke-CIPPStandardTeamsFederationConfiguration.ps1', - 'Invoke-CIPPStandardTeamsExternalFileSharing.ps1', - 'Invoke-CIPPStandardTeamsEnrollUser.ps1', - 'Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1' -) - -$ProcessedFiles = @() -$SkippedFiles = @() - -foreach ($File in $TeamsStandards) { - $FilePath = Join-Path $StandardsPath $File - - if (-not (Test-Path $FilePath)) { - Write-Host "File not found: $File" -ForegroundColor Yellow - $SkippedFiles += $File - continue - } - - $Content = Get-Content $FilePath -Raw - $StandardName = $File -replace '^Invoke-CIPPStandard', '' -replace '\.ps1$', '' - - # Check if file already has a Test-CIPPStandardLicense line - if ($Content -match 'Test-CIPPStandardLicense') { - Write-Host "Skipping $File - already has license test" -ForegroundColor Yellow - $SkippedFiles += $File - continue - } - - # Check if file has param($Tenant, $Settings) line - if ($Content -notmatch 'param\(\$Tenant,\s*\$Settings\)') { - Write-Host "Skipping $File - no param block found" -ForegroundColor Yellow - $SkippedFiles += $File - continue - } - - Write-Host "Processing: $File (StandardName: $StandardName)" -ForegroundColor Green - - # Read the file content as lines - $Lines = Get-Content $FilePath - $NewLines = @() - - for ($i = 0; $i -lt $Lines.Count; $i++) { - $Line = $Lines[$i] - $NewLines += $Line - - # Add license test line after param($Tenant, $Settings) - if ($Line -match 'param\(\$Tenant,\s*\$Settings\)') { - $LicenseTestLine = " Test-CIPPStandardLicense -StandardName '$StandardName' -TenantFilter `$Tenant -RequiredCapabilities @('MCOSTANDARD', 'MCOEV', 'MCOIMP', 'TEAMS1','Teams_Room_Standard')" - $NewLines += $LicenseTestLine - Write-Host " Added license test line after param block" -ForegroundColor Cyan - } - } - - # Write the updated content back to the file - $NewLines | Set-Content $FilePath -Encoding UTF8 - $ProcessedFiles += $File - Write-Host " Updated: $File" -ForegroundColor Green - Write-Host "" -} - -Write-Host "Summary:" -ForegroundColor Magenta -Write-Host "Successfully processed files: $($ProcessedFiles.Count)" -ForegroundColor Green -Write-Host "Skipped files: $($SkippedFiles.Count)" -ForegroundColor Yellow -Write-Host "" - -if ($ProcessedFiles.Count -gt 0) { - Write-Host "Processed files:" -ForegroundColor Green - $ProcessedFiles | ForEach-Object { Write-Host " $_" -ForegroundColor Green } - Write-Host "" -} - -if ($SkippedFiles.Count -gt 0) { - Write-Host "Skipped files:" -ForegroundColor Yellow - $SkippedFiles | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow } -} diff --git a/fix-license-test.ps1 b/fix-license-test.ps1 deleted file mode 100644 index bb6e10576732..000000000000 --- a/fix-license-test.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -# Script to fix the Test-CIPPStandardLicense line in all CIPPStandard files -$StandardsPath = 'Modules/CIPPCore/Public/Standards' - -# Get all Invoke-CIPPStandard*.ps1 files -$AllStandardFiles = Get-ChildItem -Path $StandardsPath -Name 'Invoke-CIPPStandard*.ps1' -$ProcessedFiles = @() - -foreach ($File in $AllStandardFiles) { - $FilePath = Join-Path $StandardsPath $File - $Content = Get-Content $FilePath -Raw - - # Extract the standard name from the filename (remove "Invoke-CIPPStandard" and ".ps1") - $StandardName = $File -replace '^Invoke-CIPPStandard', '' -replace '\.ps1$', '' - - # Check if file has the incorrect Test-CIPPStandardLicense line - if ($Content -match 'Test-CIPPStandardLicense.*SendFromAlias') { - Write-Host "Fixing: $File (StandardName: $StandardName)" - - # Read the file content as lines - $Lines = Get-Content $FilePath - $NewLines = @() - - for ($i = 0; $i -lt $Lines.Count; $i++) { - $Line = $Lines[$i] - - # Replace the incorrect line with the correct one - if ($Line -match 'Test-CIPPStandardLicense.*SendFromAlias') { - $CorrectLine = " Test-CIPPStandardLicense -StandardName '$StandardName' -TenantFilter `$Tenant -RequiredCapabilities @('EXCHANGE_S_STANDARD', 'EXCHANGE_S_ENTERPRISE', 'EXCHANGE_LITE') #No Foundation because that does not allow powershell access" - $NewLines += $CorrectLine - Write-Host " Replaced line: $Line" - Write-Host " With: $CorrectLine" - } else { - $NewLines += $Line - } - } - - # Write the updated content back to the file - $NewLines | Set-Content $FilePath -Encoding UTF8 - $ProcessedFiles += $File - Write-Host " Updated: $File" - Write-Host "" - } -} - -Write-Host "Summary:" -Write-Host "Successfully fixed files: $($ProcessedFiles.Count)" -Write-Host "" -Write-Host "Fixed files:" -$ProcessedFiles | ForEach-Object { Write-Host " $_" } diff --git a/teams-standards.txt b/teams-standards.txt deleted file mode 100644 index 4c1297c2d044..000000000000 --- a/teams-standards.txt +++ /dev/null @@ -1,27 +0,0 @@ -Teams Related Standards Found: - -Files with "Teams" in filename or content: -- Invoke-CIPPStandardTeamsEmailIntegration.ps1 - Teams Email Integration settings -- Invoke-CIPPStandardTeamsGlobalMeetingPolicy.ps1 - Define Global Meeting Policy for Teams -- Invoke-CIPPStandardTeamsMeetingsByDefault.ps1 - Set Teams Meetings by default state -- Invoke-CIPPStandardTeamsMessagingPolicy.ps1 - Global Messaging Policy for Microsoft Teams -- Invoke-CIPPStandardTeamsMeetingVerification.ps1 - Meeting Verification (ReCaptcha) for Teams -- Invoke-CIPPStandardTeamsGuestAccess.ps1 - Allow guest users in Teams -- Invoke-CIPPStandardTeamsMeetingRecordingExpiration.ps1 - Set Teams Meeting Recording Expiration -- Invoke-CIPPStandardTeamsFederationConfiguration.ps1 - Federation Configuration for Microsoft Teams -- Invoke-CIPPStandardTeamsExternalFileSharing.ps1 - Define approved cloud storage services for external file sharing in Teams -- Invoke-CIPPStandardTeamsEnrollUser.ps1 - Teams voice and face enrollment settings -- Invoke-CIPPStandardTeamsExternalAccessPolicy.ps1 - External Access Settings for Microsoft Teams - -Other files that mention Teams in descriptions: -- Invoke-CIPPStandardAtpPolicyForO365.ps1 - Enables Defender for Office 365 for SharePoint, OneDrive and Microsoft Teams -- Invoke-CIPPStandardSafeLinksPolicy.ps1 - Safe Links policy for Email, Office, and Teams -- Invoke-CIPPStandardDisableM365GroupUsers.ps1 - Restricts M365 group creation (affects Teams creation) -- Invoke-CIPPStandardDisableUserSiteCreate.ps1 - Disables ability to fully create teams -- Invoke-CIPPStandardDisableSecurityGroupUsers.ps1 - Breaks ability to create Teams - -These Teams standards require the following license capabilities: -- MCOSTANDARD -- MCOEV -- MCOIMP -- TEAMS1 diff --git a/update-license-test.ps1 b/update-license-test.ps1 deleted file mode 100644 index ca33333c7e72..000000000000 --- a/update-license-test.ps1 +++ /dev/null @@ -1,52 +0,0 @@ -# Script to add Test-CIPPStandardLicense line to all CIPPStandard files that use New-ExoRequest -$StandardsPath = 'Modules/CIPPCore/Public/Standards' -$LicenseTestLine = ' Test-CIPPStandardLicense -StandardName ''SendFromAlias'' -TenantFilter $Tenant -RequiredCapabilities @(''EXCHANGE_S_STANDARD'', ''EXCHANGE_S_ENTERPRISE'', ''EXCHANGE_LITE'') #No Foundation because that does not allow powershell access' - -# Get all Invoke-CIPPStandard*.ps1 files -$AllStandardFiles = Get-ChildItem -Path $StandardsPath -Name 'Invoke-CIPPStandard*.ps1' -$FilesToProcess = @() -$ProcessedFiles = @() - -foreach ($File in $AllStandardFiles) { - $FilePath = Join-Path $StandardsPath $File - $Content = Get-Content $FilePath -Raw - - # Check if file uses New-ExoRequest and doesn't already have Test-CIPPStandardLicense - if ($Content -match 'New-ExoRequest' -and $Content -notmatch 'Test-CIPPStandardLicense') { - $FilesToProcess += $FilePath - Write-Host "Processing: $File" - - # Read the file content as lines - $Lines = Get-Content $FilePath - $NewLines = @() - $ParamFound = $false - - for ($i = 0; $i -lt $Lines.Count; $i++) { - $Line = $Lines[$i] - $NewLines += $Line - - # Look for the param line and add license test after it - if ($Line -match '^\s*param\(\$Tenant,\s*\$Settings\)' -and -not $ParamFound) { - $NewLines += $LicenseTestLine - $ParamFound = $true - } - } - - if ($ParamFound) { - # Write the updated content back to the file - $NewLines | Set-Content $FilePath -Encoding UTF8 - $ProcessedFiles += $File - Write-Host "Updated: $File" - } else { - Write-Host "Warning: Could not find param line in $File" - } - } -} - -Write-Host "" -Write-Host "Summary:" -Write-Host "Total files that needed processing: $($FilesToProcess.Count)" -Write-Host "Successfully processed files: $($ProcessedFiles.Count)" -Write-Host "" -Write-Host "Processed files:" -$ProcessedFiles | ForEach-Object { Write-Host " $_" } From f0e5936aaa3e445dc72153f93d08913b06f1bf39 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:07:51 +0200 Subject: [PATCH 125/125] up version --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index 0e79152459e0..fbb9ea12de3a 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -8.1.1 +8.2.0