From 57868b62a16506d1afa553b6141b0936e76c52d3 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Sat, 31 May 2025 00:26:27 +0200 Subject: [PATCH 01/19] Fix for multiple groups with the same name --- Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 b/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 index 7225829106e8..9564d818f6d5 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCopyGroupMembers.ps1 @@ -46,7 +46,7 @@ function Set-CIPPCopyGroupMembers { try { if ($PSCmdlet.ShouldProcess($MailGroup.displayName, "Add $UserId to group")) { if ($MailGroup.MailEnabled -and $Mailgroup.ResourceProvisioningOptions -notcontains 'Team' -and $MailGroup.groupTypes -notcontains 'Unified') { - $Params = @{ Identity = $MailGroup.mailNickname; Member = $UserId; BypassSecurityGroupManagerCheck = $true } + $Params = @{ Identity = $MailGroup.id; Member = $UserId; BypassSecurityGroupManagerCheck = $true } try { $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true } catch { From 173de703fff79a55f4a152cb1f5790d9a9e45def Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 31 May 2025 15:44:03 -0400 Subject: [PATCH 02/19] fix logging --- .../Tenant/Standards/Invoke-AddStandardsTemplate.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 index aa5027e259f4..c41da1262f37 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 @@ -29,9 +29,8 @@ function Invoke-AddStandardsTemplate { RowKey = "$GUID" PartitionKey = 'StandardsTemplateV2' GUID = "$GUID" - } - Write-LogMessage -headers $Request.Headers -API $APINAME -message "Created CA Template $($Request.body.name) with GUID $GUID" -Sev 'Debug' + Write-LogMessage -headers $Request.Headers -API $APINAME -message "Standards Template $($Request.body.templateName) with GUID $GUID added/edited." -Sev 'Info' $body = [pscustomobject]@{'Results' = 'Successfully added template'; Metadata = @{id = $GUID } } # Associate values to output bindings by calling 'Push-OutputBinding'. From 8ebf9cc26f129b22f155e5e8189f5c6eb4128faf Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 31 May 2025 16:26:10 -0400 Subject: [PATCH 03/19] fix transport rules --- .../Transport/Invoke-AddTransportRule.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddTransportRule.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddTransportRule.ps1 index 48b8fad2336d..6224bc13ebb6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddTransportRule.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Transport/Invoke-AddTransportRule.ps1 @@ -1,9 +1,9 @@ using namespace System.Net -Function Invoke-AddTransportRule { +function Invoke-AddTransportRule { <# .FUNCTIONALITY - Entrypoint + Entrypoint,AnyTenant .ROLE Exchange.TransportRule.ReadWrite #> @@ -17,6 +17,15 @@ Function Invoke-AddTransportRule { $RequestParams = $Request.Body.PowerShellCommand | ConvertFrom-Json | Select-Object -Property * -ExcludeProperty GUID, HasSenderOverride, ExceptIfHasSenderOverride, ExceptIfMessageContainsDataClassifications, MessageContainsDataClassifications $Tenants = ($Request.body.selectedTenants).value + + $AllowedTenants = Test-CippAccess -Request $Request -TenantList + + if ($AllowedTenants -ne 'AllTenants') { + $AllTenants = Get-Tenants -IncludeErrors + $AllowedTenantList = $AllTenants | Where-Object { $_.customerId -in $AllowedTenants } + $Tenants = $Tenants | Where-Object { $_ -in $AllowedTenantList.defaultDomainName } + } + $Result = foreach ($tenantFilter in $tenants) { $Existing = New-ExoRequest -ErrorAction SilentlyContinue -tenantid $tenantFilter -cmdlet 'Get-TransportRule' -useSystemMailbox $true | Where-Object -Property Identity -EQ $RequestParams.name try { From 53215464eeb752aea7f3f3979eed29acc5d279bd Mon Sep 17 00:00:00 2001 From: Jr7468 Date: Sun, 1 Jun 2025 17:34:51 +0100 Subject: [PATCH 04/19] feat(Exchange): Add Invoke-ExecModifyMBPerms function Add new PowerShell function for modifying mailbox permissions in Exchange. This function provides administrative capabilities for managing mailbox access rights. --- .../Invoke-ExecModifyMBPerms.ps1 | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 new file mode 100644 index 000000000000..222c3561e579 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyMBPerms.ps1 @@ -0,0 +1,133 @@ +using namespace System.Net + +Function Invoke-ExecModifyMBPerms { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.Mailbox.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' + + $Username = $request.body.userID + $Tenantfilter = $request.body.tenantfilter + $Permissions = $request.body.permissions + + if ($username -eq $null) { exit } + + $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($username)" -tenantid $Tenantfilter).id + $Results = [System.Collections.ArrayList]::new() + + # Convert permissions to array format if it's an object with numeric keys + if ($Permissions -is [PSCustomObject]) { + if ($Permissions.PSObject.Properties.Name -match '^\d+$') { + $Permissions = $Permissions.PSObject.Properties.Value + } + else { + $Permissions = @($Permissions) + } + } + + foreach ($Permission in $Permissions) { + $PermissionLevel = $Permission.PermissionLevel + $Modification = $Permission.Modification + $AutoMap = if ($Permission.PSObject.Properties.Name -contains 'AutoMap') { $Permission.AutoMap } else { $true } + + # Handle UserID as array of objects or single value + $TargetUsers = if ($Permission.UserID -is [array]) { + $Permission.UserID | ForEach-Object { $_.value } + } + else { + @($Permission.UserID) + } + + foreach ($TargetUser in $TargetUsers) { + try { + switch ($PermissionLevel) { + 'FullAccess' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-mailboxpermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('FullAccess') + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Shared Mailbox permissions") + } + else { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-MailboxPermission' -cmdParams @{ + Identity = $userid + user = $TargetUser + accessRights = @('FullAccess') + automapping = $AutoMap + Confirm = $false + } + $null = $results.Add("Granted $($TargetUser) access to $($username) Mailbox with automapping set to $($AutoMap)") + } + } + 'SendAs' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-RecipientPermission' -cmdParams @{ + Identity = $userid + Trustee = $TargetUser + accessRights = @('SendAs') + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) with Send As permissions") + } + else { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-RecipientPermission' -cmdParams @{ + Identity = $userid + Trustee = $TargetUser + accessRights = @('SendAs') + Confirm = $false + } + $null = $results.Add("Granted $($TargetUser) access to $($username) with Send As permissions") + } + } + 'SendOnBehalf' { + if ($Modification -eq 'Remove') { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-Mailbox' -cmdParams @{ + Identity = $userid + GrantSendonBehalfTo = @{ + '@odata.type' = '#Exchange.GenericHashTable' + remove = $TargetUser + } + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Send on Behalf Permissions") + } + else { + $MailboxPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-Mailbox' -cmdParams @{ + Identity = $userid + GrantSendonBehalfTo = @{ + '@odata.type' = '#Exchange.GenericHashTable' + add = $TargetUser + } + Confirm = $false + } + $null = $results.Add("Granted $($TargetUser) access to $($username) with Send On Behalf Permissions") + } + } + } + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Executed $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Info' -tenant $TenantFilter + } + catch { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Error' -tenant $TenantFilter + $null = $results.Add("Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)") + } + } + } + + $body = [pscustomobject]@{'Results' = @($results) } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) +} From 77ecb14ee1bdee5493854b1fdc09b777dc866b6e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 2 Jun 2025 11:55:51 -0400 Subject: [PATCH 05/19] fix logs --- .../Public/Entrypoints/Invoke-ListLogs.ps1 | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 index 7547f7b2f58c..2b7848ab56b5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 @@ -54,35 +54,34 @@ function Invoke-ListLogs { Write-Host "Getting logs for filter: $Filter, LogLevel: $LogLevel, Username: $username" $Rows = Get-AzDataTableEntity @Table -Filter $Filter | Where-Object { $_.Severity -in $LogLevel -and $_.Username -like $username } + + if ($AllowedTenants -notcontains 'AllTenants') { + $TenantList = Get-Tenants -IncludeErrors | Where-Object { $_.customerId -in $AllowedTenants } + } + foreach ($Row in $Rows) { - if ($AllowedTenants -notcontains 'AllTenants') { - $TenantList = Get-Tenants -IncludeErrors - if ($Row.Tenant -ne 'None' -and $Row.Tenant) { - $Tenant = $TenantList | Where-Object -Property defaultDomainName -EQ $Row.Tenant - if ($Tenant -and $Tenant.customerId -notin $AllowedTenants) { - continue + if ($AllowedTenants -contains 'AllTenants' -or ($AllowedTenants -notcontains 'AllTenants' -and ($TenantList.defaultDomainName -contains $Row.Tenant -or $Row.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $Row.TenantId)) ) { + + $LogData = if ($Row.LogData -and (Test-Json -Json $Row.LogData -ErrorAction SilentlyContinue)) { + $Row.LogData | ConvertFrom-Json + } else { $Row.LogData } + [PSCustomObject]@{ + DateTime = $Row.Timestamp + Tenant = $Row.Tenant + API = $Row.API + Message = $Row.Message + User = $Row.Username + Severity = $Row.Severity + LogData = $LogData + TenantID = if ($Row.TenantID -ne $null) { + $Row.TenantID + } else { + 'None' } + AppId = $Row.AppId + IP = $Row.IP } } - $LogData = if ($Row.LogData -and (Test-Json -Json $Row.LogData -ErrorAction SilentlyContinue)) { - $Row.LogData | ConvertFrom-Json - } else { $Row.LogData } - [PSCustomObject]@{ - DateTime = $Row.Timestamp - Tenant = $Row.Tenant - API = $Row.API - Message = $Row.Message - User = $Row.Username - Severity = $Row.Severity - LogData = $LogData - TenantID = if ($Row.TenantID -ne $null) { - $Row.TenantID - } else { - 'None' - } - AppId = $Row.AppId - IP = $Row.IP - } } } From 48920ad2750264abe5fe7030d14f1feb99f82a1f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 2 Jun 2025 12:31:43 -0400 Subject: [PATCH 06/19] Update Invoke-ListLogs.ps1 --- Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 index 2b7848ab56b5..6e360b25a6a0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 @@ -3,7 +3,7 @@ using namespace System.Net function Invoke-ListLogs { <# .FUNCTIONALITY - Entrypoint + Entrypoint,AnyTenant .ROLE CIPP.Core.Read #> From 3b4ff4f0ccf6174c99c4029978cebe3985bac96c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 3 Jun 2025 10:19:56 -0400 Subject: [PATCH 07/19] fix audit log searches --- .../Start-AuditLogSearchCreation.ps1 | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 index a8fb469b7edb..28919fe56f95 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-AuditLogSearchCreation.ps1 @@ -7,7 +7,12 @@ function Start-AuditLogSearchCreation { param() try { $ConfigTable = Get-CippTable -TableName 'WebhookRules' - $ConfigEntries = Get-CIPPAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'Webhookv2'" + $ConfigEntries = Get-CIPPAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'Webhookv2'" | ForEach-Object { + $ConfigEntry = $_ + $ConfigEntry.excludedTenants = $ConfigEntry.excludedTenants | ConvertFrom-Json + $ConfigEntry.Tenants = $ConfigEntry.Tenants | ConvertFrom-Json + $ConfigEntry + } $TenantList = Get-Tenants -IncludeErrors # Round time down to nearest minute @@ -16,11 +21,29 @@ function Start-AuditLogSearchCreation { $EndTime = $Now.AddSeconds(-$Now.Seconds) Write-Information 'Audit Logs: Creating new searches' + foreach ($Tenant in $TenantList) { - $TenantsList = Expand-CIPPTenantGroups -TenantFilter ($ConfigEntries.Tenants | ConvertFrom-Json) - $Configuration = $ConfigEntries | Where-Object { ($_.Tenants -match $TenantFilter -or $_.Tenants -match 'AllTenants') } - if ($Configuration -and $Tenant -in $TenantsList) { - $ServiceFilters = $Configuration | Select-Object -Property type | Sort-Object -Property type -Unique | ForEach-Object { $_.type.split('.')[1] } + Write-Information "Processing tenant $($Tenant.defaultDomainName) - $($Tenant.customerId)" + $TenantInConfig = $false + $MatchingConfigs = [System.Collections.Generic.List[object]]::new() + foreach ($ConfigEntry in $ConfigEntries) { + if ($ConfigEntry.excludedTenants.value -contains $Tenant.defaultDomainName) { + continue + } + $TenantsList = Expand-CIPPTenantGroups -TenantFilter ($ConfigEntry.Tenants) + if ($TenantsList.value -contains $Tenant.defaultDomainName -or $TenantsList.value -contains 'AllTenants') { + $TenantInConfig = $true + $MatchingConfigs.Add($ConfigEntry) + } + } + + if (!$TenantInConfig) { + Write-Information "Tenant $($Tenant.defaultDomainName) has no configured audit log rules, skipping search creation." + continue + } + + if ($MatchingConfigs) { + $ServiceFilters = $MatchingConfigs | Select-Object -Property type | Sort-Object -Property type -Unique | ForEach-Object { $_.type.split('.')[1] } try { $LogSearch = @{ StartTime = $StartTime From d81c6f251faa617ea2b49ebc736125977f1f5d23 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 3 Jun 2025 15:40:09 -0400 Subject: [PATCH 08/19] fix state detection --- .../Invoke-CIPPStandardEXODisableAutoForwarding.ps1 | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 index 8267956ad637..e75909d94c41 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 @@ -38,8 +38,7 @@ function Invoke-CIPPStandardEXODisableAutoForwarding { $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-HostedOutboundSpamFilterPolicy' -cmdParams @{Identity = 'Default' } -useSystemMailbox $true $StateIsCorrect = $CurrentInfo.AutoForwardingMode -eq 'Off' - If ($Settings.remediate -eq $true) { - + if ($Settings.remediate -eq $true) { try { New-ExoRequest -tenantid $tenant -cmdlet 'Set-HostedOutboundSpamFilterPolicy' -cmdParams @{ Identity = 'Default'; AutoForwardingMode = 'Off' } -useSystemMailbox $true Write-LogMessage -API 'Standards' -tenant $tenant -message 'Disabled auto forwarding' -sev Info @@ -50,7 +49,6 @@ function Invoke-CIPPStandardEXODisableAutoForwarding { } if ($Settings.alert -eq $true) { - if ($StateIsCorrect -eq $true) { Write-LogMessage -API 'Standards' -tenant $tenant -message 'Auto forwarding is disabled.' -sev Info } else { @@ -60,9 +58,8 @@ function Invoke-CIPPStandardEXODisableAutoForwarding { } if ($Settings.report -eq $true) { - $state = $StateIsCorrect ? $true : $CurrentInfo | Select-Object autoForwardingMode + $state = $StateIsCorrect ?? ($CurrentInfo | Select-Object AutoForwardingMode) Set-CIPPStandardsCompareField -FieldName 'standards.EXODisableAutoForwarding' -FieldValue $state -TenantFilter $Tenant Add-CIPPBPAField -FieldName 'AutoForwardingDisabled' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $tenant } - } From 7502a367ac581087afcb6df726730d45e8e5a6ee Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 3 Jun 2025 16:09:17 -0400 Subject: [PATCH 09/19] trim policy and rule state for comparison report --- .../Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 index 6b3660fec34c..e1c2ee7ca8ac 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardUserSubmissions.ps1 @@ -196,6 +196,8 @@ function Invoke-CIPPStandardUserSubmissions { if ($StateIsCorrect) { $FieldValue = $true } else { + $PolicyState = $PolicyState | Select-Object EnableReportToMicrosoft, ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportJunkAddresses, ReportNotJunkAddresses, ReportPhishAddresses + $RuleState = $RuleState | Select-Object State, SentTo $FieldValue = @{ PolicyState = $PolicyState; RuleState = $RuleState } } From d2c86945c115224e93e2a00d2c83dde62a53af31 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 3 Jun 2025 16:13:59 -0400 Subject: [PATCH 10/19] spam filter policy tweak --- .../Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index 6ca1652b5e01..6db780a0af74 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -241,7 +241,9 @@ function Invoke-CIPPStandardSpamFilterPolicy { if ($StateIsCorrect) { $FieldValue = $true } else { - $FieldValue = $CurrentState ? $CurrentState : @{ state = 'Spam filter policy not found' } + $CurrentState = $CurrentState | Select-Object -Property Name, SpamAction, SpamQuarantineTag, HighConfidenceSpamAction, HighConfidenceSpamQuarantineTag, BulkSpamAction, BulkQuarantineTag, PhishSpamAction, PhishQuarantineTag, HighConfidencePhishAction, HighConfidencePhishQuarantineTag, BulkThreshold, QuarantineRetentionPeriod, IncreaseScoreWithImageLinks, IncreaseScoreWithNumericIps, IncreaseScoreWithRedirectToOtherPort, IncreaseScoreWithBizOrInfoUrls, MarkAsSpamEmptyMessages, MarkAsSpamJavaScriptInHtml, MarkAsSpamFramesInHtml, MarkAsSpamObjectTagsInHtml, MarkAsSpamEmbedTagsInHtml, MarkAsSpamFormTagsInHtml, MarkAsSpamWebBugsInHtml, MarkAsSpamSensitiveWordList, MarkAsSpamSpfRecordHardFail, MarkAsSpamFromAddressAuthFail, MarkAsSpamNdrBackscatter, MarkAsSpamBulkMail, InlineSafetyTipsEnabled, PhishZapEnabled, SpamZapEnabled + + $FieldValue = $StateIsCorrect ?? $CurrentState ?? @{ state = 'Spam filter policy not found' } } Set-CIPPStandardsCompareField -FieldName 'standards.SpamFilterPolicy' -FieldValue $FieldValue -Tenant $Tenant } From 7918d5419585a0dc2aad00dce469e4b8ed332517 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 3 Jun 2025 16:26:17 -0400 Subject: [PATCH 11/19] remove excludedTenants string from template list --- .../Tenant/Standards/Invoke-listStandardTemplates.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 index 3ebc2712adde..717939a6655c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-listStandardTemplates { +function Invoke-listStandardTemplates { <# .FUNCTIONALITY Entrypoint,AnyTenant @@ -29,7 +29,11 @@ Function Invoke-listStandardTemplates { return } $Data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force - if ($Data.excludedTenants) { $Data.excludedTenants = @($Data.excludedTenants) } + if ($Data.excludedTenants -and $Data.excludedTenants -ne 'excludedTenants') { + $Data.excludedTenants = @($Data.excludedTenants) + } else { + $Data.excludedTenants = @() + } $Data } | Sort-Object -Property templateName From 5f0a0322b12149182e485106b42e9ae204085b65 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 3 Jun 2025 17:09:40 -0400 Subject: [PATCH 12/19] Update Invoke-CIPPStandardSpamFilterPolicy.ps1 --- .../Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index 6db780a0af74..bbf74d0e09a4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -241,9 +241,7 @@ function Invoke-CIPPStandardSpamFilterPolicy { if ($StateIsCorrect) { $FieldValue = $true } else { - $CurrentState = $CurrentState | Select-Object -Property Name, SpamAction, SpamQuarantineTag, HighConfidenceSpamAction, HighConfidenceSpamQuarantineTag, BulkSpamAction, BulkQuarantineTag, PhishSpamAction, PhishQuarantineTag, HighConfidencePhishAction, HighConfidencePhishQuarantineTag, BulkThreshold, QuarantineRetentionPeriod, IncreaseScoreWithImageLinks, IncreaseScoreWithNumericIps, IncreaseScoreWithRedirectToOtherPort, IncreaseScoreWithBizOrInfoUrls, MarkAsSpamEmptyMessages, MarkAsSpamJavaScriptInHtml, MarkAsSpamFramesInHtml, MarkAsSpamObjectTagsInHtml, MarkAsSpamEmbedTagsInHtml, MarkAsSpamFormTagsInHtml, MarkAsSpamWebBugsInHtml, MarkAsSpamSensitiveWordList, MarkAsSpamSpfRecordHardFail, MarkAsSpamFromAddressAuthFail, MarkAsSpamNdrBackscatter, MarkAsSpamBulkMail, InlineSafetyTipsEnabled, PhishZapEnabled, SpamZapEnabled - - $FieldValue = $StateIsCorrect ?? $CurrentState ?? @{ state = 'Spam filter policy not found' } + $FieldValue = $StateIsCorrect -eq $true ? $true : ($CurrentState ?? @{ state = 'Spam filter policy not found' }) } Set-CIPPStandardsCompareField -FieldName 'standards.SpamFilterPolicy' -FieldValue $FieldValue -Tenant $Tenant } From 5f7a754ba88e98b3158b22ac90a26f53402ee008 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 3 Jun 2025 18:06:40 -0400 Subject: [PATCH 13/19] add teams guest access standard --- .../Invoke-CIPPStandardTeamsGuestAccess.ps1 | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 new file mode 100644 index 000000000000..715ef12c3386 --- /dev/null +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsGuestAccess.ps1 @@ -0,0 +1,77 @@ +function Invoke-CIPPStandardTeamsGuestAccess { + <# + .FUNCTIONALITY + Internal + .COMPONENT + (APIName) TeamsGuestAccess + .SYNOPSIS + (Label) Allow guest users in Teams + .DESCRIPTION + (Helptext) Allow guest users access to teams. + (DocsDescription) Allow guest users access to teams. Guest users are users who are not part of your organization but have been invited to collaborate with your organization in Teams. This setting allows you to control whether guest users can access Teams. + .NOTES + CAT + Teams Standards + TAG + ADDEDCOMPONENT + {"type":"switch","name":"standards.TeamsGuestAccess.AllowGuestUser","label":"Allow guest users"} + IMPACT + Low Impact + ADDEDDATE + 2025-06-03 + POWERSHELLEQUIVALENT + Set-CsTeamsClientConfiguration -AllowGuestUser \$true + 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) + + $CurrentState = New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Get-CsTeamsClientConfiguration' -CmdParams @{Identity = 'Global' } | Select-Object AllowGuestUser + + if ($null -eq $Settings.AllowGuestUser) { $Settings.AllowGuestUser = $false } + + $StateIsCorrect = ($CurrentState.AllowGuestUser -eq $Settings.AllowGuestUser) + + if ($Settings.remediate -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Guest Access already set.' -sev Info + } else { + $cmdParams = @{ + Identity = 'Global' + AllowGuestUser = $Settings.AllowGuestUser + } + + try { + New-TeamsRequest -TenantFilter $Tenant -Cmdlet 'Set-CsTeamsClientConfiguration' -CmdParams $cmdParams + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Updated Teams Guest Access settings' -sev Info + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to set Teams Guest Access settings. Error: $ErrorMessage" -sev Error + } + } + } + + if ($Settings.alert -eq $true) { + if ($StateIsCorrect -eq $true) { + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Guest Access settings is set correctly.' -sev Info + } else { + Write-StandardsAlert -message 'Teams Guest Access settings is not set correctly.' -object $CurrentState -tenant $Tenant -standardName 'TeamsGuestAccess' -standardId $Settings.standardId + Write-LogMessage -API 'Standards' -tenant $Tenant -message 'Teams Guest Access settings is not set correctly.' -sev Info + } + } + + if ($Settings.report -eq $true) { + if ($StateIsCorrect) { + $FieldValue = $true + } else { + $FieldValue = $CurrentState + } + Set-CIPPStandardsCompareField -FieldName 'standards.TeamsGuestAccess' -FieldValue $FieldValue -Tenant $Tenant + Add-CIPPBPAField -FieldName 'TeamsGuestAccess' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant + + } +} From 3c54f5820e7e4a0d2fe015a03653d9269fc33cfb Mon Sep 17 00:00:00 2001 From: Jr7468 Date: Wed, 4 Jun 2025 15:41:56 +0100 Subject: [PATCH 14/19] feat: Add Exchange calendar permissions modification functionality Add new PowerShell functions for modifying calendar permissions in Exchange: - ExecModifyCalPerms.ps1: Core implementation for calendar permission modifications - Invoke-ExecModifyCalPerms.ps1: HTTP function wrapper for the calendar permissions modification --- .../Administration/ExecModifyCalPerms.ps1 | 157 ++++++++++++++++++ .../Invoke-ExecModifyCalPerms.ps1 | 140 ++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 new file mode 100644 index 000000000000..1907fc3a6280 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 @@ -0,0 +1,157 @@ +using namespace System.Net + +Function Invoke-ExecModifyCalPerms { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.Calendar.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' + + $Username = $request.body.userID + $Tenantfilter = $request.body.tenantfilter + $Permissions = $request.body.permissions + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing request for user: $Username, tenant: $Tenantfilter" -Sev 'Debug' + + if ($username -eq $null) { + Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Username is null' -Sev 'Error' + $body = [pscustomobject]@{'Results' = @('Username is required') } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = $Body + }) + return + } + + try { + $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($username)" -tenantid $Tenantfilter).id + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Retrieved user ID: $userid" -Sev 'Debug' + } + catch { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Failed to get user ID: $($_.Exception.Message)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = @("Failed to get user ID: $($_.Exception.Message)") } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::NotFound + Body = $Body + }) + return + } + + $Results = [System.Collections.ArrayList]::new() + $HasErrors = $false + + # Convert permissions to array format if it's an object with numeric keys + if ($Permissions -is [PSCustomObject]) { + if ($Permissions.PSObject.Properties.Name -match '^\d+$') { + $Permissions = $Permissions.PSObject.Properties.Value + } + else { + $Permissions = @($Permissions) + } + } + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing $($Permissions.Count) permission entries" -Sev 'Debug' + + foreach ($Permission in $Permissions) { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing permission: $($Permission | ConvertTo-Json)" -Sev 'Debug' + + $PermissionLevel = if ($Permission.PermissionLevel -is [PSCustomObject]) { $Permission.PermissionLevel.value } else { $Permission.PermissionLevel } + $Modification = $Permission.Modification + $CanViewPrivateItems = if ($Permission.PSObject.Properties.Name -contains 'CanViewPrivateItems') { $Permission.CanViewPrivateItems } else { $false } + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Permission Level: $PermissionLevel, Modification: $Modification, CanViewPrivateItems: $CanViewPrivateItems" -Sev 'Debug' + + # Handle UserID as array of objects or single value + $TargetUsers = if ($Permission.UserID -is [PSCustomObject]) { + @($Permission.UserID.value) + } + elseif ($Permission.UserID -is [array]) { + $Permission.UserID | ForEach-Object { $_.value } + } + else { + @($Permission.UserID) + } + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Target Users: $($TargetUsers -join ', ')" -Sev 'Debug' + + foreach ($TargetUser in $TargetUsers) { + try { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing target user: $TargetUser" -Sev 'Debug' + + if ($Modification -eq 'Remove') { + try { + $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxFolderPermission' -cmdParams @{ + Identity = "$($userid):\Calendar" + User = $TargetUser + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Calendar permissions") + } + catch { + $null = $results.Add("No existing permissions to remove for $($TargetUser)") + } + } + else { + # Map the permission level to the correct AccessRights value + $AccessRights = switch ($PermissionLevel) { + 'Owner' { @('Owner') } + 'Editor' { @('Editor') } + 'Reviewer' { @('Reviewer') } + 'AvailabilityOnly' { @('AvailabilityOnly') } + default { throw "Invalid permission level: $PermissionLevel" } + } + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Setting permissions with AccessRights: $($AccessRights -join ', ')" -Sev 'Debug' + + $cmdParams = @{ + Identity = "$($userid):\Calendar" + User = $TargetUser + AccessRights = $AccessRights + Confirm = $false + } + + if ($CanViewPrivateItems) { + $cmdParams['SharingPermissionFlags'] = 'Delegate,CanViewPrivateItems' + } + + try { + # Try Add first + $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-MailboxFolderPermission' -cmdParams $cmdParams + $null = $results.Add("Granted $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") + } + catch { + # If Add fails, try Set + $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-MailboxFolderPermission' -cmdParams $cmdParams + $null = $results.Add("Updated $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") + } + } + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Successfully executed $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Info' -tenant $TenantFilter + } + catch { + $HasErrors = $true + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter + $null = $results.Add("Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)") + } + } + } + + if ($results.Count -eq 0) { + Write-LogMessage -headers $Request.Headers -API $APINAME-message 'No results were generated from the operation' -Sev 'Warning' + $null = $results.Add('No results were generated from the operation. Please check the logs for more details.') + $HasErrors = $true + } + + $body = [pscustomobject]@{'Results' = @($results) } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = if ($HasErrors) { [HttpStatusCode]::InternalServerError } else { [HttpStatusCode]::OK } + Body = $Body + }) +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 new file mode 100644 index 000000000000..e2eefeff06ce --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/Invoke-ExecModifyCalPerms.ps1 @@ -0,0 +1,140 @@ +using namespace System.Net + +Function Invoke-ExecModifyCalPerms { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Exchange.Calendar.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $Request.Params.CIPPEndpoint + Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' + + $Username = $request.body.userID + $Tenantfilter = $request.body.tenantfilter + $Permissions = $request.body.permissions + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing request for user: $Username, tenant: $Tenantfilter" -Sev 'Debug' + + if ($username -eq $null) { + Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Username is null' -Sev 'Error' + $body = [pscustomobject]@{'Results' = @('Username is required') } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::BadRequest + Body = $Body + }) + return + } + + try { + $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($username)" -tenantid $Tenantfilter).id + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Retrieved user ID: $userid" -Sev 'Debug' + } + catch { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Failed to get user ID: $($_.Exception.Message)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = @("Failed to get user ID: $($_.Exception.Message)") } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::NotFound + Body = $Body + }) + return + } + + $Results = [System.Collections.ArrayList]::new() + $HasErrors = $false + + # Convert permissions to array format if it's an object with numeric keys + if ($Permissions -is [PSCustomObject]) { + if ($Permissions.PSObject.Properties.Name -match '^\d+$') { + $Permissions = $Permissions.PSObject.Properties.Value + } + else { + $Permissions = @($Permissions) + } + } + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing $($Permissions.Count) permission entries" -Sev 'Debug' + + foreach ($Permission in $Permissions) { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing permission: $($Permission | ConvertTo-Json)" -Sev 'Debug' + + $PermissionLevel = $Permission.PermissionLevel.value ?? $Permission.PermissionLevel + $Modification = $Permission.Modification + $CanViewPrivateItems = $Permission.CanViewPrivateItems ?? $false + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Permission Level: $PermissionLevel, Modification: $Modification, CanViewPrivateItems: $CanViewPrivateItems" -Sev 'Debug' + + # Handle UserID as array or single value + $TargetUsers = @($Permission.UserID | ForEach-Object { $_.value ?? $_ }) + + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Target Users: $($TargetUsers -join ', ')" -Sev 'Debug' + + foreach ($TargetUser in $TargetUsers) { + try { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing target user: $TargetUser" -Sev 'Debug' + + if ($Modification -eq 'Remove') { + try { + $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxFolderPermission' -cmdParams @{ + Identity = "$($userid):\Calendar" + User = $TargetUser + Confirm = $false + } + $null = $results.Add("Removed $($TargetUser) from $($username) Calendar permissions") + } + catch { + $null = $results.Add("No existing permissions to remove for $($TargetUser)") + } + } + else { + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Setting permissions with AccessRights: $PermissionLevel" -Sev 'Debug' + + $cmdParams = @{ + Identity = "$($userid):\Calendar" + User = $TargetUser + AccessRights = $PermissionLevel + Confirm = $false + } + + if ($CanViewPrivateItems) { + $cmdParams['SharingPermissionFlags'] = 'Delegate,CanViewPrivateItems' + } + + try { + # Try Add first + $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-MailboxFolderPermission' -cmdParams $cmdParams + $null = $results.Add("Granted $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") + } + catch { + # If Add fails, try Set + $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-MailboxFolderPermission' -cmdParams $cmdParams + $null = $results.Add("Updated $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") + } + } + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Successfully executed $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Info' -tenant $TenantFilter + } + catch { + $HasErrors = $true + Write-LogMessage -headers $Request.Headers -API $APINAME-message "Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter + $null = $results.Add("Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)") + } + } + } + + if ($results.Count -eq 0) { + Write-LogMessage -headers $Request.Headers -API $APINAME-message 'No results were generated from the operation' -Sev 'Warning' + $null = $results.Add('No results were generated from the operation. Please check the logs for more details.') + $HasErrors = $true + } + + $body = [pscustomobject]@{'Results' = @($results) } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = if ($HasErrors) { [HttpStatusCode]::InternalServerError } else { [HttpStatusCode]::OK } + Body = $Body + }) +} \ No newline at end of file From 9d087e877865aef26d1815cd35b9968ab17056fb Mon Sep 17 00:00:00 2001 From: Jr7468 Date: Wed, 4 Jun 2025 15:43:18 +0100 Subject: [PATCH 15/19] Removed incorrect file --- .../Administration/ExecModifyCalPerms.ps1 | 157 ------------------ 1 file changed, 157 deletions(-) delete mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 deleted file mode 100644 index 1907fc3a6280..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Administration/ExecModifyCalPerms.ps1 +++ /dev/null @@ -1,157 +0,0 @@ -using namespace System.Net - -Function Invoke-ExecModifyCalPerms { - <# - .FUNCTIONALITY - Entrypoint - .ROLE - Exchange.Calendar.ReadWrite - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $APIName = $Request.Params.CIPPEndpoint - Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Accessed this API' -Sev 'Debug' - - $Username = $request.body.userID - $Tenantfilter = $request.body.tenantfilter - $Permissions = $request.body.permissions - - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing request for user: $Username, tenant: $Tenantfilter" -Sev 'Debug' - - if ($username -eq $null) { - Write-LogMessage -headers $Request.Headers -API $APINAME-message 'Username is null' -Sev 'Error' - $body = [pscustomobject]@{'Results' = @('Username is required') } - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::BadRequest - Body = $Body - }) - return - } - - try { - $userid = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($username)" -tenantid $Tenantfilter).id - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Retrieved user ID: $userid" -Sev 'Debug' - } - catch { - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Failed to get user ID: $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = @("Failed to get user ID: $($_.Exception.Message)") } - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::NotFound - Body = $Body - }) - return - } - - $Results = [System.Collections.ArrayList]::new() - $HasErrors = $false - - # Convert permissions to array format if it's an object with numeric keys - if ($Permissions -is [PSCustomObject]) { - if ($Permissions.PSObject.Properties.Name -match '^\d+$') { - $Permissions = $Permissions.PSObject.Properties.Value - } - else { - $Permissions = @($Permissions) - } - } - - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing $($Permissions.Count) permission entries" -Sev 'Debug' - - foreach ($Permission in $Permissions) { - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing permission: $($Permission | ConvertTo-Json)" -Sev 'Debug' - - $PermissionLevel = if ($Permission.PermissionLevel -is [PSCustomObject]) { $Permission.PermissionLevel.value } else { $Permission.PermissionLevel } - $Modification = $Permission.Modification - $CanViewPrivateItems = if ($Permission.PSObject.Properties.Name -contains 'CanViewPrivateItems') { $Permission.CanViewPrivateItems } else { $false } - - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Permission Level: $PermissionLevel, Modification: $Modification, CanViewPrivateItems: $CanViewPrivateItems" -Sev 'Debug' - - # Handle UserID as array of objects or single value - $TargetUsers = if ($Permission.UserID -is [PSCustomObject]) { - @($Permission.UserID.value) - } - elseif ($Permission.UserID -is [array]) { - $Permission.UserID | ForEach-Object { $_.value } - } - else { - @($Permission.UserID) - } - - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Target Users: $($TargetUsers -join ', ')" -Sev 'Debug' - - foreach ($TargetUser in $TargetUsers) { - try { - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Processing target user: $TargetUser" -Sev 'Debug' - - if ($Modification -eq 'Remove') { - try { - $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Remove-MailboxFolderPermission' -cmdParams @{ - Identity = "$($userid):\Calendar" - User = $TargetUser - Confirm = $false - } - $null = $results.Add("Removed $($TargetUser) from $($username) Calendar permissions") - } - catch { - $null = $results.Add("No existing permissions to remove for $($TargetUser)") - } - } - else { - # Map the permission level to the correct AccessRights value - $AccessRights = switch ($PermissionLevel) { - 'Owner' { @('Owner') } - 'Editor' { @('Editor') } - 'Reviewer' { @('Reviewer') } - 'AvailabilityOnly' { @('AvailabilityOnly') } - default { throw "Invalid permission level: $PermissionLevel" } - } - - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Setting permissions with AccessRights: $($AccessRights -join ', ')" -Sev 'Debug' - - $cmdParams = @{ - Identity = "$($userid):\Calendar" - User = $TargetUser - AccessRights = $AccessRights - Confirm = $false - } - - if ($CanViewPrivateItems) { - $cmdParams['SharingPermissionFlags'] = 'Delegate,CanViewPrivateItems' - } - - try { - # Try Add first - $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Add-MailboxFolderPermission' -cmdParams $cmdParams - $null = $results.Add("Granted $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") - } - catch { - # If Add fails, try Set - $CalPerms = New-ExoRequest -Anchor $username -tenantid $Tenantfilter -cmdlet 'Set-MailboxFolderPermission' -cmdParams $cmdParams - $null = $results.Add("Updated $($TargetUser) $($PermissionLevel) access to $($username) Calendar$($CanViewPrivateItems ? ' with access to private items' : '')") - } - } - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Successfully executed $($PermissionLevel) permission modification for $($TargetUser) on $($username)" -Sev 'Info' -tenant $TenantFilter - } - catch { - $HasErrors = $true - Write-LogMessage -headers $Request.Headers -API $APINAME-message "Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - $null = $results.Add("Could not execute $($PermissionLevel) permission modification for $($TargetUser) on $($username). Error: $($_.Exception.Message)") - } - } - } - - if ($results.Count -eq 0) { - Write-LogMessage -headers $Request.Headers -API $APINAME-message 'No results were generated from the operation' -Sev 'Warning' - $null = $results.Add('No results were generated from the operation. Please check the logs for more details.') - $HasErrors = $true - } - - $body = [pscustomobject]@{'Results' = @($results) } - - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = if ($HasErrors) { [HttpStatusCode]::InternalServerError } else { [HttpStatusCode]::OK } - Body = $Body - }) -} \ No newline at end of file From e1428029732fbc4087b2a9dcd8bbc97ef8b287c8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 4 Jun 2025 17:24:02 -0400 Subject: [PATCH 16/19] prevent empty permission scope from being pushed --- .../CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 83b4e2bfe025..d2a4f8cd19e1 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -114,6 +114,10 @@ function Add-CIPPDelegatedPermission { $OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id) if (!$OldScope) { + if ([string]::IsNullOrEmpty($NewScope) -or $NewScope -eq ' ') { + $Results.add("No delegated permissions to add for $($svcPrincipalId.displayName)") + continue + } try { $Createbody = @{ clientId = $ourSVCPrincipal.id @@ -147,6 +151,13 @@ function Add-CIPPDelegatedPermission { $Results.add("All delegated permissions exist for $($svcPrincipalId.displayName)") continue } + + if ([string]::IsNullOrEmpty($NewScope) -or $NewScope -eq ' ') { + # No permissions to update + $Results.add("No delegated permissions to update for $($svcPrincipalId.displayName)") + continue + } + $Patchbody = @{ scope = "$NewScope" } | ConvertTo-Json -Compress From e43cc50f995741955ff2989c907452f4be3af2d8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 5 Jun 2025 14:38:34 -0400 Subject: [PATCH 17/19] temp fix for standards reports --- .../Tenant/Standards/Invoke-ListStandardsCompare.ps1 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 index 4144312551fd..130cec97dfba 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListStandardsCompare.ps1 @@ -1,6 +1,6 @@ using namespace System.Net -Function Invoke-ListStandardsCompare { +function Invoke-ListStandardsCompare { <# .FUNCTIONALITY Entrypoint @@ -10,7 +10,13 @@ Function Invoke-ListStandardsCompare { [CmdletBinding()] param($Request, $TriggerMetadata) + $Table = Get-CIPPTable -TableName 'CippStandardsReports' + $TenantFilter = $Request.Query.tenantFilter + if ($TenantFilter) { + $Table.Filter = "RowKey eq '{0}'" -f $TenantFilter + } + $Results = Get-CIPPAzDataTableEntity @Table #in the results we have objects starting with "standards." All these have to be converted from JSON. Do not do this is its a boolean From 051c3ba64686677f87adb0a79afc9ceb4260de7e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 5 Jun 2025 14:48:58 -0400 Subject: [PATCH 18/19] up version --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index cd1d2e94f31d..8b22a322d0fe 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -8.0.1 +8.0.2 From 6f5b8753b2086120a2dfe1e8ea30c80fa6a266ab Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 5 Jun 2025 16:19:53 -0400 Subject: [PATCH 19/19] Update Invoke-listStandardTemplates.ps1 --- .../Tenant/Standards/Invoke-listStandardTemplates.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 index 717939a6655c..ba4abdb48e30 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 @@ -29,6 +29,11 @@ function Invoke-listStandardTemplates { return } $Data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force + + if (!$Data.excludedTenants) { + $Data | Add-Member -NotePropertyName 'excludedTenants' -NotePropertyValue @() -Force + } + if ($Data.excludedTenants -and $Data.excludedTenants -ne 'excludedTenants') { $Data.excludedTenants = @($Data.excludedTenants) } else {