From 689f4a2e4562b6346f13cee07262e0983f8a054e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 3 Apr 2024 22:11:57 -0400 Subject: [PATCH 01/57] Fix onboarding wizard --- .../Activity Triggers/Push-ExecOnboardTenantQueue.ps1 | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 index b6938cc1389c..cc23c195820c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 @@ -274,11 +274,7 @@ Function Push-ExecOnboardTenantQueue { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Clearing tenant cache' }) $y = 0 do { - try { - Remove-CIPPCache -tenantsOnly $true - } catch {} - - $Tenant = Get-Tenants -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + $Tenant = Get-Tenants -TriggerRefresh -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 $y++ Start-Sleep -Seconds 20 } while (!$Tenant -and $y -le 4) @@ -327,10 +323,7 @@ Function Push-ExecOnboardTenantQueue { $OnboardingSteps.Step4.Status = 'succeeded' $OnboardingSteps.Step4.Message = 'CPV permissions refreshed' if ($Tenant.defaultDomainName -match 'Domain Error') { - try { - Remove-CIPPCache -tenantsOnly $true - } catch {} - $Tenant = Get-Tenants -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + $Tenant = Get-Tenants -TriggerRefresh -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 } } else { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'CPV permissions failed to refresh' }) From 3469de9cf77e15f682e4016c7e916b8b702ebdc0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 4 Apr 2024 15:28:55 -0400 Subject: [PATCH 02/57] Add Get-CIPPAuthentication step before graphtoken --- .../Public/Test-CIPPAccessPermissions.ps1 | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 index f82b937d9e57..ac2629135250 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 @@ -2,7 +2,7 @@ function Test-CIPPAccessPermissions { [CmdletBinding()] param ( $TenantFilter, - $APIName = "Access Check", + $APIName = 'Access Check', $ExecutingUser ) Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Started permissions check' -Sev 'Debug' @@ -20,12 +20,12 @@ function Test-CIPPAccessPermissions { TenantId = '' UserPrincipalName = '' } - Write-Host "Setting success to true by default." + Write-Host 'Setting success to true by default.' $Success = $true try { Set-Location (Get-Item $PSScriptRoot).FullName $ExpectedPermissions = Get-Content '.\SAMManifest.json' | ConvertFrom-Json - + Get-CIPPAuthentication $GraphToken = Get-GraphToken -returnRefresh $true -SkipCache $true if ($GraphToken) { $GraphPermissions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/myorganization/applications?`$filter=appId eq '$env:ApplicationID'" -NoAuthCheck $true @@ -38,7 +38,7 @@ function Test-CIPPAccessPermissions { $KV = $ENV:WEBSITE_DEPLOYMENT_ID $KeyVaultRefresh = Get-AzKeyVaultSecret -VaultName $kv -Name 'RefreshToken' -AsPlainText if ($ENV:RefreshToken -ne $KeyVaultRefresh) { - Write-Host "Setting success to false due to nonmaching token." + Write-Host 'Setting success to false due to nonmaching token.' $Success = $false $Messages.Add('Your refresh token does not match key vault, clear your cache or wait 30 minutes.') | Out-Null @@ -47,43 +47,38 @@ function Test-CIPPAccessPermissions { Href = 'https://docs.cipp.app/setup/installation/cleartokencache' } ) | Out-Null - } - else { + } else { $Messages.Add('Your refresh token matches key vault.') | Out-Null } - } - catch { + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Key vault exception: $($_) " -Sev 'Error' } } try { $AccessTokenDetails = Read-JwtAccessDetails -Token $GraphToken.access_token -erroraction SilentlyContinue - } - catch { + } catch { $AccessTokenDetails = [PSCustomObject]@{ Name = '' AuthMethods = @() } Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $tenant -message "Token exception: $($_) " -Sev 'Error' $Success = $false - Write-Host "Setting success to false due to not able to decode token." + Write-Host 'Setting success to false due to not able to decode token.' } if ($AccessTokenDetails.Name -eq '') { $Messages.Add('Your refresh token is invalid, check for line breaks or missing characters.') | Out-Null - Write-Host "Setting success to false invalid token." + Write-Host 'Setting success to false invalid token.' $Success = $false - } - else { + } else { if ($AccessTokenDetails.AuthMethods -contains 'mfa') { $Messages.Add('Your access token contains the MFA claim.') | Out-Null - } - else { + } else { $Messages.Add('Your access token does not contain the MFA claim, Refresh your SAM tokens.') | Out-Null - Write-Host "Setting success to False due to invalid list of claims." + Write-Host 'Setting success to False due to invalid list of claims.' $Success = $false $Links.Add([PSCustomObject]@{ @@ -107,16 +102,14 @@ function Test-CIPPAccessPermissions { Href = 'https://docs.cipp.app/setup/installation/permissions' } ) | Out-Null - } - else { + } else { $Messages.Add('Your Secure Application Model has all required permissions') | Out-Null } - } - catch { + } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Permissions check failed: $($_) " -Sev 'Error' $Messages.Add("We could not connect to the API to retrieve the permissions. There might be a problem with the secure application model configuration. The returned error is: $(Get-NormalizedError -message $_)") | Out-Null - Write-Host "Setting success to False due to not being able to connect." + Write-Host 'Setting success to False due to not being able to connect.' $Success = $false } From bfc5dda12eb357569f1eea32f4873fc558b79e5c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Apr 2024 12:12:26 +0200 Subject: [PATCH 03/57] temporary removal of sharepoint from processing --- .../Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 index 38d6f00157bc..0dad62fb4d08 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 @@ -90,6 +90,7 @@ function Invoke-PublicWebhooks { Write-Host "Our operations: $Operations" Write-Host "Logs to download: $LogsToDownload" if ($ReceivedItem.ContentType -in $LogsToDownload -or 'AnyLog' -in $LogsToDownload) { + if ($ReceivedItem.ContentType -eq 'SharePoint') { continue } $Data = New-GraphPostRequest -type GET -uri "https://manage.office.com/api/v1.0/$($ReceivedItem.tenantId)/activity/feed/audit/$($ReceivedItem.contentid)" -tenantid $TenantFilter -scope 'https://manage.office.com/.default' } else { Write-Host "No data to download for $($ReceivedItem.ContentType)" From 1ccd4e125e2de76975d71837a1032eeb2c397bc8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Apr 2024 12:13:21 +0200 Subject: [PATCH 04/57] correction on audit.sharepoint --- .../Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 index 0dad62fb4d08..730cbba9d457 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 @@ -90,7 +90,7 @@ function Invoke-PublicWebhooks { Write-Host "Our operations: $Operations" Write-Host "Logs to download: $LogsToDownload" if ($ReceivedItem.ContentType -in $LogsToDownload -or 'AnyLog' -in $LogsToDownload) { - if ($ReceivedItem.ContentType -eq 'SharePoint') { continue } + if ($ReceivedItem.ContentType -eq 'Audit.SharePoint') { continue } $Data = New-GraphPostRequest -type GET -uri "https://manage.office.com/api/v1.0/$($ReceivedItem.tenantId)/activity/feed/audit/$($ReceivedItem.contentid)" -tenantid $TenantFilter -scope 'https://manage.office.com/.default' } else { Write-Host "No data to download for $($ReceivedItem.ContentType)" From 827ebfbd9b8894e621c6eb89fe6db4d1f84fc7e1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 5 Apr 2024 12:13:48 +0200 Subject: [PATCH 05/57] hotfix --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index 6ffbe8ba8ebd..3238344b3b0d 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.4.3 +5.4.4 \ No newline at end of file From 088d58b60be78a80f4b64bd4bf9271610b49a05c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Apr 2024 13:31:34 +0200 Subject: [PATCH 06/57] adds optional field for methods --- .../Push-CIPPAlertMFAAlertUsers.ps1 | 2 +- Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 index e6401a7b117f..6af3ca798606 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertMFAAlertUsers.ps1 @@ -6,7 +6,7 @@ function Push-CIPPAlertMFAAlertUsers { ) try { - $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?$filter=isMfaRegistered eq false and userType eq ''member''&$select=userPrincipalName,lastUpdatedDateTime,isMfaRegistered' -tenantid $($Item.tenant) + $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?$top=999&filter=isMfaRegistered eq false and userType eq ''member''&$select=userPrincipalName,lastUpdatedDateTime,isMfaRegistered' -tenantid $($Item.tenant) if ($users.UserPrincipalName) { Write-AlertMessage -tenant $Item.tenant -message "The following $($users.Count) users do not have MFA registered: $($users.UserPrincipalName -join ', ')" } diff --git a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 index 73a2295b3be1..40eb80366161 100644 --- a/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPMFAState.ps1 @@ -3,7 +3,7 @@ function Get-CIPPMFAState { [CmdletBinding()] param ( $TenantFilter, - $APIName = "Get MFA Status", + $APIName = 'Get MFA Status', $ExecutingUser ) @@ -23,8 +23,7 @@ function Get-CIPPMFAState { Try { $MFARegistration = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails' -tenantid $TenantFilter) - } - catch { + } catch { $CAState.Add('Not Licensed for Conditional Access') | Out-Null $MFARegistration = $null } @@ -51,8 +50,7 @@ function Get-CIPPMFAState { } } } - } - catch { + } catch { } } @@ -68,12 +66,10 @@ function Get-CIPPMFAState { if ($CA -like '*All Users*') { if ($ExcludeAllUsers -contains $_.ObjectId) { $UserCAState.Add("Excluded from $($policy.displayName) - All Users") | Out-Null } else { $UserCAState.Add($CA) | Out-Null } - } - elseif ($CA -like '*Specific Applications*') { + } elseif ($CA -like '*Specific Applications*') { if ($ExcludeSpecific -contains $_.ObjectId) { $UserCAState.Add("Excluded from $($policy.displayName) - Specific Applications") | Out-Null } else { $UserCAState.Add($CA) | Out-Null } - } - else { + } else { Write-Host 'Adding to CA' $UserCAState.Add($CA) | Out-Null } @@ -81,7 +77,8 @@ function Get-CIPPMFAState { $PerUser = if ($_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state -ne $null) { $_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state } else { 'Disabled' } - $MFARegUser = if (($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered -eq $null) { $false } else { ($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered } + $MFARegUser = if (($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName).IsMFARegistered -eq $null) { $false } else { ($MFARegistration | Where-Object -Property UserPrincipalName -EQ $_.UserPrincipalName) } + [PSCustomObject]@{ Tenant = $TenantFilter ID = $_.ObjectId @@ -90,7 +87,8 @@ function Get-CIPPMFAState { AccountEnabled = $_.accountEnabled PerUser = $PerUser isLicensed = $_.isLicensed - MFARegistration = $MFARegUser + MFARegistration = $MFARegUser.IsMFARegistered + MFAMethods = $($MFARegUser.authMethods -join ', ') CoveredByCA = ($UserCAState -join ', ') CoveredBySD = $SecureDefaultsState RowKey = [string]($_.UserPrincipalName).replace('#', '') From 83748f713a31bc5364001a5dee0fc1bc30656868 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 8 Apr 2024 23:33:32 +0200 Subject: [PATCH 07/57] added sort object --- .../Public/Entrypoints/Invoke-ListGraphRequest.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 index 2cd1d9cd9bac..95866c41c395 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 @@ -133,8 +133,13 @@ function Invoke-ListGraphRequest { else { $StatusCode = [HttpStatusCode]::BadRequest } } + if ($request.Query.Sort) { + $GraphRequestData.Results = $GraphRequestData.Results | Sort-Object -Property $request.Query.Sort + } + $Outputdata = $GraphRequestData | ConvertTo-Json -Depth 20 -Compress + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = $GraphRequestData | ConvertTo-Json -Depth 20 -Compress + Body = $Outputdata }) } \ No newline at end of file From febe1806a006802315607a702611a5ce0dcc6e57 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Apr 2024 19:48:48 -0400 Subject: [PATCH 08/57] Fix bug with get-tenants --- Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 index bcb9b94ef9cc..f2e1d746288d 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 @@ -32,11 +32,11 @@ function Get-Tenants { if (($IncludedTenantsCache | Measure-Object).Count -eq 0) { $BuildRequired = $true - } + } if ($BuildRequired -or $TriggerRefresh.IsPresent) { #get the full list of tenants - $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')&`$select=customer,autoExtendDuration,endDateTime" -NoAuthCheck:$true + $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')&`$select=customer,autoExtendDuration,endDateTime" -NoAuthCheck:$true $GDAPList = foreach ($Relationship in $GDAPRelationships) { [PSCustomObject]@{ customerId = $Relationship.customer.tenantId @@ -72,13 +72,13 @@ function Get-Tenants { $defaultDomainName = $Domain $initialDomainName = $Domain $RequiresRefresh = $true - + } catch { Write-LogMessage -API 'Get-Tenants' -message "Tried adding $($LatestRelationship.customerId) to tenant list but failed to get domains - $($_.Exception.Message)" -level 'Critical' } } - + [PSCustomObject]@{ PartitionKey = 'Tenants' RowKey = $_.Name @@ -129,7 +129,7 @@ function Get-Tenants { Add-CIPPAzDataTableEntity @TenantsTable -Entity $IncludedTenantsCache -Force $CurrentTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq false" $CurrentTenants | Where-Object { $_.customerId -notin $IncludedTenantsCache.customerId } | ForEach-Object { - Remove-AzDataTableEntity -Context $TenantsTable -Entity $_ -Force + Remove-AzDataTableEntity @TenantsTable -Entity $_ -Force } } return ($IncludedTenantsCache | Where-Object { $null -ne $_.defaultDomainName -and ($_.defaultDomainName -notmatch 'Domain Error' -or $IncludeAll.IsPresent) } | Sort-Object -Property displayName) From 4f5a8eec6322644c0653e822faa8876f16d58b1f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Apr 2024 19:49:15 -0400 Subject: [PATCH 09/57] Fix bug with graph explorer presets --- .../Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 6587c2c41822..fa2211ecc392 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 @@ -33,7 +33,9 @@ Function Invoke-ExecGraphExplorerPreset { } $params = $Request.Body.preset | Select-Object endpoint, '$filter', '$select', '$count', '$expand', '$search', NoPagination, '$top', IsShared - if ($params.'$select') { $params.'$select' = ($params.'$select').value -join ',' } + if ($params.'$select' -and -not $params.'$select' -is [string]) { + $params.'$select' = ($params.'$select').value -join ',' + } $Preset = [PSCustomObject]@{ PartitionKey = 'Preset' From fb7ed648b774339a49435b420b5c71726945b72c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Apr 2024 19:49:32 -0400 Subject: [PATCH 10/57] Update Invoke-ExecGDAPInvite.ps1 --- .../HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 index d6a5965f2055..4739df9c2df1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/GDAP/Invoke-ExecGDAPInvite.ps1 @@ -64,6 +64,8 @@ Function Invoke-ExecGDAPInvite { } else { $Message = 'Error creating GDAP relationship request' } + + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Info' } } catch { $Message = 'Error creating GDAP relationship' @@ -71,8 +73,6 @@ Function Invoke-ExecGDAPInvite { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $env:TenantID -message "$($Message): $($_.Exception.Message)" -Sev 'Error' } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created GDAP Invite - $InviteUrl" -Sev 'Info' - $body = @{ Message = $Message Invite = $InviteEntity From 8cfca646cff132c4319d4feddaa9ded7ca951de0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 8 Apr 2024 21:06:30 -0400 Subject: [PATCH 11/57] Update Get-Tenants.ps1 --- Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 index f2e1d746288d..2cb51e4d929a 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 @@ -129,7 +129,7 @@ function Get-Tenants { Add-CIPPAzDataTableEntity @TenantsTable -Entity $IncludedTenantsCache -Force $CurrentTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq false" $CurrentTenants | Where-Object { $_.customerId -notin $IncludedTenantsCache.customerId } | ForEach-Object { - Remove-AzDataTableEntity @TenantsTable -Entity $_ -Force + Remove-AzDataTableEntity @TenantsTable -Entity $_ } } return ($IncludedTenantsCache | Where-Object { $null -ne $_.defaultDomainName -and ($_.defaultDomainName -notmatch 'Domain Error' -or $IncludeAll.IsPresent) } | Sort-Object -Property displayName) From b19db2084dead68d861329fe82a897691312dd6c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Apr 2024 12:14:26 +0200 Subject: [PATCH 12/57] add sort --- .../HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 index 5c83df245e5f..c5969ad1d8ae 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeams.ps1 @@ -18,7 +18,7 @@ Function Invoke-ListTeams { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter if ($request.query.type -eq 'List') { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$select=id,displayname,description,visibility,mailNickname" -tenantid $TenantFilter + $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&`$select=id,displayname,description,visibility,mailNickname" -tenantid $TenantFilter | Sort-Object -Property displayName } $TeamID = $request.query.ID Write-Host $TeamID @@ -37,7 +37,7 @@ Function Invoke-ListTeams { Members = @($Members) Owners = @($owners) InstalledApps = @($AppsList) - } + } } From 996c4cba51aae16ef595cb29c5ec5ee75a1aa7f0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Apr 2024 12:21:44 +0200 Subject: [PATCH 13/57] add sort --- .../HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 index 51f31ef28b70..48f3f8badb80 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListSites.ps1 @@ -59,7 +59,7 @@ Function Invoke-ListSites { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode - Body = @($GraphRequest) + Body = @($GraphRequest | Sort-Object -Property UPN) }) } From 5b5d9a78fad186250ac2156e5a417414adaa93b1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Apr 2024 12:33:36 +0200 Subject: [PATCH 14/57] fixes potential issue with arrays becoming singular items. --- .../HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 index 7ff48e0ca1c9..0e632d4fba35 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 @@ -77,7 +77,7 @@ Function Invoke-ListBPA { # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = ($Results | ConvertTo-Json -Depth 15) + Body = (ConvertTo-Json -Depth 15 -InputObject $Results) }) } From d9d493f9a883c8872f7c2a92d6d6be52403985e5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 9 Apr 2024 21:17:59 +0200 Subject: [PATCH 15/57] Module no longer sees this as stopping error, made sure to catch. --- Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 index 6f03e28b64e2..befa8155df6c 100644 --- a/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPAzDataTableEntity.ps1 @@ -9,7 +9,7 @@ function Add-CIPPAzDataTableEntity { foreach ($SingleEnt in $Entity) { try { - Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt + Add-AzDataTableEntity -context $Context -force:$Force -CreateTableIfNotExists:$CreateTableIfNotExists -Entity $SingleEnt -ErrorAction Stop } catch [System.Exception] { if ($_.Exception.ErrorCode -eq 'PropertyValueTooLarge' -or $_.Exception.ErrorCode -eq 'EntityTooLarge') { try { @@ -52,6 +52,8 @@ function Add-CIPPAzDataTableEntity { throw "Error processing entity: $($_.Exception.Message)." } } else { + Write-Host "THE ERROR IS $($_.Exception.ErrorCode)" + throw $_ } } From cf9e895b98b08310cefb6819aaa2dc783f037aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 9 Apr 2024 22:47:14 +0200 Subject: [PATCH 16/57] Add exclaimer domain exclusion and change DA to use TenantGUID --- DomainAnalyser_All/run.ps1 | 1 + DomainAnalyser_GetTenantDomains/run.ps1 | 8 ++++++-- Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/DomainAnalyser_All/run.ps1 b/DomainAnalyser_All/run.ps1 index c9f0b989c04e..9ab5f2d1f85a 100644 --- a/DomainAnalyser_All/run.ps1 +++ b/DomainAnalyser_All/run.ps1 @@ -36,6 +36,7 @@ try { $Result = [PSCustomObject]@{ Tenant = $Tenant.Tenant + TenantID = $Tenant.TenantGUID GUID = $($Domain.Replace('.', '')) LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') Domain = $Domain diff --git a/DomainAnalyser_GetTenantDomains/run.ps1 b/DomainAnalyser_GetTenantDomains/run.ps1 index 4e0fd71f2be8..41cffd655051 100644 --- a/DomainAnalyser_GetTenantDomains/run.ps1 +++ b/DomainAnalyser_GetTenantDomains/run.ps1 @@ -9,10 +9,13 @@ $TenantDomains = $Tenants | ForEach-Object -Parallel { $Tenant = $_ # Get Domains to Lookup try { - $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant.defaultDomainName | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.isVerified) } + $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant.defaultDomainName | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -Notlike '*.excl.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.isVerified) } + foreach ($d in $domains) { [PSCustomObject]@{ Tenant = $Tenant.defaultDomainName + TenantGUID = $Tenant.customerId + InitialDomainName = $Tenant.initialDomainName Domain = $d.id AuthenticationType = $d.authenticationType IsAdminManaged = $d.isAdminManaged @@ -57,11 +60,12 @@ if ($TenantCount -gt 0) { $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $Tenant.Domain $Domain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter - if (!$Domain) { + if (!$Domain -or $null -eq $Domain.TenantGUID) { $DomainObject = [pscustomobject]@{ DomainAnalyser = '' TenantDetails = $TenantDetails TenantId = $Tenant.Tenant + TenantGUID = $Tenant.TenantGUID DkimSelectors = '' MailProviders = '' RowKey = $Tenant.Domain diff --git a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 index e39e6d5953ba..fc4ad53915a1 100644 --- a/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPDomainAnalyser.ps1 @@ -6,7 +6,7 @@ function Get-CIPPDomainAnalyser { # Get all the things if ($TenantFilter -ne 'AllTenants') { - $DomainTable.Filter = "TenantId eq '{0}'" -f $TenantFilter + $DomainTable.Filter = "TenantGUID eq '{0}'" -f $TenantFilter } try { From 3547d95096cecf271a9f47e47b57d6a6b567e6eb Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 10 Apr 2024 14:00:28 -0400 Subject: [PATCH 17/57] Partner center webhook support --- .../Push-PublicWebhookProcess.ps1 | 4 +- .../CIPP/Core/Invoke-ExecPartnerWebhook.ps1 | 46 ++++++++++++++ .../Alerts/Invoke-PublicWebhooks.ps1 | 12 ++++ .../Invoke-CIPPPartnerWebhookProcessing.ps1 | 15 +++++ .../Public/New-CIPPGraphSubscription.ps1 | 61 ++++++++++++++++--- 5 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 create mode 100644 Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 index 4d321a825f4e..639860b0c1af 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 @@ -6,12 +6,14 @@ function Push-PublicWebhookProcess { Invoke-CippGraphWebhookProcessing -Data ($Item.Data | ConvertFrom-Json) -CIPPID $Item.CIPPID -WebhookInfo ($Item.Webhookinfo | ConvertFrom-Json) } elseif ($Item.Type -eq 'AuditLog') { Invoke-CippWebhookProcessing -TenantFilter $Item.TenantFilter -Data ($Item.Data | ConvertFrom-Json) -CIPPPURL $Item.CIPPURL + } elseif ($Item.Type -eq 'PartnerCenter') { + Invoke-CippPartnerCenterWebhookProcessing -Data ($Item.Data | ConvertFrom-Json) } } catch { Write-Host "Webhook Exception: $($_.Exception.Message)" } finally { $WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming $Entity = $Item | Select-Object -Property RowKey, PartitionKey - Remove-AzDataTableEntity @WebhookIncoming -Entity $Entity + Remove-AzDataTableEntity @WebhookIncoming -Entity $Entity } } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 new file mode 100644 index 000000000000..d529d55b1f0f --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 @@ -0,0 +1,46 @@ +function Invoke-ExecPartnerWebhook { + Param($Request, $TriggerMetadata) + + switch ($Request.Query.Action) { + 'ListEventTypes' { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration/events' + $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + 'ListSubscription' { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + 'CreateSubscription' { + $BaseURL = ([System.Uri]$request.headers.'x-ms-original-url').Host + $Webhook = @{ + TenantFilter = $env:TenantId + PartnerCenter = $true + BaseURL = $BaseURL + EventType = $Request.body.EventType + ExecutingUser = $Request.headers.'x-ms-client-principal' + } + $Results = New-CIPPGraphSubscription @Webhook + } + 'SendTest' { + $Results = New-GraphPOSTRequest -uri 'https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents' -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + 'ValidateTest' { + $Results = New-GraphGetRequest -uri "https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/$($Request.Query.CorrelationId)" -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } + default { + $Results = 'Invalid Action' + } + } + + $Body = [PSCustomObject]@{ + Results = $Results + Metadata = [PSCustomObject]@{ + Action = $Request.Query.Action + } + } + + Push-OutputBinding -Name Response -Value @{ + StatusCode = [System.Net.HttpStatusCode]::OK + Body = $Body + } +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 index 730cbba9d457..9f843768ab9b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-PublicWebhooks.ps1 @@ -54,6 +54,18 @@ function Invoke-PublicWebhooks { ## Push webhook data to queue #Invoke-CippGraphWebhookProcessing -Data $ReceivedItem -CIPPID $request.Query.CIPPID -WebhookInfo $Webhookinfo + } elseif ($Request.Query.Type -eq 'PartnerCenter') { + [pscustomobject]$ReceivedItem = $Request.Body + $Entity = [PSCustomObject]@{ + PartitionKey = 'Webhook' + RowKey = [string](New-Guid).Guid + Type = $Request.Query.Type + Data = [string]($ReceivedItem | ConvertTo-Json -Depth 10) + CIPPID = $Request.Query.CIPPID + WebhookInfo = [string]($WebhookInfo | ConvertTo-Json -Depth 10) + FunctionName = 'PublicWebhookProcess' + } + Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity } else { # Auditlog Subscriptions try { diff --git a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 new file mode 100644 index 000000000000..7cf9d8bb15cf --- /dev/null +++ b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 @@ -0,0 +1,15 @@ +function Invoke-CippPartnerWebhookProcessing { + [CmdletBinding()] + param ( + $Data + ) + + Switch ($Data.EventType) { + 'test-created' { + Write-LogMessage -API 'Webhooks' -message 'Partner Center webhook test received' -Sev 'Info' + } + default { + Write-LogMessage -API 'Webhooks' -message "Partner Center webhook received: $($Data | ConvertTo-Json -Depth 5)" -Sev 'Info' + } + } +} diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index 6eac507448ec..9f6da599425b 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -11,7 +11,8 @@ function New-CIPPGraphSubscription { $EventType, $APIName = 'Create Webhook', $ExecutingUser, - [Switch]$Recreate + [Switch]$Recreate, + [switch]$PartnerCenter ) $CIPPID = (New-Guid).GUID $WebhookTable = Get-CIPPTable -TableName webhookTable @@ -76,13 +77,57 @@ function New-CIPPGraphSubscription { } } } + } elseif ($PartnerCenter.IsPresent) { + $WebhookFilter = "PartitionKey eq '$($env:TenantId)'" + $ExistingWebhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter $WebhookFilter + $CIPPID = $env:TenantId + $MatchedWebhook = $ExistingWebhooks | Where-Object { $_.Resource -eq 'PartnerCenter' -and $_.RowKey -eq $CIPPID } + + # Required event types + $EventList = [System.Collections.Generic.List[string]]@('test-created', 'granular-admin-relationship-approved') + if (($EventType | Measure-Object).count -gt 0) { + $EventList.AddRange($EventType) + } + + $Body = [PSCustomObject]@{ + WebhookUrl = "https://$BaseURL/API/PublicWebhooks?CIPPID=$($CIPPID)&Type=PartnerCenter" + WebhookEvents = @($EventList) + } + try { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + $Subscription = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + if ($Subscription.WebhookUrl -ne $MatchedWebhook.WebhookNotificationUrl) { + + if ($Subscription.WebhookUrl) { $Method = 'PUT' } else { $Method = 'POST' } + $GraphRequest = New-GraphPOSTRequest -uri 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' -type $Method -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' -body ($Body | ConvertTo-Json) -verbose + + $WebhookRow = @{ + PartitionKey = [string]$CIPPID + RowKey = [string]$CIPPID + EventType = [string](ConvertTo-Json -InputObject $EventList) + Resource = [string]'PartnerCenter' + SubscriptionID = [string]$GraphRequest.SubscriberId + Expiration = 'Does Not Expire' + WebhookNotificationUrl = [string]$Body.WebhookUrl + } + $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow + Write-LogMessage -user $ExecutingUser -API $APIName -message 'Created Partner Center Webhook subscription' -Sev 'Info' -tenant 'PartnerTenant' + return 'Created Partner Center Webhook subscription' + } else { + Write-LogMessage -user $ExecutingUser -API $APIName -message 'Existing Partner Center Webhook subscription found' -Sev 'Info' -tenant 'PartnerTenant' + return 'Existing Partner Center Webhook subscription found' + } + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Partner Center Webhook Subscription: $($_.Exception.Message)" -Sev 'Error' -tenant 'PartnerTenant' + return "Failed to create Partner Center Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" + } + } else { # First check if there is an exsiting Webhook in place $WebhookFilter = "PartitionKey eq '$($TenantFilter)'" $ExistingWebhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter $WebhookFilter $MatchedWebhook = $ExistingWebhooks | Where-Object { $_.Resource -eq $Resource } - if (($MatchedWebhook | Measure-Object).count -eq 0 -or $Recreate) { - + if (($MatchedWebhook | Measure-Object).count -eq 0 -or $Recreate.IsPresent) { $expiredate = (Get-Date).AddDays(1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') $params = @{ changeType = $TypeofSubscription @@ -90,10 +135,10 @@ function New-CIPPGraphSubscription { resource = $Resource expirationDateTime = $expiredate } | ConvertTo-Json - + $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/subscriptions' -tenantid $TenantFilter -type POST -body $params -verbose - #If creation is succesfull, we store the GUID in the storage table webhookTable to make sure we can check against this later on. + #If creation is succesfull, we store the GUID in the storage table webhookTable to make sure we can check against this later on. #We store the GUID as rowkey, the event type, the resource, and the expiration date as properties, we also add the Tenant name so we can easily find this later on. #We don't store the return, because Ms decided that a renewal or re-authenticate does not change the url, but does change the id... $WebhookRow = @{ @@ -108,7 +153,7 @@ function New-CIPPGraphSubscription { } $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow #todo: add remove webhook function, add check webhook function, add list webhooks function - #add refresh webhook function based on table. + #add refresh webhook function based on table. Write-LogMessage -user $ExecutingUser -API $APIName -message "Created Graph Webhook subscription for $($TenantFilter)" -Sev 'Info' -tenant $TenantFilter } else { Write-LogMessage -user $ExecutingUser -API $APIName -message "Existing Graph Webhook subscription for $($TenantFilter) found" -Sev 'Info' -tenant $TenantFilter @@ -117,8 +162,6 @@ function New-CIPPGraphSubscription { return "Created Webhook subscription for $($TenantFilter)" } catch { Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Webhook Subscription: $($_.Exception.Message)" -Sev 'Error' -tenant $TenantFilter - Return "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" + Return "Failed to create Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" } - } - From b38ed7f6aaa94bc1521f34578f257f00caa1933d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 10 Apr 2024 20:00:12 -0400 Subject: [PATCH 18/57] tweak partner webhook --- .../Public/New-CIPPGraphSubscription.ps1 | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index 9f6da599425b..0dabb10544ba 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -86,20 +86,37 @@ function New-CIPPGraphSubscription { # Required event types $EventList = [System.Collections.Generic.List[string]]@('test-created', 'granular-admin-relationship-approved') if (($EventType | Measure-Object).count -gt 0) { - $EventList.AddRange($EventType) + foreach ($Event in $EventType) { + if ($EventList -notcontains $Event) { + $EventList.Add($Event) + } + } } $Body = [PSCustomObject]@{ WebhookUrl = "https://$BaseURL/API/PublicWebhooks?CIPPID=$($CIPPID)&Type=PartnerCenter" WebhookEvents = @($EventList) } + $EventCompare = Compare-Object $EventList ($MatchedWebhook.EventType | ConvertFrom-Json) try { $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' - $Subscription = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' - if ($Subscription.WebhookUrl -ne $MatchedWebhook.WebhookNotificationUrl) { - - if ($Subscription.WebhookUrl) { $Method = 'PUT' } else { $Method = 'POST' } - $GraphRequest = New-GraphPOSTRequest -uri 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' -type $Method -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' -body ($Body | ConvertTo-Json) -verbose + try { + $Existing = New-GraphGetRequest -NoAuthCheck $true -uri $Uri -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' + } catch {} + if ($Existing.webhookUrl -ne $MatchedWebhook.WebhookNotificationUrl -or $EventCompare) { + if (![string]::IsNullOrEmpty($MatchedWebhook.WebhookNotificationUrl) -or $Existing.WebhookUrl) { + $Action = 'Updated' + $Method = 'PUT' + Write-Host 'updating webhook' + } else { + $Action = 'Created' + $Method = 'POST' + Write-Host 'creating webhook' + } + try { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + $GraphRequest = New-GraphPOSTRequest -uri $Uri -type $Method -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' -body ($Body | ConvertTo-Json) + } catch {} $WebhookRow = @{ PartitionKey = [string]$CIPPID @@ -110,16 +127,16 @@ function New-CIPPGraphSubscription { Expiration = 'Does Not Expire' WebhookNotificationUrl = [string]$Body.WebhookUrl } - $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow - Write-LogMessage -user $ExecutingUser -API $APIName -message 'Created Partner Center Webhook subscription' -Sev 'Info' -tenant 'PartnerTenant' - return 'Created Partner Center Webhook subscription' + $null = Add-CIPPAzDataTableEntity @WebhookTable -Entity $WebhookRow -Force + Write-LogMessage -user $ExecutingUser -API $APIName -message "$Action Partner Center Webhook subscription" -Sev 'Info' -tenant 'PartnerTenant' + return "$Action Partner Center Webhook subscription" } else { Write-LogMessage -user $ExecutingUser -API $APIName -message 'Existing Partner Center Webhook subscription found' -Sev 'Info' -tenant 'PartnerTenant' return 'Existing Partner Center Webhook subscription found' } } catch { Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to create Partner Center Webhook Subscription: $($_.Exception.Message)" -Sev 'Error' -tenant 'PartnerTenant' - return "Failed to create Partner Center Webhook Subscription for $($TenantFilter): $($_.Exception.Message)" + return "Failed to create Partner Webhook Subscription: $($_.Exception.Message)" } } else { From 93046537dac6b68485584dfb46609b3807827a37 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 10 Apr 2024 20:03:27 -0400 Subject: [PATCH 19/57] cleanup function --- .../Scheduler/Invoke-ExecScheduledCommand.ps1 | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ExecScheduledCommand.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ExecScheduledCommand.ps1 deleted file mode 100644 index ce73476b5c97..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Scheduler/Invoke-ExecScheduledCommand.ps1 +++ /dev/null @@ -1,93 +0,0 @@ - using namespace System.Net - - Function Invoke-ExecScheduledCommand { - <# - .FUNCTIONALITY - Entrypoint - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $commandParameters = $QueueItem.Parameters - -$tenant = $QueueItem.Parameters['TenantFilter'] -Write-Host 'started task' -try { - try { - $results = & $QueueItem.command @commandParameters - } - catch { - $results = "Task Failed: $($_.Exception.Message)" - - } - - Write-Host 'ran the command' - if ($results -is [String]) { - $results = @{ Results = $results } - } - if ($results -is [array]) { - $results = $results | Where-Object { $_ -is [string] } - $results = $results | ForEach-Object { @{ Results = $_ } } - } - - $results = $results | Select-Object * -ExcludeProperty RowKey, PartitionKey - - $StoredResults = $results | ConvertTo-Json -Compress -Depth 20 | Out-String - if ($StoredResults.Length -gt 64000 -or $task.Tenant -eq 'AllTenants') { - $StoredResults = @{ Results = 'The results for this query are too long to store in this table, or the query was meant for All Tenants. Please use the options to send the results to another target to be able to view the results. ' } | ConvertTo-Json -Compress - } -} -catch { - $errorMessage = $_.Exception.Message - if ($task.Recurrence -gt 0) { $State = 'Failed - Planned' } else { $State = 'Failed' } - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$errorMessage" - TaskState = $State - } - Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to execute task $($task.Name): $errorMessage" -sev Error -} - - -$TableDesign = '' -$HTML = ($results | Select-Object * -ExcludeProperty RowKey, PartitionKey | ConvertTo-Html -Fragment) -replace '', "$TableDesign
" | Out-String -$title = "Scheduled Task $($task.Name) - $($task.ExpectedRunTime)" -Write-Host $title -switch -wildcard ($task.PostExecution) { - '*psa*' { Send-CIPPAlert -Type 'psa' -Title $title -HTMLContent $HTML } - '*email*' { Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML } - '*webhook*' { - $Webhook = [PSCustomObject]@{ - 'Tenant' = $tenant - 'TaskInfo' = $QueueItem.TaskInfo - 'Results' = $Results - } - Send-CIPPAlert -Type 'webhook' -Title $title -JSONContent $($Webhook | ConvertTo-Json -Depth 20) - } -} - -Write-Host 'ran the command' - -if ($task.Recurrence -le '0' -or $task.Recurrence -eq $null) { - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$StoredResults" - TaskState = 'Completed' - } -} -else { - $nextRun = (Get-Date).AddDays($task.Recurrence) - $nextRunUnixTime = [int64]($nextRun - (Get-Date '1/1/1970')).TotalSeconds - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$StoredResults" - TaskState = 'Planned' - ScheduledTime = "$nextRunUnixTime" - } -} -Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Successfully executed task: $($task.name)" -sev Info - - } From c4f4732a2382fb6cf906d983a714157a6fbbace3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 12:51:55 +0200 Subject: [PATCH 20/57] adds name based options to templates. --- .../Conditional/Invoke-AddCATemplate.ps1 | 22 +++++++++++++++ Modules/CIPPCore/Public/Set-CIPPSignature.ps1 | 27 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Modules/CIPPCore/Public/Set-CIPPSignature.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 index 7fc95e0d7af1..666784adeff7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 @@ -40,6 +40,28 @@ Function Invoke-AddCATemplate { } if ($excludelocations) { $JSON.conditions.locations.excludeLocations = $excludelocations } + if ($JSON.conditions.users.includeUsers) { + $JSON.conditions.users.includeUsers = @($JSON.conditions.users.includeUsers | ForEach-Object { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName + }) + } + + if ($JSON.conditions.users.excludeUsers) { + $JSON.conditions.users.excludeUsers = @($JSON.conditions.users.excludeUsers | ForEach-Object { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName + }) + } + + if ($JSON.conditions.users.includeGroups) { + $JSON.conditions.users.includeGroups = @($JSON.conditions.users.includeGroups | ForEach-Object { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName + }) + } + if ($JSON.conditions.users.excludeGroups) { + $JSON.conditions.users.excludeGroups = @($JSON.conditions.users.excludeGroups | ForEach-Object { + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName + }) + } $JSON | Add-Member -NotePropertyName 'LocationInfo' -NotePropertyValue @($IncludeJSON, $ExcludeJSON) diff --git a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 new file mode 100644 index 000000000000..5bfa8c174d8c --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 @@ -0,0 +1,27 @@ +function Set-CIPPOutOfOffice { + [CmdletBinding()] + param ( + $userid, + $InternalMessage, + $ExternalMessage, + $TenantFilter, + $State, + $APIName = 'Set Outlook Roaming Signature', + $ExecutingUser, + $StartTime, + $EndTime + ) + + try { + $SignatureProfile = @' +[{"name":"Roaming_New_Signature","itemClass":"","id":"","scope":"AdeleV@M365x42953883.OnMicrosoft.com","parentSetting":"","secondaryKey":"","type":"String","timestamp":638296273181532792,"metadata":"","value":"Kelvin","isFirstSync":"true","source":"UserOverride"}] +'@ + $GraphRequest = New-GraphPostRequest -uri 'https://substrate.office.com/ows/beta/outlookcloudsettings/settings/global' -tenantid $TenantFilter -type GET -contentType 'application/json' -verbose -scope 'https://outlook.office.com/.default' + Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev 'Info' -tenant $TenantFilter + return "Set Out-of-office for $($userid) to $state." + + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter + return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" + } +} From 5e044ef19221828267752901ce85ebb713315b26 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 14:13:28 +0200 Subject: [PATCH 21/57] add ability to replace users & groups in policy. --- .../Tenant/Conditional/Invoke-AddCAPolicy.ps1 | 5 ++- Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 31 ++++++++++++++++--- Modules/CIPPCore/Public/Set-CIPPSignature.ps1 | 2 +- 3 files changed, 30 insertions(+), 8 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 ab6635459e4b..547828a337f4 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 @@ -16,11 +16,10 @@ Function Invoke-AddCAPolicy { $results = foreach ($Tenant in $tenants) { try { - $CAPolicy = New-CIPPCAPolicy -TenantFilter $tenant -state $request.body.NewState -RawJSON $Request.body.RawJSON -APIName $APIName -ExecutingUser $request.headers.'x-ms-client-principal' + $CAPolicy = New-CIPPCAPolicy -replacePattern $Request.body.replacename -Overwrite $request.body.overwrite -TenantFilter $tenant -state $request.body.NewState -RawJSON $Request.body.RawJSON -APIName $APIName -ExecutingUser $request.headers.'x-ms-client-principal' Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added Conditional Access Policy $($Displayname)" -Sev 'Info' "Successfully added Conditional Access Policy for $($Tenant)" - } - catch { + } catch { "Failed to add policy for $($Tenant): $($_.Exception.Message)" Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Failed to add Conditional Access Policy $($Displayname). Error: $($_.Exception.Message)" -Sev 'Error' continue diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 1d749a69bf76..38f9a89b4ba0 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -6,6 +6,7 @@ function New-CIPPCAPolicy { $TenantFilter, $State, $Overwrite, + $ReplacePattern = 'none', $APIName = 'Create CA Policy', $ExecutingUser ) @@ -101,19 +102,41 @@ function New-CIPPCAPolicy { $index = [array]::IndexOf($JSONObj.conditions.locations.excludeLocations, $location) $JSONObj.conditions.locations.excludeLocations[$index] = $lookup.id } - + switch ($ReplacePattern) { + 'none' { + Write-Host 'Replacement pattern for inclusions and exclusions is none' + break + } + 'AllUsers' { + Write-Host 'Replacement pattern for inclusions and exclusions is All users. This policy will now apply to everyone.' + $JSONObj.conditions.users.includeUsers = @('All') + $JSONObj.conditions.users.excludeUsers = @() + $JSONObj.conditions.users.includeGroups = @() + $JSONObj.conditions.users.excludeGroups = @() + } + 'displayName' { + Write-Host 'Replacement pattern for inclusions and exclusions is displayName.' + $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/users?$select=id,displayName' -tenantid $TenantFilter + $Groups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName' -tenantid $TenantFilter + $JSONObj.conditions.users.includeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.includeUsers).id) + $JSONObj.conditions.users.excludeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeUsers).id) + $JSONObj.conditions.users.includeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.includeGroups).id) + $JSONObj.conditions.users.excludeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeGroups).id) + } + + } $JsonObj.PSObject.Properties.Remove('LocationInfo') $RawJSON = $JSONObj | ConvertTo-Json -Depth 10 Write-Host $RawJSON try { Write-Host 'Checking' - $CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $TenantFilter - if ($displayname -in $CheckExististing.displayName) { + $CheckExististing = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $TenantFilter | Where-Object -Property displayName -EQ $displayname + if ($CheckExististing) { if ($Overwrite -ne $true) { Throw "Conditional Access Policy with Display Name $($Displayname) Already exists" return $false } else { - Write-Host 'overwriting' + Write-Host "overwriting $($CheckExististing.id)" $PatchRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Updated Conditional Access Policy $($JSONObj.Displayname) to the template standard." -Sev 'Info' return "Updated policy $displayname for $tenantfilter" diff --git a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 index 5bfa8c174d8c..fa2de7dc415b 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 @@ -16,7 +16,7 @@ function Set-CIPPOutOfOffice { $SignatureProfile = @' [{"name":"Roaming_New_Signature","itemClass":"","id":"","scope":"AdeleV@M365x42953883.OnMicrosoft.com","parentSetting":"","secondaryKey":"","type":"String","timestamp":638296273181532792,"metadata":"","value":"Kelvin","isFirstSync":"true","source":"UserOverride"}] '@ - $GraphRequest = New-GraphPostRequest -uri 'https://substrate.office.com/ows/beta/outlookcloudsettings/settings/global' -tenantid $TenantFilter -type GET -contentType 'application/json' -verbose -scope 'https://outlook.office.com/.default' + $GraphRequest = New-GraphPostRequest -uri 'https://substrate.office.com/ows/beta/outlookcloudsettings/settings/global' -tenantid $TenantFilter -type PATCH -contentType 'application/json' -verbose -scope 'https://outlook.office.com/.default' Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev 'Info' -tenant $TenantFilter return "Set Out-of-office for $($userid) to $state." From a335fad6d32f364c0b215771fca01b0ca8160047 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 15:55:00 +0200 Subject: [PATCH 22/57] added if statements --- .../Tenant/Conditional/Invoke-AddCATemplate.ps1 | 4 ++++ Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 | 17 +++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 index 666784adeff7..3f4b8d651650 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-AddCATemplate.ps1 @@ -42,23 +42,27 @@ Function Invoke-AddCATemplate { if ($excludelocations) { $JSON.conditions.locations.excludeLocations = $excludelocations } if ($JSON.conditions.users.includeUsers) { $JSON.conditions.users.includeUsers = @($JSON.conditions.users.includeUsers | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName }) } if ($JSON.conditions.users.excludeUsers) { $JSON.conditions.users.excludeUsers = @($JSON.conditions.users.excludeUsers | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $TenantFilter).displayName }) } if ($JSON.conditions.users.includeGroups) { $JSON.conditions.users.includeGroups = @($JSON.conditions.users.includeGroups | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName }) } if ($JSON.conditions.users.excludeGroups) { $JSON.conditions.users.excludeGroups = @($JSON.conditions.users.excludeGroups | ForEach-Object { + if ($_ -in 'All', 'None', 'GuestOrExternalUsers') { return $_ } (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups/$($_)" -tenantid $TenantFilter).displayName }) } diff --git a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 index 38f9a89b4ba0..78f3173c2aee 100644 --- a/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1 @@ -109,19 +109,20 @@ function New-CIPPCAPolicy { } 'AllUsers' { Write-Host 'Replacement pattern for inclusions and exclusions is All users. This policy will now apply to everyone.' - $JSONObj.conditions.users.includeUsers = @('All') - $JSONObj.conditions.users.excludeUsers = @() - $JSONObj.conditions.users.includeGroups = @() - $JSONObj.conditions.users.excludeGroups = @() + if ($JSONObj.conditions.users.includeUsers -ne 'All') { $JSONObj.conditions.users.includeUsers = @('All') } + if ($JSONObj.conditions.users.excludeUsers) { $JSONObj.conditions.users.excludeUsers = @() } + if ($JSONObj.conditions.users.includeGroups) { $JSONObj.conditions.users.includeGroups = @() } + if ($JSONObj.conditions.users.excludeGroups) { $JSONObj.conditions.users.excludeGroups = @() } } 'displayName' { Write-Host 'Replacement pattern for inclusions and exclusions is displayName.' $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/users?$select=id,displayName' -tenantid $TenantFilter $Groups = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName' -tenantid $TenantFilter - $JSONObj.conditions.users.includeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.includeUsers).id) - $JSONObj.conditions.users.excludeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeUsers).id) - $JSONObj.conditions.users.includeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.includeGroups).id) - $JSONObj.conditions.users.excludeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeGroups).id) + + if ($JSONObj.conditions.users.includeUsers -notin 'All', 'None', 'GuestOrExternalUsers') { $JSONObj.conditions.users.includeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.includeUsers).id) } + if ($JSONObj.conditions.users.excludeUsers) { $JSONObj.conditions.users.excludeUsers = @(($users | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeUsers).id) } + if ($JSONObj.conditions.users.includeGroups) { $JSONObj.conditions.users.includeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.includeGroups).id) } + if ($JSONObj.conditions.users.excludeGroups) { $JSONObj.conditions.users.excludeGroups = @(($groups | Where-Object -Property displayName -In $JSONObj.conditions.users.excludeGroups).id) } } } From 3462e3bad4369a9843509cc7c4da6505238ff5ae Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 10:05:59 -0400 Subject: [PATCH 23/57] Update New-CIPPGraphSubscription.ps1 --- Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index 0dabb10544ba..c36f9cbe8e06 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -104,7 +104,7 @@ function New-CIPPGraphSubscription { $Existing = New-GraphGetRequest -NoAuthCheck $true -uri $Uri -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' } catch {} if ($Existing.webhookUrl -ne $MatchedWebhook.WebhookNotificationUrl -or $EventCompare) { - if (![string]::IsNullOrEmpty($MatchedWebhook.WebhookNotificationUrl) -or $Existing.WebhookUrl) { + if ($Existing.WebhookUrl) { $Action = 'Updated' $Method = 'PUT' Write-Host 'updating webhook' From 8ecb758fb43258417441fbfc46ef4d4cfc488b12 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 10:14:18 -0400 Subject: [PATCH 24/57] Update New-CIPPGraphSubscription.ps1 --- Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index c36f9cbe8e06..e744232e9e60 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -113,10 +113,9 @@ function New-CIPPGraphSubscription { $Method = 'POST' Write-Host 'creating webhook' } - try { - $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' - $GraphRequest = New-GraphPOSTRequest -uri $Uri -type $Method -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' -body ($Body | ConvertTo-Json) - } catch {} + + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + $GraphRequest = New-GraphPOSTRequest -uri $Uri -type $Method -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' -body ($Body | ConvertTo-Json) -NoAuthCheck $true $WebhookRow = @{ PartitionKey = [string]$CIPPID From c92bc80219c40b86b7ecf6e1eae8e02cb4ddc0d1 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 10:35:18 -0400 Subject: [PATCH 25/57] typo --- .../Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 index 639860b0c1af..fa4872195b0e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-PublicWebhookProcess.ps1 @@ -7,7 +7,7 @@ function Push-PublicWebhookProcess { } elseif ($Item.Type -eq 'AuditLog') { Invoke-CippWebhookProcessing -TenantFilter $Item.TenantFilter -Data ($Item.Data | ConvertFrom-Json) -CIPPPURL $Item.CIPPURL } elseif ($Item.Type -eq 'PartnerCenter') { - Invoke-CippPartnerCenterWebhookProcessing -Data ($Item.Data | ConvertFrom-Json) + Invoke-CippPartnerWebhookProcessing -Data ($Item.Data | ConvertFrom-Json) } } catch { Write-Host "Webhook Exception: $($_.Exception.Message)" From 12738899aafb6b6c90ff99c39a8371a8331b3837 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 10:54:28 -0400 Subject: [PATCH 26/57] Add audit log collection --- .../Public/Invoke-CIPPPartnerWebhookProcessing.ps1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 index 7cf9d8bb15cf..30469dc0066f 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 @@ -4,12 +4,17 @@ function Invoke-CippPartnerWebhookProcessing { $Data ) - Switch ($Data.EventType) { + Switch ($Data.EventName) { 'test-created' { Write-LogMessage -API 'Webhooks' -message 'Partner Center webhook test received' -Sev 'Info' } default { - Write-LogMessage -API 'Webhooks' -message "Partner Center webhook received: $($Data | ConvertTo-Json -Depth 5)" -Sev 'Info' + if ($Data.AuditUri) { + $AuditLog = New-GraphGetRequest -uri $Data.AuditUri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + Write-Logessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log: $($AuditLog | ConvertTo-Json -Depth 5)" -Sev 'Info' + } else { + Write-LogMessage -API 'Webhooks' -message "Partner Center webhook received (no audit): $($Data | ConvertTo-Json -Depth 5)" -Sev 'Info' + } } } } From 9ec3b79a91227f4c4c535b1d1a86219fdfaf8ea0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 11:49:26 -0400 Subject: [PATCH 27/57] Logging improvemnt Add Get-CippException Add LogData parameter to Write-LogMessage --- .../Public/GraphHelper/Get-CippException.ps1 | 14 ++++++++++++++ .../Public/GraphHelper/Write-LogMessage.ps1 | 16 ++++++++++++++-- .../Invoke-CIPPPartnerWebhookProcessing.ps1 | 4 ++-- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 Modules/CIPPCore/Public/GraphHelper/Get-CippException.ps1 diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-CippException.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-CippException.ps1 new file mode 100644 index 000000000000..92c4d936dd22 --- /dev/null +++ b/Modules/CIPPCore/Public/GraphHelper/Get-CippException.ps1 @@ -0,0 +1,14 @@ +function Get-CippException { + Param( + $Exception + ) + + [PSCustomObject]@{ + Message = $Exception.Exception.Message + NormalizedError = Get-NormalizedError -message $Exception.Exception.Message + Position = $Exception.InvocationInfo.PositionMessage + ScriptName = $Exception.InvocationInfo.ScriptName + LineNumber = $Exception.InvocationInfo.ScriptLineNumber + Category = $Exception.CategoryInfo.ToString() + } +} diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 index fbef7fae41bf..8dec84538272 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 @@ -1,14 +1,25 @@ -function Write-LogMessage ($message, $tenant = 'None', $API = 'None', $tenantId = $null, $user, $sev) { +function Write-LogMessage { <# .FUNCTIONALITY Internal #> + Param( + $message, + $tenant = 'None', + $API = 'None', + $tenantId = $null, + $user, + $sev, + $LogData = '' + ) try { $username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($user)) | ConvertFrom-Json).userDetails } catch { $username = $user } + if ($LogData) { $LogData = ConvertTo-Json -InputObject $LogData -Depth 10 -Compress } + $Table = Get-CIPPTable -tablename CippLogs if (!$tenant) { $tenant = 'None' } @@ -27,13 +38,14 @@ function Write-LogMessage ($message, $tenant = 'None', $API = 'None', $tenantId 'SentAsAlert' = $false 'PartitionKey' = $PartitionKey 'RowKey' = ([guid]::NewGuid()).ToString() + 'LogData' = [string]$LogData } if ($tenantId) { $TableRow.Add('TenantID', [string]$tenantId) } - + $Table.Entity = $TableRow Add-CIPPAzDataTableEntity @Table | Out-Null } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 index 30469dc0066f..e7d6a279dc2f 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 @@ -11,9 +11,9 @@ function Invoke-CippPartnerWebhookProcessing { default { if ($Data.AuditUri) { $AuditLog = New-GraphGetRequest -uri $Data.AuditUri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' - Write-Logessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log: $($AuditLog | ConvertTo-Json -Depth 5)" -Sev 'Info' + Write-Logessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log received" -LogData $AuditLog -Sev 'Info' } else { - Write-LogMessage -API 'Webhooks' -message "Partner Center webhook received (no audit): $($Data | ConvertTo-Json -Depth 5)" -Sev 'Info' + Write-LogMessage -API 'Webhooks' -message 'Partner Center webhook received (no audit)' -LogData $Data -Sev 'Info' } } } From f40d30bf07d15aba5189fdc7052ea31998ca44cd Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 11 Apr 2024 18:29:13 +0200 Subject: [PATCH 28/57] added standards templates --- .../Standards/Invoke-AddStandardsDeploy.ps1 | 2 +- .../Standards/Invoke-AddStandardsTemplate.ps1 | 32 +++++++++++++++++++ .../Invoke-listStandardTemplates.ps1 | 27 ++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 index 567452b932c7..a97bb06cf6de 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsDeploy.ps1 @@ -15,7 +15,7 @@ Function Invoke-AddStandardsDeploy { $username = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($user)) | ConvertFrom-Json).userDetails try { - $Tenants = ($Request.body | Select-Object Select_*).psobject.properties.value + $Tenants = $Request.body.Tenant $Settings = ($request.body | Select-Object -Property *, v2* -ExcludeProperty Select_*, None ) $Settings | Add-Member -NotePropertyName 'v2.1' -NotePropertyValue $true -Force if ($Settings.phishProtection.remediate) { 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 new file mode 100644 index 000000000000..27c8774bae3c --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-AddStandardsTemplate.ps1 @@ -0,0 +1,32 @@ +using namespace System.Net + +Function Invoke-AddStandardsTemplate { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $GUID = (New-Guid).GUID + $JSON = (ConvertTo-Json -Depth 100 -InputObject ($Request.body | Select-Object standards, name)) + $Table = Get-CippTable -tablename 'templates' + $Table.Force = $true + Add-CIPPAzDataTableEntity @Table -Entity @{ + JSON = "$JSON" + RowKey = "$GUID" + PartitionKey = 'StandardsTemplate' + GUID = "$GUID" + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Created CA Template $($Request.body.name) with GUID $GUID" -Sev 'Debug' + $body = [pscustomobject]@{'Results' = 'Successfully added template' } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $body + }) + +} \ No newline at end of file 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 new file mode 100644 index 000000000000..acc984d6e0ab --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-listStandardTemplates.ps1 @@ -0,0 +1,27 @@ +using namespace System.Net + +Function Invoke-listStandardTemplates { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + + $Table = Get-CippTable -tablename 'templates' + $Filter = "PartitionKey eq 'StandardsTemplate'" + $Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter) | ForEach-Object { + $data = $_.JSON | ConvertFrom-Json -Depth 100 + $data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force + $data + } | Sort-Object -Property displayName + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($Templates) + }) + +} From 32e798b512c6d78c3e2c88bfcc36542522fc1d46 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 13:04:11 -0400 Subject: [PATCH 29/57] Add exception objects to logs --- Activity_AddOrUpdateTableRows/run.ps1 | 13 +++++----- Applications_Orchestrator/run.ps1 | 2 +- Applications_Upload/run.ps1 | 24 +++++++++---------- BestPracticeAnalyser_All/run.ps1 | 2 +- DomainAnalyser_All/run.ps1 | 16 ++++++------- DomainAnalyser_GetTenantDomains/run.ps1 | 6 ++--- DomainAnalyser_Orchestration/run.ps1 | 2 +- ExecSchedulerBillingRun/run.ps1 | 9 ++++--- .../CIPPCore/Public/Add-CIPPGroupMember.ps1 | 14 +++++------ .../Public/Entrypoints/Invoke-ListLogs.ps1 | 10 +++++--- .../Public/Get-CIPPAuthentication.ps1 | 2 +- .../CIPPCore/Public/Get-CIPPBitlockerKey.ps1 | 9 ++++--- profile.ps1 | 5 ++-- 13 files changed, 58 insertions(+), 56 deletions(-) diff --git a/Activity_AddOrUpdateTableRows/run.ps1 b/Activity_AddOrUpdateTableRows/run.ps1 index 6bb7e218b987..adc07df73e23 100644 --- a/Activity_AddOrUpdateTableRows/run.ps1 +++ b/Activity_AddOrUpdateTableRows/run.ps1 @@ -3,11 +3,10 @@ $TableName = ($TableParams.Context['TableName']) $Table = Get-CippTable -tablename $TableName foreach ($param in $TableParams.Entity) { - try { - #Sending each item indivually, if it fails, log an error. - Add-CIPPAzDataTableEntity @Table -Entity $param -Force - } - catch { - Write-LogMessage -API 'Activity_AddOrUpdateTableRows' -message "Unable to write to '$($TableParams.TableName)' Using RowKey $($param.RowKey) table: $($_.Exception.Message)" -sev error - } + try { + #Sending each item indivually, if it fails, log an error. + Add-CIPPAzDataTableEntity @Table -Entity $param -Force + } catch { + Write-LogMessage -API 'Activity_AddOrUpdateTableRows' -message "Unable to write to '$($TableParams.TableName)' Using RowKey $($param.RowKey)" -LogData (Get-CippException -Exception $_) -sev error + } } diff --git a/Applications_Orchestrator/run.ps1 b/Applications_Orchestrator/run.ps1 index 9c8457ff91fc..ebf60eb55628 100644 --- a/Applications_Orchestrator/run.ps1 +++ b/Applications_Orchestrator/run.ps1 @@ -17,7 +17,7 @@ try { $Outputs = Wait-ActivityFunction -Task $ParallelTasks Write-Host $Outputs } -catch { +catch { Write-Host "Applications_Orchestrator exception: $($_.Exception.Message)" } finally { diff --git a/Applications_Upload/run.ps1 b/Applications_Upload/run.ps1 index a92637a42882..b1aacdc41318 100644 --- a/Applications_Upload/run.ps1 +++ b/Applications_Upload/run.ps1 @@ -1,14 +1,14 @@ param($name) $Table = Get-CippTable -tablename 'apps' -$Filter = "PartitionKey eq 'apps' and RowKey eq '$name'" +$Filter = "PartitionKey eq 'apps' and RowKey eq '$name'" Set-Location (Get-Item $PSScriptRoot).Parent.FullName $ChocoApp = (Get-CIPPAzDataTableEntity @Table -filter $Filter).JSON | ConvertFrom-Json $intuneBody = $ChocoApp.IntuneBody -$tenants = if ($chocoapp.Tenant -eq 'AllTenants') { +$tenants = if ($chocoapp.Tenant -eq 'AllTenants') { (Get-tenants).defaultDomainName } else { $chocoapp.Tenant -} +} if ($chocoApp.type -eq 'MSPApp') { [xml]$Intunexml = Get-Content "AddMSPApp\$($ChocoApp.MSPAppName).app.xml" $intunewinFilesize = (Get-Item "AddMSPApp\$($ChocoApp.MSPAppName).intunewin") @@ -25,7 +25,7 @@ $ContentBody = ConvertTo-Json @{ name = $intunexml.ApplicationInfo.FileName size = [int64]$intunexml.ApplicationInfo.UnencryptedContentSize sizeEncrypted = [int64]($intunewinFilesize).length -} +} $ClearRow = Get-CIPPAzDataTableEntity @Table -Filter $Filter $RemoveCacheFile = if ($chocoapp.Tenant -ne 'AllTenants') { Remove-AzDataTableEntity @Table -Entity $clearRow @@ -54,11 +54,11 @@ foreach ($tenant in $tenants) { Try { $ApplicationList = (New-graphGetRequest -Uri $baseuri -tenantid $Tenant) | Where-Object { $_.DisplayName -eq $ChocoApp.ApplicationName } - if ($ApplicationList.displayname.count -ge 1) { + if ($ApplicationList.displayname.count -ge 1) { Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "$($ChocoApp.ApplicationName) exists. Skipping this application" -Sev 'Info' continue } - if ($chocoApp.type -eq 'WinGet') { + if ($chocoApp.type -eq 'WinGet') { Write-Host 'Winget!' Write-Host ($intuneBody | ConvertTo-Json -Compress) $NewApp = New-GraphPostRequest -Uri $baseuri -Body ($intuneBody | ConvertTo-Json -Compress) -Type POST -tenantid $tenant @@ -79,8 +79,8 @@ foreach ($tenant in $tenants) { $AzFileUri = New-graphGetRequest -Uri "$($BaseURI)/$($NewApp.id)/microsoft.graph.win32lobapp/contentVersions/1/files/$($ContentReq.id)" -tenantid $tenant if ($AZfileuri.uploadState -like '*fail*') { break } Start-Sleep -Milliseconds 300 - } while ($AzFileUri.AzureStorageUri -eq $null) - + } while ($AzFileUri.AzureStorageUri -eq $null) + $chunkSizeInBytes = 4mb [byte[]]$bytes = [System.IO.File]::ReadAllBytes($($intunewinFilesize.fullname)) $chunks = [Math]::Ceiling($bytes.Length / $chunkSizeInBytes) @@ -89,15 +89,15 @@ foreach ($tenant in $tenants) { $Upload = Invoke-RestMethod -Uri "$($AzFileUri.azureStorageUri)&comp=block&blockid=$id" -Method Put -Headers @{'x-ms-blob-type' = 'BlockBlob' } -InFile $inFile -ContentType 'application/octet-stream' $ConfirmUpload = Invoke-RestMethod -Uri "$($AzFileUri.azureStorageUri)&comp=blocklist" -Method Put -Body "$id" $CommitReq = New-graphPostRequest -Uri "$($BaseURI)/$($NewApp.id)/microsoft.graph.win32lobapp/contentVersions/1/files/$($ContentReq.id)/commit" -Body $EncBody -Type POST -tenantid $tenant - + do { $CommitStateReq = New-graphGetRequest -Uri "$($BaseURI)/$($NewApp.id)/microsoft.graph.win32lobapp/contentVersions/1/files/$($ContentReq.id)" -tenantid $tenant if ($CommitStateReq.uploadState -like '*fail*') { Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "$($ChocoApp.ApplicationName) Commit failed. Please check if app uploaded succesful" -Sev 'Warning' - break + break } Start-Sleep -Milliseconds 300 - } while ($CommitStateReq.uploadState -eq 'commitFilePending') + } while ($CommitStateReq.uploadState -eq 'commitFilePending') $CommitFinalizeReq = New-graphPostRequest -Uri "$($BaseURI)/$($NewApp.id)" -tenantid $tenant -Body '{"@odata.type":"#microsoft.graph.win32lobapp","committedContentVersion":"1"}' -type PATCH Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "Added Application $($chocoApp.ApplicationName)" -Sev 'Info' if ($AssignTo -ne 'On') { @@ -108,7 +108,7 @@ foreach ($tenant in $tenants) { Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message 'Successfully added Application' -Sev 'Info' } catch { "Failed to add Application for $($Tenant): $($_.Exception.Message)" - Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "Failed adding Application $($ChocoApp.ApplicationName). Error: $($_.Exception.Message)" -Sev 'Error' + Write-LogMessage -api 'AppUpload' -tenant $($Tenant) -message "Failed adding Application $($ChocoApp.ApplicationName). Error: $($_.Exception.Message)" -LogData (Get-CippException -Exception $_) -Sev 'Error' continue } diff --git a/BestPracticeAnalyser_All/run.ps1 b/BestPracticeAnalyser_All/run.ps1 index ddd92560ccda..6e90102a161a 100644 --- a/BestPracticeAnalyser_All/run.ps1 +++ b/BestPracticeAnalyser_All/run.ps1 @@ -107,7 +107,7 @@ $AddRow = foreach ($Template in $templates) { try { Add-CIPPAzDataTableEntity @Table -Entity $Result -Force } catch { - Write-LogMessage -API 'BPA' -tenant $tenant -message "Error getting saving data for $($template.Name) - $($TenantName.customerId). Error: $($_.Exception.Message)" -sev Error + Write-LogMessage -API 'BPA' -tenant $tenant -message "Error getting saving data for $($template.Name) - $($TenantName.customerId). Error: $($_.Exception.Message)" -LogData (Get-CippException -Exception $_) -sev Error } } diff --git a/DomainAnalyser_All/run.ps1 b/DomainAnalyser_All/run.ps1 index 9ab5f2d1f85a..981afa4b7bbc 100644 --- a/DomainAnalyser_All/run.ps1 +++ b/DomainAnalyser_All/run.ps1 @@ -117,8 +117,8 @@ try { $ScoreExplanation.Add('No SPF Record Found') | Out-Null } } catch { - $Message = 'SPF Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error + $Message = 'SPF Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error throw $Message } @@ -180,8 +180,8 @@ try { } } } catch { - $Message = 'DMARC Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error + $Message = 'DMARC Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error throw $Message } @@ -198,8 +198,8 @@ try { $ScoreExplanation.Add('DNSSEC Not Configured or Enabled') | Out-Null } } catch { - $Message = 'DNSSEC Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error + $Message = 'DNSSEC Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error throw $Message } @@ -227,8 +227,8 @@ try { $ScoreExplanation.Add('DKIM Not Configured') | Out-Null } } catch { - $Message = 'DKIM Exception: {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -sev Error + $Message = 'DKIM Exception' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error throw $Message } # Final Score diff --git a/DomainAnalyser_GetTenantDomains/run.ps1 b/DomainAnalyser_GetTenantDomains/run.ps1 index 41cffd655051..6c4b8b0d8110 100644 --- a/DomainAnalyser_GetTenantDomains/run.ps1 +++ b/DomainAnalyser_GetTenantDomains/run.ps1 @@ -27,7 +27,7 @@ $TenantDomains = $Tenants | ForEach-Object -Parallel { } } } catch { - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.defaultDomainName -message "DNS Analyser GraphGetRequest Exception: $($_.Exception.Message)" -sev Error + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.defaultDomainName -message 'DNS Analyser GraphGetRequest' -LogData (Get-CippException -Exception $_) -sev Error } } | Sort-Object -Unique -Property Domain @@ -91,6 +91,6 @@ if ($TenantCount -gt 0) { # Batch insert all tenant domains try { Add-CIPPAzDataTableEntity @DomainTable -Entity $TenantDomainObjects -Force - } catch { Write-LogMessage -API 'DomainAnalyser' -message "Domain Analyser GetTenantDomains Error $($_.Exception.Message)" -sev info } - } catch { Write-LogMessage -API 'DomainAnalyser' -message "GetTenantDomains loop exception: $($_.Exception.Message) line $($_.InvocationInfo.ScriptLineNumber)" -sev 'Error' } + } catch { Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser GetTenantDomains error' -sev info -LogData (Get-CippException -Exception $_) } + } catch { Write-LogMessage -API 'DomainAnalyser' -message 'GetTenantDomains loop error' -sev 'Error' -LogData (Get-CippException -Exception $_) } } diff --git a/DomainAnalyser_Orchestration/run.ps1 b/DomainAnalyser_Orchestration/run.ps1 index 34848e03284d..e8d51585ffa7 100644 --- a/DomainAnalyser_Orchestration/run.ps1 +++ b/DomainAnalyser_Orchestration/run.ps1 @@ -33,7 +33,7 @@ try { Write-Host "Orchestrator exception UpdateDomains $($_.Exception.Message)" } } catch { - Write-LogMessage -API 'DomainAnalyser' -message "Domain Analyser Orchestrator Error $($_.Exception.Message)" -sev info + Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser Orchestrator Error' -sev info -LogData (Get-CippException -Exception $_) #Write-Host $_.Exception | ConvertTo-Json } finally { Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser has Finished' -sev Info diff --git a/ExecSchedulerBillingRun/run.ps1 b/ExecSchedulerBillingRun/run.ps1 index ff93986817b2..3ea7e6621fac 100644 --- a/ExecSchedulerBillingRun/run.ps1 +++ b/ExecSchedulerBillingRun/run.ps1 @@ -3,20 +3,19 @@ param($QueueItem) # Get the current universal time in the default string format. try { - Write-LogMessage -API "Scheduler_Billing" -tenant "none" -message "Starting billing processing." -sev Info + Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message 'Starting billing processing.' -sev Info $Table = Get-CIPPTable -TableName Extensionsconfig $Configuration = (Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -Depth 10 foreach ($ConfigItem in $Configuration.psobject.properties.name) { switch ($ConfigItem) { - "Gradient" { + 'Gradient' { If ($Configuration.Gradient.enabled -and $Configuration.Gradient.BillingEnabled) { New-GradientServiceSyncRun } } } } -} -catch { - Write-LogMessage -API "Scheduler_Billing" -tenant "none" -message "Could not start billing processing $($_.Exception.Message)" -sev Error +} catch { + Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message 'Could not start billing processing' -sev Error -LogData (Get-CippException -Exception $_) } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 index a4c66a07cb7b..b29972bcce3a 100644 --- a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 @@ -1,18 +1,18 @@ function Add-CIPPGroupMember( [string]$ExecutingUser, - [string]$GroupType, + [string]$GroupType, [string]$GroupId, - [string]$Member, + [string]$Member, [string]$TenantFilter, [string]$APIName = 'Add Group Member' ) { try { if ($member -like '*#EXT#*') { $member = [System.Web.HttpUtility]::UrlEncode($member) } - $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id + $MemberIDs = 'https://graph.microsoft.com/v1.0/directoryObjects/' + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($member)" -tenantid $TenantFilter).id $addmemberbody = "{ `"members@odata.bind`": $(ConvertTo-Json @($MemberIDs)) }" if ($GroupType -eq 'Distribution list' -or $GroupType -eq 'Mail-Enabled Security') { $Params = @{ Identity = $GroupId; Member = $member; BypassSecurityGroupManagerCheck = $true } - New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true + New-ExoRequest -tenantid $TenantFilter -cmdlet 'Add-DistributionGroupMember' -cmdParams $params -UseSystemMailbox $true } else { New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)" -tenantid $TenantFilter -type patch -body $addmemberbody -Verbose } @@ -21,9 +21,9 @@ function Add-CIPPGroupMember( return $message return } catch { - $message = "Failed to add user $($Member) to $($GroupId): $($_.Exception.Message)" - Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $message -Sev 'error' - return $message + $message = "Failed to add user $($Member) to $($GroupId)" + Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $message -Sev 'error' -LogData (Get-CippException -Exception $_) + return $message } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 index 319b43a00995..a3963bd0bc94 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1 @@ -12,7 +12,7 @@ Function Invoke-ListLogs { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' if ($request.Query.Filter -eq 'True') { - $LogLevel = if ($Request.query.Severity) { ($Request.query.Severity).split(',') } else { 'Info', 'Warn', 'Error', 'Critical', 'Alert' } + $LogLevel = if ($Request.query.Severity) { ($Request.query.Severity).split(',') } else { 'Info', 'Warn', 'Error', 'Critical', 'Alert' } $PartitionKey = $Request.query.DateFilter $username = $Request.Query.User } else { @@ -25,7 +25,7 @@ Function Invoke-ListLogs { $ReturnedLog = if ($Request.Query.ListLogs) { Get-CIPPAzDataTableEntity @Table -Property PartitionKey | Sort-Object -Unique PartitionKey | Select-Object PartitionKey | ForEach-Object { - @{ + @{ value = $_.PartitionKey label = $_.PartitionKey } @@ -34,13 +34,17 @@ Function Invoke-ListLogs { $Filter = "PartitionKey eq '{0}'" -f $PartitionKey $Rows = Get-CIPPAzDataTableEntity @Table -Filter $Filter | Where-Object { $_.Severity -In $LogLevel -and $_.user -like $username } foreach ($Row in $Rows) { - @{ + $LogData = if ($Row.LogData -and (Test-Json -Json $Row.LogData)) { + $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 { diff --git a/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 b/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 index 0d3092fc54c3..9b1885d5c465 100644 --- a/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPAuthentication.ps1 @@ -30,7 +30,7 @@ function Get-CIPPAuthentication { return $true } catch { - Write-LogMessage -message "Could not retrieve keys from Keyvault: $($_.Exception.Message)" -Sev 'CRITICAL' -API 'CIPP Authentication' + Write-LogMessage -message 'Could not retrieve keys from Keyvault' -Sev 'CRITICAL' -API 'CIPP Authentication' -LogData (Get-CippException -Exception $_) return $false } } diff --git a/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 index 7d886ea2912f..a80a5d3b002e 100644 --- a/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 @@ -4,18 +4,17 @@ function Get-CIPPBitlockerKey { param ( $device, $TenantFilter, - $APIName = "Get Bitlocker key", + $APIName = 'Get Bitlocker key', $ExecutingUser ) try { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/informationProtection/bitlocker/recoveryKeys?`$filter=deviceId eq '$($device)'" -tenantid $TenantFilter | ForEach-Object { + $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/informationProtection/bitlocker/recoveryKeys?`$filter=deviceId eq '$($device)'" -tenantid $TenantFilter | ForEach-Object { (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/informationProtection/bitlocker/recoveryKeys/$($_.id)?`$select=key" -tenantid $TenantFilter).key } return $GraphRequest - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev "Error" -tenant $TenantFilter + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" } } diff --git a/profile.ps1 b/profile.ps1 index f30159db12cb..ec6aa4e19b86 100644 --- a/profile.ps1 +++ b/profile.ps1 @@ -15,9 +15,10 @@ # Import modules @('CippCore', 'CippExtensions', 'Az.KeyVault', 'Az.Accounts') | ForEach-Object { try { + $Module = $_ Import-Module -Name $_ -ErrorAction Stop } catch { - Write-LogMessage -message "Failed to import module $($_): $_.Exception.Message" -Sev 'debug' + Write-LogMessage -message "Failed to import module - $Module" -LogData (Get-CippException -Exception $_) -Sev 'debug' $_.Exception.Message } } @@ -32,7 +33,7 @@ try { $Auth = Get-CIPPAuthentication } } catch { - Write-LogMessage -message "Could not retrieve keys from Keyvault: $($_.Exception.Message)" -Sev 'debug' + Write-LogMessage -message 'Could not retrieve keys from Keyvault' -LogData (Get-CippException -Exception $_) -Sev 'debug' } # Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. From 0f60c734ee66e5fa4055cc41c45dbe828e5c690f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 16:17:22 -0400 Subject: [PATCH 30/57] Webhook improvements --- .../Invoke-CIPPPartnerWebhookProcessing.ps1 | 71 ++++++++++++++++--- .../Public/Invoke-CIPPWebhookProcessing.ps1 | 14 ++-- Modules/CippEntrypoints/CippEntrypoints.psm1 | 7 +- 3 files changed, 73 insertions(+), 19 deletions(-) diff --git a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 index e7d6a279dc2f..36c6cd85f385 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 @@ -4,17 +4,70 @@ function Invoke-CippPartnerWebhookProcessing { $Data ) - Switch ($Data.EventName) { - 'test-created' { - Write-LogMessage -API 'Webhooks' -message 'Partner Center webhook test received' -Sev 'Info' + try { + if ($Data.AuditUri) { + $AuditLog = New-GraphGetRequest -uri $Data.AuditUri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' } - default { - if ($Data.AuditUri) { - $AuditLog = New-GraphGetRequest -uri $Data.AuditUri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' - Write-Logessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log received" -LogData $AuditLog -Sev 'Info' - } else { - Write-LogMessage -API 'Webhooks' -message 'Partner Center webhook received (no audit)' -LogData $Data -Sev 'Info' + + Switch ($Data.EventName) { + 'test-created' { + Write-LogMessage -API 'Webhooks' -message 'Partner Center webhook test received' -Sev 'Info' + } + default { + if ($Data.EventName -eq 'granular-admin-relationship-approved') { + if ($AuditLog.resourceNewValue) { + $AuditObj = $AuditLog.resourceNewValue | ConvertFrom-Json + $Id = $AuditObj.Id + $OnboardingSteps = [PSCustomObject]@{ + 'Step1' = @{ + 'Status' = 'pending' + 'Title' = 'Step 1: GDAP Invite' + 'Message' = 'Waiting for onboarding job to start' + } + 'Step2' = @{ + 'Status' = 'pending' + 'Title' = 'Step 2: GDAP Role Test' + 'Message' = 'Waiting for Step 1' + } + 'Step3' = @{ + 'Status' = 'pending' + 'Title' = 'Step 3: GDAP Group Mapping' + 'Message' = 'Waiting for Step 2' + } + 'Step4' = @{ + 'Status' = 'pending' + 'Title' = 'Step 4: CPV Refresh' + 'Message' = 'Waiting for Step 3' + } + 'Step5' = @{ + 'Status' = 'pending' + 'Title' = 'Step 5: Graph API Test' + 'Message' = 'Waiting for Step 4' + } + } + $TenantOnboarding = [PSCustomObject]@{ + PartitionKey = 'Onboarding' + RowKey = [string]$Id + CustomerId = '' + Status = 'queued' + OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) + Relationship = [string](ConvertTo-Json -InputObject $AuditObj -Compress) + Logs = '' + Exception = '' + } + Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + Push-ExecOnboardTenantQueue -Item @{ Id = $Id } + } + + if ($AuditLog) { + Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log webhook received" -LogData $AuditLog -Sev 'Alert' + } else { + Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) webhook received" -LogData $Data -Sev 'Alert' + } + } } } + } catch { + Write-LogMessage -API 'Webhooks' -message 'Error processing Partner Center webhook' -LogData (Get-CippException -Exception $_) -Sev 'Error' } } diff --git a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 index db62dae8b160..86e7e8a41c3f 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPWebhookProcessing.ps1 @@ -82,7 +82,7 @@ function Invoke-CippWebhookProcessing { { 'UserLoggedIn' -eq $data.operation -and $hosting -eq $true -and !$TrustedIps } { $data.operation = 'HostedIP' } { 'UserLoggedIn' -eq $data.operation -and $Country -notin $AllowedLocations -and $data.ResultStatus -eq 'Success' -and $TableObj.ResultStatusDetail -eq 'Success' } { Write-Host "$($country) is not in $($AllowedLocations)" - $data.operation = 'UserLoggedInFromUnknownLocation' + $data.operation = 'UserLoggedInFromUnknownLocation' } { 'UserloggedIn' -eq $data.operation -and $data.UserType -eq 2 -and $data.ResultStatus -eq 'Success' -and $TableObj.ResultStatusDetail -eq 'Success' } { $data.operation = 'AdminLoggedIn' } default { break } @@ -130,7 +130,7 @@ function Invoke-CippWebhookProcessing { $key = $parts[0] $operator = $parts[1] $value = $parts[2] - if (!$value) { + if (!$value) { Write-Host 'blank value, skip' continue } @@ -165,9 +165,9 @@ function Invoke-CippWebhookProcessing { $RuleDisabled = 0 New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'get-inboxrule' -cmdParams @{Mailbox = $username } | ForEach-Object { $null = New-ExoRequest -anchor $username -tenantid $TenantFilter -cmdlet 'Disable-InboxRule' -cmdParams @{Confirm = $false; Identity = $_.Identity } - "Disabled Inbox Rule $($_.Identity) for $username" + "Disabled Inbox Rule $($_.Identity) for $username" $RuleDisabled ++ - } + } if ($RuleDisabled) { "Disabled $RuleDisabled Inbox Rules for $username" } else { @@ -211,7 +211,7 @@ function Invoke-CippWebhookProcessing { } } Write-Host 'Going to create the content' - foreach ($action in $dos) { + foreach ($action in $dos) { switch ($action.execute) { 'generatemail' { Write-Host 'Going to create the email' @@ -220,9 +220,9 @@ function Invoke-CippWebhookProcessing { Send-CIPPAlert -Type 'email' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter Write-Host 'email should be sent' - } + } 'generatePSA' { - $GenerateEmail = New-CIPPAlertTemplate -format 'html'-data $Data -LocationInfo $Location -ActionResults $ActionResults + $GenerateEmail = New-CIPPAlertTemplate -format 'html' -data $Data -LocationInfo $Location -ActionResults $ActionResults Send-CIPPAlert -Type 'psa' -Title $GenerateEmail.title -HTMLContent $GenerateEmail.htmlcontent -TenantFilter $TenantFilter } 'generateWebhook' { diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index f0bf7fcf2d1b..02651d9a2a3f 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -50,7 +50,7 @@ function Receive-CippQueueTrigger { function Receive-CippOrchestrationTrigger { param($Context) - + Write-Host 'Orchestrator started' try { if (Test-Json -Json $Context.Input) { $OrchestratorInput = $Context.Input | ConvertFrom-Json @@ -77,9 +77,10 @@ function Receive-CippOrchestrationTrigger { } if (($Batch | Measure-Object).Count -gt 0) { - foreach ($Item in $Batch) { - $null = Invoke-DurableActivity -FunctionName 'CIPPActivityFunction' -Input $Item -NoWait -RetryOptions $RetryOptions -ErrorAction Stop + $Tasks = foreach ($Item in $Batch) { + Invoke-DurableActivity -FunctionName 'CIPPActivityFunction' -Input $Item -NoWait -RetryOptions $RetryOptions -ErrorAction Stop } + $null = Wait-ActivityFunction -Task $Tasks } if ($Context.IsReplaying -ne $true -and $OrchestratorInput.SkipLog -ne $true) { From 47ad9b151c45c45c3522004ed4e8525d4b2e9106 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 16:45:11 -0400 Subject: [PATCH 31/57] Webhook tweak --- .../Public/Invoke-CIPPPartnerWebhookProcessing.ps1 | 14 ++++++++------ Scheduler_GetWebhooks/run.ps1 | 7 +++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 index 36c6cd85f385..5a16c69d530d 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPPartnerWebhookProcessing.ps1 @@ -17,6 +17,7 @@ function Invoke-CippPartnerWebhookProcessing { if ($Data.EventName -eq 'granular-admin-relationship-approved') { if ($AuditLog.resourceNewValue) { $AuditObj = $AuditLog.resourceNewValue | ConvertFrom-Json + Write-LogMessage -API 'Webhooks' -message "Partner Webhook: GDAP Relationship for $($AuditObj.customer.organizationDisplayName) was approved, starting onboarding" -LogData $AuditObj -Sev 'Alert' $Id = $AuditObj.Id $OnboardingSteps = [PSCustomObject]@{ 'Step1' = @{ @@ -51,18 +52,19 @@ function Invoke-CippPartnerWebhookProcessing { CustomerId = '' Status = 'queued' OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) - Relationship = [string](ConvertTo-Json -InputObject $AuditObj -Compress) + Relationship = '' Logs = '' Exception = '' } + $OnboardTable = Get-CIPPTable -TableName 'TenantOnboarding' Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop Push-ExecOnboardTenantQueue -Item @{ Id = $Id } - } - - if ($AuditLog) { - Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log webhook received" -LogData $AuditLog -Sev 'Alert' } else { - Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) webhook received" -LogData $Data -Sev 'Alert' + if ($AuditLog) { + Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) audit log webhook received" -LogData $AuditObj -Sev 'Alert' + } else { + Write-LogMessage -API 'Webhooks' -message "Partner Center $($Data.EventName) webhook received" -LogData $Data -Sev 'Alert' + } } } } diff --git a/Scheduler_GetWebhooks/run.ps1 b/Scheduler_GetWebhooks/run.ps1 index b262f320738b..b55b57d1f05c 100644 --- a/Scheduler_GetWebhooks/run.ps1 +++ b/Scheduler_GetWebhooks/run.ps1 @@ -3,11 +3,12 @@ param($Timer) try { $webhookTable = Get-CIPPTable -tablename webhookTable - $Webhooks = Get-CIPPAzDataTableEntity @webhookTable + $Webhooks = Get-CIPPAzDataTableEntity @webhookTable -Property RowKey if (($Webhooks | Measure-Object).Count -eq 0) { Write-Host 'No webhook subscriptions found. Exiting.' return } + Write-Host 'Processing webhooks' $InputObject = [PSCustomObject]@{ OrchestratorName = 'WebhookOrchestrator' @@ -16,8 +17,10 @@ try { } SkipLog = $true } + Write-Host ($InputObject | ConvertTo-Json -Depth 5) $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) Write-Host "Started orchestration with ID = '$InstanceId'" } catch { - Write-LogMessage -API 'Webhooks' -message "Error processing webhooks - $($_.Exception.Message)" -sev Error + Write-LogMessage -API 'Webhooks' -message 'Error processing webhooks' -sev Error -LogData (Get-CippException -Exception $_) + Write-Host ( 'Webhook error {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message) } From 32ef9bff1187bd5e73c3841faa3ed487eb0bebc2 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 16:58:52 -0400 Subject: [PATCH 32/57] Set logs to debug --- .../CIPP/Settings/Invoke-ExecDnsConfig.ps1 | 2 +- .../CIPP/Settings/Invoke-ExecExcludeTenant.ps1 | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 index 067da939c2ca..41768a704af5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecDnsConfig.ps1 @@ -88,7 +88,7 @@ Function Invoke-ExecDnsConfig { } 'GetConfig' { $body = [pscustomobject]$Config - Write-LogMessage -API $APINAME -tenant 'Global' -user $request.headers.'x-ms-client-principal' -message 'Retrieved DNS configuration' -Sev 'Info' + Write-LogMessage -API $APINAME -tenant 'Global' -user $request.headers.'x-ms-client-principal' -message 'Retrieved DNS configuration' -Sev 'Debug' } 'RemoveDomain' { $Filter = "RowKey eq '{0}'" -f $Request.Query.Domain diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 index ca63f9f20afa..ca3e85feed9a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecExcludeTenant.ps1 @@ -17,13 +17,13 @@ Function Invoke-ExecExcludeTenant { $TenantsTable = Get-CippTable -tablename Tenants if ($Request.Query.List) { - $ExcludedFilter = "PartitionKey eq 'Tenants' and Excluded eq true" - $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $ExcludedFilter - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Info' + $ExcludedFilter = "PartitionKey eq 'Tenants' and Excluded eq true" + $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter $ExcludedFilter + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Debug' $body = @($ExcludedTenants) } elseif ($Request.query.ListAll) { - $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -filter "PartitionKey eq 'Tenants'" - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Info' + $ExcludedTenants = Get-CIPPAzDataTableEntity @TenantsTable -filter "PartitionKey eq 'Tenants'" + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message 'got excluded tenants list' -Sev 'Debug' $body = @($ExcludedTenants) } try { @@ -31,7 +31,7 @@ Function Invoke-ExecExcludeTenant { $name = $Request.Query.TenantFilter if ($Request.Query.AddExclusion) { $Tenants = Get-Tenants -IncludeAll | Where-Object { $Request.body.value -contains $_.customerId } - + $Excluded = foreach ($Tenant in $Tenants) { $Tenant.Excluded = $true $Tenant.ExcludeUser = $username @@ -41,17 +41,17 @@ Function Invoke-ExecExcludeTenant { Write-Host ($Excluded | ConvertTo-Json) Update-AzDataTableEntity @TenantsTable -Entity ([pscustomobject]$Excluded) #Remove-CIPPCache - Write-LogMessage -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Added exclusion for customer(s): $($Excluded.defaultDomainName -join ',')" -Sev 'Info' + Write-LogMessage -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Added exclusion for customer(s): $($Excluded.defaultDomainName -join ',')" -Sev 'Info' $body = [pscustomobject]@{'Results' = "Success. Added exclusions for customer(s): $($Excluded.defaultDomainName -join ',')" } } if ($Request.Query.RemoveExclusion) { $Filter = "PartitionKey eq 'Tenants' and defaultDomainName eq '{0}'" -f $name - $Tenant = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter + $Tenant = Get-CIPPAzDataTableEntity @TenantsTable -Filter $Filter $Tenant.Excluded = $false $Tenant.ExcludeUser = '' $Tenant.ExcludeDate = '' - Update-AzDataTableEntity @TenantsTable -Entity $Tenant + Update-AzDataTableEntity @TenantsTable -Entity $Tenant #Remove-CIPPCache Write-LogMessage -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Removed exclusion for customer $($name)" -Sev 'Info' $body = [pscustomobject]@{'Results' = "Success. We've removed $name from the excluded tenants." } From ecceda84e2ab6b8e24641c60edc2a782676c0ab5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 17:57:49 -0400 Subject: [PATCH 33/57] Onboarding updates --- .../Push-ExecOnboardTenantQueue.ps1 | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 index cc23c195820c..221068716478 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 @@ -215,39 +215,40 @@ Function Push-ExecOnboardTenantQueue { $OnboardingSteps.Step3.Status = 'failed' $OnboardingSteps.Step3.Message = 'Failed to map security groups, no pending invite available' } + } - do { - $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id/accessAssignments" - Start-Sleep -Seconds 15 - } while ($AccessAssignments.status -contains 'pending' -and (Get-Date) -lt $Start.AddMinutes(8)) + do { + $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id/accessAssignments" + Start-Sleep -Seconds 15 + } while ($AccessAssignments.status -contains 'pending' -and (Get-Date) -lt $Start.AddMinutes(8)) - if ($AccessAssignments.status -notcontains 'pending') { - $OnboardingSteps.Step3.Message = 'Group check: Access assignments are mapped and active' - $OnboardingSteps.Step3.Status = 'succeeded' - if ($Item.AddMissingGroups -eq $true) { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking for missing groups for SAM user' }) - $SamUserId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me?`$select=id").id - $CurrentMemberships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me/transitiveMemberOf?`$select=id,displayName" - foreach ($Role in $Item.Roles) { - if ($CurrentMemberships.id -notcontains $Role.GroupId) { - $PostBody = @{ - '@odata.id' = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $SamUserId - } | ConvertTo-Json -Compress - try { - New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($Role.GroupId)/members/`$ref" -body $PostBody -AsApp $true -NoAuthCheck $true - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Added SAM user to $($Role.GroupName)" }) - } catch { - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Failed to add SAM user to $($Role.GroupName) - $($_.Exception.Message)" }) - } + if ($AccessAssignments.status -notcontains 'pending') { + $OnboardingSteps.Step3.Message = 'Group check: Access assignments are mapped and active' + $OnboardingSteps.Step3.Status = 'succeeded' + if ($Item.AddMissingGroups -eq $true) { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking for missing groups for SAM user' }) + $SamUserId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me?`$select=id").id + $CurrentMemberships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me/transitiveMemberOf?`$select=id,displayName" + foreach ($Role in $Item.Roles) { + if ($CurrentMemberships.id -notcontains $Role.GroupId) { + $PostBody = @{ + '@odata.id' = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $SamUserId + } | ConvertTo-Json -Compress + try { + New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($Role.GroupId)/members/`$ref" -body $PostBody -AsApp $true -NoAuthCheck $true + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Added SAM user to $($Role.GroupName)" }) + } catch { + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Failed to add SAM user to $($Role.GroupName) - $($_.Exception.Message)" }) } } - $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'SAM user group check completed' }) } - } else { - $OnboardingSteps.Step3.Message = 'Group check: Access assignments are still pending, try again later' - $OnboardingSteps.Step3.Status = 'failed' - $TenantOnboarding.Status = 'failed' + $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'SAM user group check completed' }) } + } else { + $OnboardingSteps.Step3.Message = 'Group check: Access assignments are still pending, try again later' + $OnboardingSteps.Step3.Status = 'failed' + $TenantOnboarding.Status = 'failed' + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding failed at group mapping step for $($Relationship.customer.displayName)" -Sev 'Error' } $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) @@ -298,6 +299,7 @@ Function Push-ExecOnboardTenantQueue { $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding failed at CPV step for $($Relationship.customer.displayName)" -Sev 'Error' return } $Refreshing = $true @@ -357,6 +359,7 @@ Function Push-ExecOnboardTenantQueue { } catch { $UserCount = 0 $ApiError = $_.Exception.Message + $ApiException = $_ } if ($UserCount -gt 0) { @@ -368,6 +371,7 @@ Function Push-ExecOnboardTenantQueue { $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding succeeded for $($Relationship.customer.displayName)" -Sev 'Info' } else { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'API Test failed: {0}' -f $ApiError }) $OnboardingSteps.Step5.Status = 'failed' @@ -376,6 +380,7 @@ Function Push-ExecOnboardTenantQueue { $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding API test failed for $($Relationship.customer.displayName)" -Sev 'Error' -LogData (Get-CippException -Exception $ApiException) } } } catch { @@ -385,5 +390,6 @@ Function Push-ExecOnboardTenantQueue { $TenantOnboarding.OnboardingSteps = [string](ConvertTo-Json -InputObject $OnboardingSteps -Compress) $TenantOnboarding.Logs = [string](ConvertTo-Json -InputObject @($Logs) -Compress) Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop + Write-LogMessage -API 'Onboarding' -message "Tenant onboarding failed for $Id" -Sev 'Error' -LogData (Get-CippException -Exception $_) } } From 0022382be9ee525a947fc31dd1b9dd6e2323bff9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 19:13:29 -0400 Subject: [PATCH 34/57] fix explorer preset bug --- .../Tenant/Tools/Invoke-ExecGraphExplorerPreset.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 fa2211ecc392..f04c365c5ccf 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 @@ -33,7 +33,8 @@ Function Invoke-ExecGraphExplorerPreset { } $params = $Request.Body.preset | Select-Object endpoint, '$filter', '$select', '$count', '$expand', '$search', NoPagination, '$top', IsShared - if ($params.'$select' -and -not $params.'$select' -is [string]) { + + if ($params.'$select'.value) { $params.'$select' = ($params.'$select').value -join ',' } From 619f332658c2b00146845f35499ad4b7cb510d14 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 11 Apr 2024 19:15:08 -0400 Subject: [PATCH 35/57] allow for editing schedule --- .../CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 04c5d358d599..8cd5b159d458 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -18,8 +18,7 @@ function Add-CIPPScheduledTask { $ht[$p.Key] = $p.Value } $Parameters[$Key] = [PSCustomObject]$ht - } - else { + } else { $Parameters[$Key] = $Param } } @@ -30,10 +29,15 @@ function Add-CIPPScheduledTask { } $AdditionalProperties = ([PSCustomObject]$AdditionalProperties | ConvertTo-Json -Compress) if ($Parameters -eq 'null') { $Parameters = '' } + if (!$Task.RowKey) { + $RowKey = (New-Guid).Guid + } else { + $RowKey = $Task.RowKey + } $entity = @{ PartitionKey = [string]'ScheduledTask' TaskState = [string]'Planned' - RowKey = [string]"$(New-Guid)" + RowKey = [string]$RowKey Tenant = [string]$task.TenantFilter Name = [string]$task.Name Command = [string]$task.Command.value @@ -46,10 +50,9 @@ function Add-CIPPScheduledTask { Results = 'Planned' } try { - Add-CIPPAzDataTableEntity @Table -Entity $entity - } - catch { + Add-CIPPAzDataTableEntity @Table -Entity $entity -Force + } catch { return "Could not add task: $($_.Exception.Message)" } - return "Successfully added task" + return 'Successfully added task' } \ No newline at end of file From 5a336f7830344e31c1a004612fa948be61ccf0a4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 12 Apr 2024 12:38:52 +0200 Subject: [PATCH 36/57] fixes universal search --- .../Push-CIPPAlertNewAppApproval.ps1 | 2 +- .../Tenant/Conditional/Invoke-ExecCACheck.ps1 | 58 +++++++++++++++++++ .../Invoke-ExecUniversalSearch.ps1 | 25 +++++++- .../GraphHelper/New-GraphPOSTRequest.ps1 | 4 +- 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCACheck.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 index 438fd62739d6..f5317b9769a5 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-CIPPAlertNewAppApproval.ps1 @@ -6,7 +6,7 @@ function Push-CIPPAlertNewAppApproval { [pscustomobject]$Item ) try { - $Approvals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $item.tenant + $Approvals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/identityGovernance/appConsent/appConsentRequests' -tenantid $item.tenant | Where-Object -Property requestStatus -EQ 'inProgress' if ($Approvals.count -gt 1) { Write-AlertMessage -tenant $($Item.tenant) -message "There is are $($Approvals.count) App Approvals waiting." } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCACheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCACheck.ps1 new file mode 100644 index 000000000000..137c55a9c28b --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ExecCACheck.ps1 @@ -0,0 +1,58 @@ +using namespace System.Net + +Function Invoke-ExecCaCheck { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $Tenant = $request.body.tenantFilter + $UserID = $request.body.userId.value + if ($Request.body.IncludeApplications.value) { + $IncludeApplications = $Request.body.IncludeApplications.value + } else { + $IncludeApplications = '67ad5377-2d78-4ac2-a867-6300cda00e85' + } + $results = try { + $CAContext = @{ + '@odata.type' = '#microsoft.graph.whatIfApplicationContext' + 'includeApplications' = @($IncludeApplications) + } + $ConditionalAccessWhatIfDefinition = @{ + 'conditionalAccessWhatIfSubject' = @{ + '@odata.type' = '#microsoft.graph.userSubject' + 'userId' = "$userId" + } + 'conditionalAccessContext' = $CAContext + 'conditionalAccessWhatIfConditions' = @{} + } + $whatIfConditions = $ConditionalAccessWhatIfDefinition.conditionalAccessWhatIfConditions + if ($Request.body.UserRiskLevel) { $whatIfConditions.userRiskLevel = $Request.body.UserRiskLevel.value } + if ($Request.body.SignInRiskLevel) { $whatIfConditions.signInRiskLevel = $Request.body.SignInRiskLevel.value } + if ($Request.body.ClientAppType) { $whatIfConditions.clientAppType = $Request.body.ClientAppType.value } + if ($Request.body.DevicePlatform) { $whatIfConditions.devicePlatform = $Request.body.DevicePlatform.value } + if ($Request.body.Country) { $whatIfConditions.country = $Request.body.Country.value } + if ($Request.body.IpAddress) { $whatIfConditions.ipAddress = $Request.body.IpAddress.value } + + $JSONBody = $ConditionalAccessWhatIfDefinition | ConvertTo-Json -Depth 10 + Write-Host $JSONBody + $Request = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/evaluate' -tenantid $tenant -type POST -body $JsonBody -AsApp $true + $Request + } catch { + "Failed to execute check: $($_.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 + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 index 838f23ee3cd6..eda323666fa1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUniversalSearch.ps1 @@ -19,8 +19,29 @@ Function Invoke-ExecUniversalSearch { try { $tenantfilter = Get-Tenants - $payload = '{ "returnsPartialResults":true, "displayName":"getUsers", "target": { "allTenants":true }, "operationDefinition": { "values":["@sys.normalize([ConsistencyLevel: eventual GET /v1.0/users?$top=5&$search=\"userPrincipalName:' + $request.query.name + '\" OR \"displayName:' + $request.query.name + '\"])"] }, "aggregationDefinition": { "values":["@sys.append([/result],50)"] } }' - $GraphRequest = (New-GraphPOSTRequest -noauthcheck $true -type 'POST' -uri 'https://graph.microsoft.com/beta/tenantRelationships/managedTenants/managedTenantOperations' -tenantid $env:TenantID -body $payload).result.Results | ConvertFrom-Json | Where-Object { $_.'_TenantId' -in $tenantfilter.customerId } + $payload = [PSCustomObject]@{ + returnsPartialResults = $false + displayName = 'getUsers' + target = [PSCustomObject]@{ + allTenants = $true + } + operationDefinition = [PSCustomObject]@{ + values = @( + "@sys.normalize([ConsistencyLevel: eventual GET /v1.0/users?`$top=5&`$search=`"userPrincipalName:$($Request.query.name)`" OR `"displayName:$($Request.query.name)`"])" + ) + } + aggregationDefinition = [PSCustomObject]@{ + values = @( + '@sys.append([/result],50)' + ) + } + } | ConvertTo-Json -Depth 10 + $GraphRequest = (New-GraphPOSTRequest -noauthcheck $true -type 'POST' -uri 'https://graph.microsoft.com/beta/tenantRelationships/managedTenants/managedTenantOperations' -tenantid $env:TenantID -body $payload -IgnoreErrors $true) + if (!$GraphRequest.result.results) { + $GraphRequest = ($GraphRequest.error.message | ConvertFrom-Json).result.results | ConvertFrom-Json | Where-Object { $_.'_TenantId' -in $tenantfilter.customerId } + } else { + $GraphRequest.result.Results | ConvertFrom-Json -ErrorAction SilentlyContinue | Where-Object { $_.'_TenantId' -in $tenantfilter.customerId } + } $StatusCode = [HttpStatusCode]::OK } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message diff --git a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 index 9236b7559fcf..315881e1048b 100644 --- a/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/New-GraphPOSTRequest.ps1 @@ -1,5 +1,5 @@ -function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $NoAuthCheck, $skipTokenCache, $AddedHeaders, $contentType) { +function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $NoAuthCheck, $skipTokenCache, $AddedHeaders, $contentType, $IgnoreErrors) { <# .FUNCTIONALITY Internal @@ -20,7 +20,7 @@ function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp, $N $contentType = 'application/json; charset=utf-8' } try { - $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType $contentType) + $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType $contentType -SkipHttpErrorCheck:$IgnoreErrors) } catch { $Message = if ($_.ErrorDetails.Message) { Get-NormalizedError -Message $_.ErrorDetails.Message From 1101cc1664b4886e9bbb8f52550dba7b810b8d0a Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 12 Apr 2024 14:29:53 +0200 Subject: [PATCH 37/57] up version --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index 3238344b3b0d..c7ba1e87f75e 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.4.4 \ No newline at end of file +5.5.0 \ No newline at end of file From b6ea7103880fd859907b502b0dd5b7c5abffaf42 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 12 Apr 2024 11:43:14 -0400 Subject: [PATCH 38/57] Update Test-CIPPAccessPermissions.ps1 --- Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 index ac2629135250..110f2fde20e4 100644 --- a/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 +++ b/Modules/CIPPCore/Public/Test-CIPPAccessPermissions.ps1 @@ -25,7 +25,7 @@ function Test-CIPPAccessPermissions { try { Set-Location (Get-Item $PSScriptRoot).FullName $ExpectedPermissions = Get-Content '.\SAMManifest.json' | ConvertFrom-Json - Get-CIPPAuthentication + $null = Get-CIPPAuthentication $GraphToken = Get-GraphToken -returnRefresh $true -SkipCache $true if ($GraphToken) { $GraphPermissions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/myorganization/applications?`$filter=appId eq '$env:ApplicationID'" -NoAuthCheck $true From dbe1cc5b90ae412dbb4896fe26ec808c5578b852 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 12 Apr 2024 11:43:34 -0400 Subject: [PATCH 39/57] Update version_latest.txt --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index c7ba1e87f75e..d41f08f1f3c0 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.5.0 \ No newline at end of file +5.5.1 \ No newline at end of file From 009d575cb26914c1edf2db2154daaf4200258782 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 12 Apr 2024 12:23:43 -0400 Subject: [PATCH 40/57] partner webhook - fix errors on no existing sub --- .../CIPP/Core/Invoke-ExecPartnerWebhook.ps1 | 12 ++++++++++-- .../CIPPCore/Public/New-CIPPGraphSubscription.ps1 | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 index d529d55b1f0f..3a8f954588ef 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 @@ -7,8 +7,16 @@ function Invoke-ExecPartnerWebhook { $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' } 'ListSubscription' { - $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' - $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + try { + $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' + $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' + } catch { + $Results = [PSCustomObject]@{ + webhoookUrl = 'None' + lastModifiedTimestamp = 'Never' + webhookEvents = @() + } + } } 'CreateSubscription' { $BaseURL = ([System.Uri]$request.headers.'x-ms-original-url').Host diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index e744232e9e60..c17fd09ebeee 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -97,7 +97,11 @@ function New-CIPPGraphSubscription { WebhookUrl = "https://$BaseURL/API/PublicWebhooks?CIPPID=$($CIPPID)&Type=PartnerCenter" WebhookEvents = @($EventList) } - $EventCompare = Compare-Object $EventList ($MatchedWebhook.EventType | ConvertFrom-Json) + try { + $EventCompare = Compare-Object $EventList ($MatchedWebhook.EventType | ConvertFrom-Json) + } catch { + $EventCompare = $false + } try { $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' try { From 74e50fef36004890fc3848af5ad46d0273f65b17 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 12 Apr 2024 12:26:18 -0400 Subject: [PATCH 41/57] Update Invoke-ExecPartnerWebhook.ps1 --- .../HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 index 3a8f954588ef..1644f9a961dc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecPartnerWebhook.ps1 @@ -10,7 +10,8 @@ function Invoke-ExecPartnerWebhook { try { $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' $Results = New-GraphGetRequest -uri $Uri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default' - } catch { + } catch {} + if (!$Results) { $Results = [PSCustomObject]@{ webhoookUrl = 'None' lastModifiedTimestamp = 'Never' From 55f649bf2badec88fd082364d805ab0c462b4aa5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 12 Apr 2024 14:39:16 -0400 Subject: [PATCH 42/57] Update New-CIPPGraphSubscription.ps1 --- Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 index c17fd09ebeee..378df5a1cb48 100644 --- a/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 +++ b/Modules/CIPPCore/Public/New-CIPPGraphSubscription.ps1 @@ -100,14 +100,14 @@ function New-CIPPGraphSubscription { try { $EventCompare = Compare-Object $EventList ($MatchedWebhook.EventType | ConvertFrom-Json) } catch { - $EventCompare = $false + $EventCompare = $false } try { $Uri = 'https://api.partnercenter.microsoft.com/webhooks/v1/registration' try { $Existing = New-GraphGetRequest -NoAuthCheck $true -uri $Uri -tenantid $env:TenantId -scope 'https://api.partnercenter.microsoft.com/.default' - } catch {} - if ($Existing.webhookUrl -ne $MatchedWebhook.WebhookNotificationUrl -or $EventCompare) { + } catch { $Existing = $false } + if (!$Existing -or $Existing.webhookUrl -ne $MatchedWebhook.WebhookNotificationUrl -or $EventCompare) { if ($Existing.WebhookUrl) { $Action = 'Updated' $Method = 'PUT' From 44258d643dfecdba6a7bbe9be8240a847f77f5c6 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 12 Apr 2024 21:36:48 -0400 Subject: [PATCH 43/57] Fix out of office --- .../Email-Exchange/Invoke-ExecSetOoO.ps1 | 21 +++++++++++++------ .../CIPPCore/Public/Set-CIPPOutOfoffice.ps1 | 20 ++++++++---------- Modules/CIPPCore/Public/Set-CIPPSignature.ps1 | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 index 128df28ca6a9..c0a004791254 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Email-Exchange/Invoke-ExecSetOoO.ps1 @@ -21,13 +21,22 @@ Function Invoke-ExecSetOoO { } $StartTime = $Request.body.StartTime $EndTime = $Request.body.EndTime - + + $OutOfOffice = @{ + userid = $Request.body.user + InternalMessage = $InternalMessage + ExternalMessage = $ExternalMessage + TenantFilter = $TenantFilter + State = $Request.Body.AutoReplyState + APIName = $APINAME + ExecutingUser = $request.headers.'x-ms-client-principal' + StartTime = $StartTime + EndTime = $EndTime + } + Write-Host ($OutOfOffice | ConvertTo-Json -Depth 10) + $Results = try { - if ($Request.Body.AutoReplyState -ne 'Scheduled') { - Set-CIPPOutOfOffice -userid $Request.body.user -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -InternalMessage $InternalMessage -ExternalMessage $ExternalMessage -State $Request.Body.AutoReplyState - } else { - Set-CIPPOutOfOffice -userid $Request.body.user -tenantFilter $TenantFilter -APIName $APINAME -ExecutingUser $request.headers.'x-ms-client-principal' -InternalMessage $InternalMessage -ExternalMessage $ExternalMessage -StartTime $StartTime -EndTime $EndTime -State $Request.Body.AutoReplyState - } + Set-CIPPOutOfOffice @OutOfOffice } catch { "Could not add out of office message for $($username). Error: $($_.Exception.Message)" } diff --git a/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 b/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 index 79b6222f5c37..ee1266ca949d 100644 --- a/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPOutOfoffice.ps1 @@ -6,7 +6,7 @@ function Set-CIPPOutOfOffice { $ExternalMessage, $TenantFilter, $State, - $APIName = "Set Out of Office", + $APIName = 'Set Out of Office', $ExecutingUser, $StartTime, $EndTime @@ -19,19 +19,17 @@ function Set-CIPPOutOfOffice { if (-not $EndTime) { $EndTime = (Get-Date $StartTime).AddDays(7) } - if ($State -ne "Scheduled") { - $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-MailboxAutoReplyConfiguration" -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage } -Anchor $userid - Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev "Info" -tenant $TenantFilter + if ($State -ne 'Scheduled') { + $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage } -Anchor $userid + Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Out-of-office for $($userid) to $state" -Sev 'Info' -tenant $TenantFilter return "Set Out-of-office for $($userid) to $state." - } - else { - $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet "Set-MailboxAutoReplyConfiguration" -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage; StartTime = $StartTime; EndTime = $EndTime } -Anchor $userid - Write-LogMessage -user $ExecutingUser -API $APIName -message "Scheduled Out-of-office for $($userid) between $StartTime and $EndTime" -Sev "Info" -tenant $TenantFilter + } else { + $OutOfOffice = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxAutoReplyConfiguration' -cmdParams @{Identity = $userid; AutoReplyState = $State; InternalMessage = $InternalMessage; ExternalMessage = $ExternalMessage; StartTime = $StartTime; EndTime = $EndTime } -Anchor $userid + Write-LogMessage -user $ExecutingUser -API $APIName -message "Scheduled Out-of-office for $($userid) between $StartTime and $EndTime" -Sev 'Info' -tenant $TenantFilter return "Scheduled Out-of-office for $($userid) between $($StartTime.toString()) and $($EndTime.toString())" } - } - catch { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev "Error" -tenant $TenantFilter + } catch { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) return "Could not add out of office message for $($userid). Error: $($_.Exception.Message)" } } diff --git a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 index fa2de7dc415b..e3ad3c8dd83e 100644 --- a/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPSignature.ps1 @@ -1,4 +1,4 @@ -function Set-CIPPOutOfOffice { +function Set-CIPPSignature { [CmdletBinding()] param ( $userid, From 20d54071b372b3a5d754579080e63e1a7e3b80f7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 14 Apr 2024 09:32:25 -0400 Subject: [PATCH 44/57] add concurrency settings --- host.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/host.json b/host.json index b1667f1e9bbb..260e4c628a81 100644 --- a/host.json +++ b/host.json @@ -11,6 +11,10 @@ "extensions": { "queues": { "maxDequeueCount": 1 + }, + "durableTask": { + "maxConcurrentActivityFunctions": 4, + "maxConcurrentOrchestratorFunctions": 4 } } } From 5bb2b63d63fba38b7ec1b73aa7a97d6e65a4c126 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 14 Apr 2024 11:35:29 -0400 Subject: [PATCH 45/57] Fix domain analyser large messages --- DomainAnalyser_All/run.ps1 | 15 +++++++++---- DomainAnalyser_GetTenantDomains/run.ps1 | 11 ++++++++-- DomainAnalyser_Orchestration/run.ps1 | 28 ++++++++++--------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/DomainAnalyser_All/run.ps1 b/DomainAnalyser_All/run.ps1 index 981afa4b7bbc..ad6b432abdb5 100644 --- a/DomainAnalyser_All/run.ps1 +++ b/DomainAnalyser_All/run.ps1 @@ -236,10 +236,17 @@ $Result.Score = $ScoreDomain $Result.ScorePercentage = [int](($Result.Score / $Result.MaximumScore) * 100) $Result.ScoreExplanation = ($ScoreExplanation) -join ', ' - $DomainObject.DomainAnalyser = ($Result | ConvertTo-Json -Compress).ToString() -# Final Write to Output -Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message "DNS Analyser Finished For $Domain" -sev Info +try { + $DomainTable = Get-CippTable -tablename 'Domains' + $DomainTable.Entity = $DomainObject + $DomainTable.Force = $true + Add-CIPPAzDataTableEntity @DomainTable -Write-Output $DomainObject \ No newline at end of file + # Final Write to Output + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message "DNS Analyser Finished For $Domain" -sev Info +} catch { + Write-LogMessage -API -API 'DomainAnalyser' -tenant $tenant.tenant -message "Error saving domain $Domain to table " -sev Error -LogData (Get-CippException -Exception $_) +} +return $null diff --git a/DomainAnalyser_GetTenantDomains/run.ps1 b/DomainAnalyser_GetTenantDomains/run.ps1 index 6c4b8b0d8110..c442316fe207 100644 --- a/DomainAnalyser_GetTenantDomains/run.ps1 +++ b/DomainAnalyser_GetTenantDomains/run.ps1 @@ -91,6 +91,13 @@ if ($TenantCount -gt 0) { # Batch insert all tenant domains try { Add-CIPPAzDataTableEntity @DomainTable -Entity $TenantDomainObjects -Force - } catch { Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser GetTenantDomains error' -sev info -LogData (Get-CippException -Exception $_) } - } catch { Write-LogMessage -API 'DomainAnalyser' -message 'GetTenantDomains loop error' -sev 'Error' -LogData (Get-CippException -Exception $_) } + return $true + } catch { + Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser GetTenantDomains error' -sev info -LogData (Get-CippException -Exception $_) + return $false + } + } catch { + Write-LogMessage -API 'DomainAnalyser' -message 'GetTenantDomains loop error' -sev 'Error' -LogData (Get-CippException -Exception $_) + return $false + } } diff --git a/DomainAnalyser_Orchestration/run.ps1 b/DomainAnalyser_Orchestration/run.ps1 index e8d51585ffa7..d39acbf62f37 100644 --- a/DomainAnalyser_Orchestration/run.ps1 +++ b/DomainAnalyser_Orchestration/run.ps1 @@ -10,27 +10,21 @@ try { $RetryOptions = New-DurableRetryOptions @DurableRetryOptions # Sync tenants + $GotDomains = $false try { - Invoke-ActivityFunction -FunctionName 'DomainAnalyser_GetTenantDomains' -Input 'Tenants' + $GotDomains = Invoke-ActivityFunction -FunctionName 'DomainAnalyser_GetTenantDomains' -Input 'Tenants' } catch { Write-Host "EXCEPTION: TenantDomains $($_.Exception.Message)" } - # Get list of all domains to process - $Batch = Invoke-ActivityFunction -FunctionName 'Activity_GetAllTableRows' -Input 'Domains' + if ($GotDomains) { + # Get list of all domains to process + $Batch = Invoke-ActivityFunction -FunctionName 'Activity_GetAllTableRows' -Input 'Domains' - $ParallelTasks = foreach ($Item in $Batch) { - Invoke-DurableActivity -FunctionName 'DomainAnalyser_All' -Input $item -NoWait -RetryOptions $RetryOptions - } - - # Collect activity function results and send to database - $TableParams = Get-CippTable -tablename 'Domains' - $TableParams.Entity = Wait-ActivityFunction -Task $ParallelTasks - $TableParams.Force = $true - $TableParams = $TableParams | ConvertTo-Json -Compress - - try { - Invoke-ActivityFunction -FunctionName 'Activity_AddOrUpdateTableRows' -Input $TableParams - } catch { - Write-Host "Orchestrator exception UpdateDomains $($_.Exception.Message)" + if (($Batch | Measure-Object).Count -gt 0) { + $ParallelTasks = foreach ($Item in $Batch) { + Invoke-DurableActivity -FunctionName 'DomainAnalyser_All' -Input $item -NoWait -RetryOptions $RetryOptions + } + $null = Wait-ActivityFunction -Task $ParallelTasks + } } } catch { Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser Orchestrator Error' -sev info -LogData (Get-CippException -Exception $_) From f4767950367cb01cb9b8d9023eb45f21cee45551 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sun, 14 Apr 2024 22:09:41 +0200 Subject: [PATCH 46/57] decrease concurrency per node --- host.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/host.json b/host.json index b1667f1e9bbb..37287e7860e2 100644 --- a/host.json +++ b/host.json @@ -9,6 +9,10 @@ "version": "[4.*, 5.0.0)" }, "extensions": { + "durableTask": { + "maxConcurrentActivityFunctions": 4, + "maxConcurrentOrchestratorFunctions": 4 + }, "queues": { "maxDequeueCount": 1 } From 51091b3f7c38be0ceae201a6baf556babc31c32c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 15 Apr 2024 00:25:25 -0400 Subject: [PATCH 47/57] Refactor domain analyser --- DomainAnalyser_All/function.json | 9 - DomainAnalyser_All/run.ps1 | 252 ------------------ DomainAnalyser_GetTenantDomains/function.json | 9 - DomainAnalyser_GetTenantDomains/run.ps1 | 103 ------- DomainAnalyser_Orchestration/function.json | 9 - DomainAnalyser_Orchestration/run.ps1 | 34 --- DomainAnalyser_OrchestrationStarter/run.ps1 | 25 +- Domain_OrchestrationStarterTimer/run.ps1 | 27 +- .../Push-DomainAnalyserDomain.ps1 | 252 ++++++++++++++++++ .../Push-DomainAnalyserTenant.ps1 | 107 ++++++++ .../Activity Triggers/Push-GetTenants.ps1 | 10 + Modules/CippEntrypoints/CippEntrypoints.psm1 | 5 +- 12 files changed, 404 insertions(+), 438 deletions(-) delete mode 100644 DomainAnalyser_All/function.json delete mode 100644 DomainAnalyser_All/run.ps1 delete mode 100644 DomainAnalyser_GetTenantDomains/function.json delete mode 100644 DomainAnalyser_GetTenantDomains/run.ps1 delete mode 100644 DomainAnalyser_Orchestration/function.json delete mode 100644 DomainAnalyser_Orchestration/run.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetTenants.ps1 diff --git a/DomainAnalyser_All/function.json b/DomainAnalyser_All/function.json deleted file mode 100644 index 50f47ad53465..000000000000 --- a/DomainAnalyser_All/function.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bindings": [ - { - "name": "DomainObject", - "direction": "in", - "type": "activityTrigger" - } - ] -} diff --git a/DomainAnalyser_All/run.ps1 b/DomainAnalyser_All/run.ps1 deleted file mode 100644 index ad6b432abdb5..000000000000 --- a/DomainAnalyser_All/run.ps1 +++ /dev/null @@ -1,252 +0,0 @@ -param($DomainObject) - -Import-Module DNSHealth - -try { - $ConfigTable = Get-CippTable -tablename Config - $Filter = "PartitionKey eq 'Domains' and RowKey eq 'Domains'" - $Config = Get-CIPPAzDataTableEntity @ConfigTable -Filter $Filter - - $ValidResolvers = @('Google', 'CloudFlare', 'Quad9') - if ($ValidResolvers -contains $Config.Resolver) { - $Resolver = $Config.Resolver - } else { - $Resolver = 'Google' - $Config = @{ - PartitionKey = 'Domains' - RowKey = 'Domains' - Resolver = $Resolver - } - Add-CIPPAzDataTableEntity @ConfigTable -Entity $Config -Force - } -} catch { - $Resolver = 'Google' -} -Set-DnsResolver -Resolver $Resolver - -$Domain = $DomainObject.rowKey - -try { - $Tenant = $DomainObject.TenantDetails | ConvertFrom-Json -ErrorAction Stop -} catch { - $Tenant = @{Tenant = 'None' } -} - -#Write-Host "$($DomainObject.TenantDetails)" - -$Result = [PSCustomObject]@{ - Tenant = $Tenant.Tenant - TenantID = $Tenant.TenantGUID - GUID = $($Domain.Replace('.', '')) - LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') - Domain = $Domain - NSRecords = (Read-NSRecord -Domain $Domain).Records - ExpectedSPFRecord = '' - ActualSPFRecord = '' - SPFPassAll = '' - ActualMXRecords = '' - MXPassTest = '' - DMARCPresent = '' - DMARCFullPolicy = '' - DMARCActionPolicy = '' - DMARCReportingActive = '' - DMARCPercentagePass = '' - DNSSECPresent = '' - MailProvider = '' - DKIMEnabled = '' - DKIMRecords = '' - Score = '' - MaximumScore = 160 - ScorePercentage = '' - ScoreExplanation = '' -} - -$Scores = [PSCustomObject]@{ - SPFPresent = 10 - SPFCorrectAll = 20 - MXRecommended = 10 - DMARCPresent = 10 - DMARCSetQuarantine = 20 - DMARCSetReject = 30 - DMARCReportingActive = 20 - DMARCPercentageGood = 20 - DNSSECPresent = 20 - DKIMActiveAndWorking = 20 -} - -$ScoreDomain = 0 -# Setup Score Explanation -$ScoreExplanation = [System.Collections.Generic.List[string]]::new() - -# Check MX Record -$MXRecord = Read-MXRecord -Domain $Domain -ErrorAction Stop - -$Result.ExpectedSPFRecord = $MXRecord.ExpectedInclude -$Result.MXPassTest = $false -$Result.ActualMXRecords = $MXRecord.Records - -# Check fail counts to ensure all tests pass -#$MXWarnCount = $MXRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count -$MXFailCount = $MXRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - -if ($MXFailCount -eq 0) { - $Result.MXPassTest = $true - $ScoreDomain += $Scores.MXRecommended -} else { - $ScoreExplanation.Add('MX record did not pass validation') | Out-Null -} - -if ([string]::IsNullOrEmpty($MXRecord.MailProvider)) { - $Result.MailProvider = 'Unknown' -} else { - $Result.MailProvider = $MXRecord.MailProvider.Name -} - -# Get SPF Record -try { - $SPFRecord = Read-SpfRecord -Domain $Domain -ErrorAction Stop - if ($SPFRecord.RecordCount -gt 0) { - $Result.ActualSPFRecord = $SPFRecord.Record - if ($SPFRecord.RecordCount -eq 1) { - $ScoreDomain += $Scores.SPFPresent - } else { - $ScoreExplanation.Add('Multiple SPF records detected') | Out-Null - } - } else { - $Result.ActualSPFRecord = 'No SPF Record' - $ScoreExplanation.Add('No SPF Record Found') | Out-Null - } -} catch { - $Message = 'SPF Error' - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error - throw $Message -} - -# Check SPF Record -$Result.SPFPassAll = $false - -# Check warning + fail counts to ensure all tests pass -#$SPFWarnCount = $SPFRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count -$SPFFailCount = $SPFRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - -if ($SPFFailCount -eq 0) { - $ScoreDomain += $Scores.SPFCorrectAll - $Result.SPFPassAll = $true -} else { - $ScoreExplanation.Add('SPF record did not pass validation') | Out-Null -} - -# Get DMARC Record -try { - $DMARCPolicy = Read-DmarcPolicy -Domain $Domain -ErrorAction Stop - - If ([string]::IsNullOrEmpty($DMARCPolicy.Record)) { - $Result.DMARCPresent = $false - $ScoreExplanation.Add('No DMARC Records Found') | Out-Null - } else { - $Result.DMARCPresent = $true - $ScoreDomain += $Scores.DMARCPresent - - $Result.DMARCFullPolicy = $DMARCPolicy.Record - if ($DMARCPolicy.Policy -eq 'reject' -and $DMARCPolicy.SubdomainPolicy -eq 'reject') { - $Result.DMARCActionPolicy = 'Reject' - $ScoreDomain += $Scores.DMARCSetReject - } - if ($DMARCPolicy.Policy -eq 'none') { - $Result.DMARCActionPolicy = 'None' - $ScoreExplanation.Add('DMARC is not being enforced') | Out-Null - } - if ($DMARCPolicy.Policy -eq 'quarantine') { - $Result.DMARCActionPolicy = 'Quarantine' - $ScoreDomain += $Scores.DMARCSetQuarantine - $ScoreExplanation.Add('DMARC Partially Enforced with quarantine') | Out-Null - } - - $ReportEmailCount = $DMARCPolicy.ReportingEmails | Measure-Object | Select-Object -ExpandProperty Count - if ($ReportEmailCount -gt 0) { - $Result.DMARCReportingActive = $true - $ScoreDomain += $Scores.DMARCReportingActive - } else { - $Result.DMARCReportingActive = $False - $ScoreExplanation.Add('DMARC Reporting not Configured') | Out-Null - } - - if ($DMARCPolicy.Percent -eq 100) { - $Result.DMARCPercentagePass = $true - $ScoreDomain += $Scores.DMARCPercentageGood - } else { - $Result.DMARCPercentagePass = $false - $ScoreExplanation.Add('DMARC Not Checking All Messages') | Out-Null - } - } -} catch { - $Message = 'DMARC Error' - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error - throw $Message -} - -# DNS Sec Check -try { - $DNSSECResult = Test-DNSSEC -Domain $Domain -ErrorAction Stop - $DNSSECFailCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - $DNSSECWarnCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - if (($DNSSECFailCount + $DNSSECWarnCount) -eq 0) { - $Result.DNSSECPresent = $true - $ScoreDomain += $Scores.DNSSECPresent - } else { - $Result.DNSSECPresent = $false - $ScoreExplanation.Add('DNSSEC Not Configured or Enabled') | Out-Null - } -} catch { - $Message = 'DNSSEC Error' - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error - throw $Message -} - -# DKIM Check -try { - $DkimParams = @{ - Domain = $Domain - FallbackToMicrosoftSelectors = $true - } - if (![string]::IsNullOrEmpty($DomainObject.DkimSelectors)) { - $DkimParams.Selectors = $DomainObject.DkimSelectors | ConvertFrom-Json - } - - $DkimRecord = Read-DkimRecord @DkimParams -ErrorAction Stop - - $DkimRecordCount = $DkimRecord.Records | Measure-Object | Select-Object -ExpandProperty Count - $DkimFailCount = $DkimRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count - #$DkimWarnCount = $DkimRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count - if ($DkimRecordCount -gt 0 -and $DkimFailCount -eq 0) { - $Result.DKIMEnabled = $true - $ScoreDomain += $Scores.DKIMActiveAndWorking - $Result.DKIMRecords = $DkimRecord.Records | Select-Object Selector, Record - } else { - $Result.DKIMEnabled = $false - $ScoreExplanation.Add('DKIM Not Configured') | Out-Null - } -} catch { - $Message = 'DKIM Exception' - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error - throw $Message -} -# Final Score -$Result.Score = $ScoreDomain -$Result.ScorePercentage = [int](($Result.Score / $Result.MaximumScore) * 100) -$Result.ScoreExplanation = ($ScoreExplanation) -join ', ' - -$DomainObject.DomainAnalyser = ($Result | ConvertTo-Json -Compress).ToString() - -try { - $DomainTable = Get-CippTable -tablename 'Domains' - $DomainTable.Entity = $DomainObject - $DomainTable.Force = $true - Add-CIPPAzDataTableEntity @DomainTable - - # Final Write to Output - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message "DNS Analyser Finished For $Domain" -sev Info -} catch { - Write-LogMessage -API -API 'DomainAnalyser' -tenant $tenant.tenant -message "Error saving domain $Domain to table " -sev Error -LogData (Get-CippException -Exception $_) -} -return $null diff --git a/DomainAnalyser_GetTenantDomains/function.json b/DomainAnalyser_GetTenantDomains/function.json deleted file mode 100644 index ce320a44c1a3..000000000000 --- a/DomainAnalyser_GetTenantDomains/function.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bindings": [ - { - "name": "name", - "type": "activityTrigger", - "direction": "in" - } - ] -} diff --git a/DomainAnalyser_GetTenantDomains/run.ps1 b/DomainAnalyser_GetTenantDomains/run.ps1 deleted file mode 100644 index c442316fe207..000000000000 --- a/DomainAnalyser_GetTenantDomains/run.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -param($name) - -$Tenants = Get-Tenants -$ExcludedTenants = Get-Tenants -SkipList -$DomainTable = Get-CippTable -tablename 'Domains' - -$TenantDomains = $Tenants | ForEach-Object -Parallel { - Import-Module CippCore - $Tenant = $_ - # Get Domains to Lookup - try { - $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant.defaultDomainName | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -Notlike '*.excl.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.isVerified) } - - foreach ($d in $domains) { - [PSCustomObject]@{ - Tenant = $Tenant.defaultDomainName - TenantGUID = $Tenant.customerId - InitialDomainName = $Tenant.initialDomainName - Domain = $d.id - AuthenticationType = $d.authenticationType - IsAdminManaged = $d.isAdminManaged - IsDefault = $d.isDefault - IsInitial = $d.isInitial - IsRoot = $d.isRoot - IsVerified = $d.isVerified - SupportedServices = $d.supportedServices - } - } - } catch { - Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.defaultDomainName -message 'DNS Analyser GraphGetRequest' -LogData (Get-CippException -Exception $_) -sev Error - } -} | Sort-Object -Unique -Property Domain - -# Cleanup domains from tenants with errors, skip domains with manually set selectors or mail providers -foreach ($Exclude in $ExcludedTenants) { - $Filter = "PartitionKey eq 'TenantDomains' and TenantId eq '{0}'" -f $Exclude.defaultDomainName - $CleanupRows = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter - $CleanupCount = ($CleanupRows | Measure-Object).Count - if ($CleanupCount -gt 0) { - Write-LogMessage -API 'DomainAnalyser' -tenant $Exclude.defaultDomainName -message "Cleaning up $CleanupCount domain(s) for excluded tenant" -sev Info - Remove-AzDataTableEntity @DomainTable -Entity $CleanupRows - } -} - -$TenantCount = ($TenantDomains | Measure-Object).Count -if ($TenantCount -gt 0) { - Write-Host "$TenantCount tenant Domains" - - # Process tenant domain results - try { - $TenantDomainObjects = foreach ($Tenant in $TenantDomains) { - $TenantDetails = ($Tenant | ConvertTo-Json -Compress).ToString() - $Filter = "PartitionKey eq '{0}' and RowKey eq '{1}'" -f $Tenant.Tenant, $Tenant.Domain - $OldDomain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter - - if ($OldDomain) { - Remove-AzDataTableEntity @DomainTable -Entity $OldDomain | Out-Null - } - - $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $Tenant.Domain - $Domain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter - - if (!$Domain -or $null -eq $Domain.TenantGUID) { - $DomainObject = [pscustomobject]@{ - DomainAnalyser = '' - TenantDetails = $TenantDetails - TenantId = $Tenant.Tenant - TenantGUID = $Tenant.TenantGUID - DkimSelectors = '' - MailProviders = '' - RowKey = $Tenant.Domain - PartitionKey = 'TenantDomains' - } - - if ($OldDomain) { - $DomainObject.DkimSelectors = $OldDomain.DkimSelectors - $DomainObject.MailProviders = $OldDomain.MailProviders - } - $Domain = $DomainObject - } else { - $Domain.TenantDetails = $TenantDetails - if ($OldDomain) { - $Domain.DkimSelectors = $OldDomain.DkimSelectors - $Domain.MailProviders = $OldDomain.MailProviders - } - } - # Return domain object to list - $Domain - } - - # Batch insert all tenant domains - try { - Add-CIPPAzDataTableEntity @DomainTable -Entity $TenantDomainObjects -Force - return $true - } catch { - Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser GetTenantDomains error' -sev info -LogData (Get-CippException -Exception $_) - return $false - } - } catch { - Write-LogMessage -API 'DomainAnalyser' -message 'GetTenantDomains loop error' -sev 'Error' -LogData (Get-CippException -Exception $_) - return $false - } -} diff --git a/DomainAnalyser_Orchestration/function.json b/DomainAnalyser_Orchestration/function.json deleted file mode 100644 index 7326b39c184d..000000000000 --- a/DomainAnalyser_Orchestration/function.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "bindings": [ - { - "name": "Context", - "type": "orchestrationTrigger", - "direction": "in" - } - ] -} \ No newline at end of file diff --git a/DomainAnalyser_Orchestration/run.ps1 b/DomainAnalyser_Orchestration/run.ps1 deleted file mode 100644 index d39acbf62f37..000000000000 --- a/DomainAnalyser_Orchestration/run.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -param($Context) - -try { - - $DurableRetryOptions = @{ - FirstRetryInterval = (New-TimeSpan -Seconds 5) - MaxNumberOfAttempts = 1 - BackoffCoefficient = 2 - } - $RetryOptions = New-DurableRetryOptions @DurableRetryOptions - - # Sync tenants - $GotDomains = $false - try { - $GotDomains = Invoke-ActivityFunction -FunctionName 'DomainAnalyser_GetTenantDomains' -Input 'Tenants' - } catch { Write-Host "EXCEPTION: TenantDomains $($_.Exception.Message)" } - - if ($GotDomains) { - # Get list of all domains to process - $Batch = Invoke-ActivityFunction -FunctionName 'Activity_GetAllTableRows' -Input 'Domains' - - if (($Batch | Measure-Object).Count -gt 0) { - $ParallelTasks = foreach ($Item in $Batch) { - Invoke-DurableActivity -FunctionName 'DomainAnalyser_All' -Input $item -NoWait -RetryOptions $RetryOptions - } - $null = Wait-ActivityFunction -Task $ParallelTasks - } - } -} catch { - Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser Orchestrator Error' -sev info -LogData (Get-CippException -Exception $_) - #Write-Host $_.Exception | ConvertTo-Json -} finally { - Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser has Finished' -sev Info -} \ No newline at end of file diff --git a/DomainAnalyser_OrchestrationStarter/run.ps1 b/DomainAnalyser_OrchestrationStarter/run.ps1 index 7c85e87640cd..b2fd0769428c 100644 --- a/DomainAnalyser_OrchestrationStarter/run.ps1 +++ b/DomainAnalyser_OrchestrationStarter/run.ps1 @@ -1,19 +1,20 @@ using namespace System.Net param($Request, $TriggerMetadata) -if ($CurrentlyRunning) { - $Results = [pscustomobject]@{'Results' = 'Already running. Please wait for the current instance to finish' } - Write-LogMessage -API 'DomainAnalyser' -message 'Attempted to start domain analysis but an instance was already running.' -sev Info -} -else { - $InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' - Write-Host "Started orchestration with ID = '$InstanceId'" - $Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId - Write-LogMessage -API 'DomainAnalyser' -message 'Started retrieving domain information' -sev Info - $Results = [pscustomobject]@{'Results' = 'Started running analysis' } -} -Write-Host ($Orchestrator | ConvertTo-Json) +$Results = [pscustomobject]@{'Results' = 'Domain Analyser started' } +$InputObject = [PSCustomObject]@{ + QueueFunction = [PSCustomObject]@{ + FunctionName = 'GetTenants' + DurableName = 'DomainAnalyserTenant' + TenantParams = @{ + IncludeAll = $true + } + } + OrchestratorName = 'DomainAnalyser_Tenants' + SkipLog = $true +} +Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5) Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK diff --git a/Domain_OrchestrationStarterTimer/run.ps1 b/Domain_OrchestrationStarterTimer/run.ps1 index 67e945736bb1..63d7cf8985bb 100644 --- a/Domain_OrchestrationStarterTimer/run.ps1 +++ b/Domain_OrchestrationStarterTimer/run.ps1 @@ -1,22 +1,33 @@ param($Timer) -if ($env:DEV_SKIP_DOMAIN_TIMER) { +if ($env:DEV_SKIP_DOMAIN_TIMER) { Write-Host 'Skipping DomainAnalyser timer' - exit 0 + exit 0 } try { if ($CurrentlyRunning) { $Results = [pscustomobject]@{'Results' = 'Already running. Please wait for the current instance to finish' } Write-LogMessage -API 'DomainAnalyser' -message 'Attempted to start analysis but an instance was already running.' -sev Info - } - else { - $InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' + } else { + #$InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' Write-Host "Started orchestration with ID = '$InstanceId'" - $Orchestrator = New-OrchestrationCheckStatusResponse -Request $Timer -InstanceId $InstanceId + #Orchestrator = New-OrchestrationCheckStatusResponse -Request $Timer -InstanceId $InstanceId Write-LogMessage -API 'DomainAnalyser' -message 'Starting Domain Analyser' -sev Info $Results = [pscustomobject]@{'Results' = 'Starting Domain Analyser' } + + $InputObject = [PSCustomObject]@{ + QueueFunction = [PSCustomObject]@{ + FunctionName = 'GetTenants' + DurableName = 'DomainAnalyserTenant' + TenantParams = @{ + IncludeAll = $true + } + } + OrchestratorName = 'DomainAnalyser_Tenants' + SkipLog = $true + } + Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5) } Write-Host ($Orchestrator | ConvertTo-Json) -} -catch { Write-Host "Domain_OrchestratorStarterTimer Exception $($_.Exception.Message)" } \ No newline at end of file +} catch { Write-Host "Domain_OrchestratorStarterTimer Exception $($_.Exception.Message)" } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 new file mode 100644 index 000000000000..c5aa1f420919 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 @@ -0,0 +1,252 @@ +function Push-DomainAnalyserDomain { + param($Item) + $DomainTable = Get-CippTable -tablename 'Domains' + $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $Item.RowKey + $DomainObject = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + + try { + $ConfigTable = Get-CippTable -tablename Config + $Filter = "PartitionKey eq 'Domains' and RowKey eq 'Domains'" + $Config = Get-CIPPAzDataTableEntity @ConfigTable -Filter $Filter + + $ValidResolvers = @('Google', 'CloudFlare', 'Quad9') + if ($ValidResolvers -contains $Config.Resolver) { + $Resolver = $Config.Resolver + } else { + $Resolver = 'Google' + $Config = @{ + PartitionKey = 'Domains' + RowKey = 'Domains' + Resolver = $Resolver + } + Add-CIPPAzDataTableEntity @ConfigTable -Entity $Config -Force + } + } catch { + $Resolver = 'Google' + } + Set-DnsResolver -Resolver $Resolver + + $Domain = $DomainObject.rowKey + + try { + $Tenant = $DomainObject.TenantDetails | ConvertFrom-Json -ErrorAction Stop + } catch { + $Tenant = @{Tenant = 'None' } + } + + $Result = [PSCustomObject]@{ + Tenant = $Tenant.Tenant + TenantID = $Tenant.TenantGUID + GUID = $($Domain.Replace('.', '')) + LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') + Domain = $Domain + NSRecords = (Read-NSRecord -Domain $Domain).Records + ExpectedSPFRecord = '' + ActualSPFRecord = '' + SPFPassAll = '' + ActualMXRecords = '' + MXPassTest = '' + DMARCPresent = '' + DMARCFullPolicy = '' + DMARCActionPolicy = '' + DMARCReportingActive = '' + DMARCPercentagePass = '' + DNSSECPresent = '' + MailProvider = '' + DKIMEnabled = '' + DKIMRecords = '' + Score = '' + MaximumScore = 160 + ScorePercentage = '' + ScoreExplanation = '' + } + + $Scores = [PSCustomObject]@{ + SPFPresent = 10 + SPFCorrectAll = 20 + MXRecommended = 10 + DMARCPresent = 10 + DMARCSetQuarantine = 20 + DMARCSetReject = 30 + DMARCReportingActive = 20 + DMARCPercentageGood = 20 + DNSSECPresent = 20 + DKIMActiveAndWorking = 20 + } + + $ScoreDomain = 0 + # Setup Score Explanation + $ScoreExplanation = [System.Collections.Generic.List[string]]::new() + + # Check MX Record + $MXRecord = Read-MXRecord -Domain $Domain -ErrorAction Stop + + $Result.ExpectedSPFRecord = $MXRecord.ExpectedInclude + $Result.MXPassTest = $false + $Result.ActualMXRecords = $MXRecord.Records + + # Check fail counts to ensure all tests pass + #$MXWarnCount = $MXRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count + $MXFailCount = $MXRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + + if ($MXFailCount -eq 0) { + $Result.MXPassTest = $true + $ScoreDomain += $Scores.MXRecommended + } else { + $ScoreExplanation.Add('MX record did not pass validation') | Out-Null + } + + if ([string]::IsNullOrEmpty($MXRecord.MailProvider)) { + $Result.MailProvider = 'Unknown' + } else { + $Result.MailProvider = $MXRecord.MailProvider.Name + } + + # Get SPF Record + try { + $SPFRecord = Read-SpfRecord -Domain $Domain -ErrorAction Stop + if ($SPFRecord.RecordCount -gt 0) { + $Result.ActualSPFRecord = $SPFRecord.Record + if ($SPFRecord.RecordCount -eq 1) { + $ScoreDomain += $Scores.SPFPresent + } else { + $ScoreExplanation.Add('Multiple SPF records detected') | Out-Null + } + } else { + $Result.ActualSPFRecord = 'No SPF Record' + $ScoreExplanation.Add('No SPF Record Found') | Out-Null + } + } catch { + $Message = 'SPF Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + + # Check SPF Record + $Result.SPFPassAll = $false + + # Check warning + fail counts to ensure all tests pass + #$SPFWarnCount = $SPFRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count + $SPFFailCount = $SPFRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + + if ($SPFFailCount -eq 0) { + $ScoreDomain += $Scores.SPFCorrectAll + $Result.SPFPassAll = $true + } else { + $ScoreExplanation.Add('SPF record did not pass validation') | Out-Null + } + + # Get DMARC Record + try { + $DMARCPolicy = Read-DmarcPolicy -Domain $Domain -ErrorAction Stop + + If ([string]::IsNullOrEmpty($DMARCPolicy.Record)) { + $Result.DMARCPresent = $false + $ScoreExplanation.Add('No DMARC Records Found') | Out-Null + } else { + $Result.DMARCPresent = $true + $ScoreDomain += $Scores.DMARCPresent + + $Result.DMARCFullPolicy = $DMARCPolicy.Record + if ($DMARCPolicy.Policy -eq 'reject' -and $DMARCPolicy.SubdomainPolicy -eq 'reject') { + $Result.DMARCActionPolicy = 'Reject' + $ScoreDomain += $Scores.DMARCSetReject + } + if ($DMARCPolicy.Policy -eq 'none') { + $Result.DMARCActionPolicy = 'None' + $ScoreExplanation.Add('DMARC is not being enforced') | Out-Null + } + if ($DMARCPolicy.Policy -eq 'quarantine') { + $Result.DMARCActionPolicy = 'Quarantine' + $ScoreDomain += $Scores.DMARCSetQuarantine + $ScoreExplanation.Add('DMARC Partially Enforced with quarantine') | Out-Null + } + + $ReportEmailCount = $DMARCPolicy.ReportingEmails | Measure-Object | Select-Object -ExpandProperty Count + if ($ReportEmailCount -gt 0) { + $Result.DMARCReportingActive = $true + $ScoreDomain += $Scores.DMARCReportingActive + } else { + $Result.DMARCReportingActive = $False + $ScoreExplanation.Add('DMARC Reporting not Configured') | Out-Null + } + + if ($DMARCPolicy.Percent -eq 100) { + $Result.DMARCPercentagePass = $true + $ScoreDomain += $Scores.DMARCPercentageGood + } else { + $Result.DMARCPercentagePass = $false + $ScoreExplanation.Add('DMARC Not Checking All Messages') | Out-Null + } + } + } catch { + $Message = 'DMARC Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + + # DNS Sec Check + try { + $DNSSECResult = Test-DNSSEC -Domain $Domain -ErrorAction Stop + $DNSSECFailCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + $DNSSECWarnCount = $DNSSECResult.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + if (($DNSSECFailCount + $DNSSECWarnCount) -eq 0) { + $Result.DNSSECPresent = $true + $ScoreDomain += $Scores.DNSSECPresent + } else { + $Result.DNSSECPresent = $false + $ScoreExplanation.Add('DNSSEC Not Configured or Enabled') | Out-Null + } + } catch { + $Message = 'DNSSEC Error' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + + # DKIM Check + try { + $DkimParams = @{ + Domain = $Domain + FallbackToMicrosoftSelectors = $true + } + if (![string]::IsNullOrEmpty($DomainObject.DkimSelectors)) { + $DkimParams.Selectors = $DomainObject.DkimSelectors | ConvertFrom-Json + } + + $DkimRecord = Read-DkimRecord @DkimParams -ErrorAction Stop + + $DkimRecordCount = $DkimRecord.Records | Measure-Object | Select-Object -ExpandProperty Count + $DkimFailCount = $DkimRecord.ValidationFails | Measure-Object | Select-Object -ExpandProperty Count + #$DkimWarnCount = $DkimRecord.ValidationWarns | Measure-Object | Select-Object -ExpandProperty Count + if ($DkimRecordCount -gt 0 -and $DkimFailCount -eq 0) { + $Result.DKIMEnabled = $true + $ScoreDomain += $Scores.DKIMActiveAndWorking + $Result.DKIMRecords = $DkimRecord.Records | Select-Object Selector, Record + } else { + $Result.DKIMEnabled = $false + $ScoreExplanation.Add('DKIM Not Configured') | Out-Null + } + } catch { + $Message = 'DKIM Exception' + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message $Message -LogData (Get-CippException -Exception $_) -sev Error + throw $Message + } + # Final Score + $Result.Score = $ScoreDomain + $Result.ScorePercentage = [int](($Result.Score / $Result.MaximumScore) * 100) + $Result.ScoreExplanation = ($ScoreExplanation) -join ', ' + + $DomainObject.DomainAnalyser = ($Result | ConvertTo-Json -Compress).ToString() + + try { + $DomainTable.Entity = $DomainObject + $DomainTable.Force = $true + Add-CIPPAzDataTableEntity @DomainTable -Entity $DomainObject -Force + + # Final Write to Output + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.tenant -message "DNS Analyser Finished For $Domain" -sev Info + } catch { + Write-LogMessage -API -API 'DomainAnalyser' -tenant $tenant.tenant -message "Error saving domain $Domain to table " -sev Error -LogData (Get-CippException -Exception $_) + } + return $null +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 new file mode 100644 index 000000000000..b376215dca70 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 @@ -0,0 +1,107 @@ +function Push-DomainAnalyserTenant { + <# + .FUNCTIONALITY + Entrypoint + #> + param($Item) + + $Tenant = Get-Tenants | Where-Object { $_.customerId -eq $Item.customerId } + $DomainTable = Get-CippTable -tablename 'Domains' + + if ($Tenant.Excluded -eq $true) { + $Filter = "PartitionKey eq 'TenantDomains' and TenantId eq '{0}'" -f $Exclude.defaultDomainName + $CleanupRows = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + $CleanupCount = ($CleanupRows | Measure-Object).Count + if ($CleanupCount -gt 0) { + Write-LogMessage -API 'DomainAnalyser' -tenant $Exclude.defaultDomainName -message "Cleaning up $CleanupCount domain(s) for excluded tenant" -sev Info + Remove-AzDataTableEntity @DomainTable -Entity $CleanupRows + } + } elseif ($Tenant.GraphErrorCount -gt 50) { + return + } else { + try { + $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant.defaultDomainName | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -Notlike '*.excl.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.isVerified) } + + $TenantDomains = foreach ($d in $domains) { + [PSCustomObject]@{ + Tenant = $Tenant.defaultDomainName + TenantGUID = $Tenant.customerId + InitialDomainName = $Tenant.initialDomainName + Domain = $d.id + AuthenticationType = $d.authenticationType + IsAdminManaged = $d.isAdminManaged + IsDefault = $d.isDefault + IsInitial = $d.isInitial + IsRoot = $d.isRoot + IsVerified = $d.isVerified + SupportedServices = $d.supportedServices + } + } + + $DomainCount = ($TenantDomains | Measure-Object).Count + if ($DomainCount -gt 0) { + Write-Host "$TenantCount tenant Domains" + try { + $TenantDomainObjects = foreach ($Tenant in $TenantDomains) { + $TenantDetails = ($Tenant | ConvertTo-Json -Compress).ToString() + $Filter = "PartitionKey eq '{0}' and RowKey eq '{1}'" -f $Tenant.Tenant, $Tenant.Domain + $OldDomain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + + if ($OldDomain) { + Remove-AzDataTableEntity @DomainTable -Entity $OldDomain | Out-Null + } + + $Filter = "PartitionKey eq 'TenantDomains' and RowKey eq '{0}'" -f $Tenant.Domain + $Domain = Get-CIPPAzDataTableEntity @DomainTable -Filter $Filter + + if (!$Domain -or $null -eq $Domain.TenantGUID) { + $DomainObject = [pscustomobject]@{ + DomainAnalyser = '' + TenantDetails = $TenantDetails + TenantId = $Tenant.Tenant + TenantGUID = $Tenant.TenantGUID + DkimSelectors = '' + MailProviders = '' + RowKey = $Tenant.Domain + PartitionKey = 'TenantDomains' + } + + if ($OldDomain) { + $DomainObject.DkimSelectors = $OldDomain.DkimSelectors + $DomainObject.MailProviders = $OldDomain.MailProviders + } + $Domain = $DomainObject + } else { + $Domain.TenantDetails = $TenantDetails + if ($OldDomain) { + $Domain.DkimSelectors = $OldDomain.DkimSelectors + $Domain.MailProviders = $OldDomain.MailProviders + } + } + # Return domain object to list + $Domain + } + + # Batch insert tenant domains + try { + Add-CIPPAzDataTableEntity @DomainTable -Entity $TenantDomainObjects -Force + $InputObject = [PSCustomObject]@{ + Batch = $TenantDomainObjects | Select-Object RowKey, @{n = 'FunctionName'; exp = { 'DomainAnalyserDomain' } } + OrchestratorName = "DomainAnalyser_$($Tenant.defaultDomainName)" + SkipLog = $true + } + Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Compress -Depth 5) + } catch { + Write-LogMessage -API 'DomainAnalyser' -message 'Domain Analyser GetTenantDomains error' -sev info -LogData (Get-CippException -Exception $_) + } + } catch { + Write-LogMessage -API 'DomainAnalyser' -message 'GetTenantDomains loop error' -sev 'Error' -LogData (Get-CippException -Exception $_) + } + } + } catch { + Write-Host (Get-CippException -Exception $_) + Write-LogMessage -API 'DomainAnalyser' -tenant $tenant.defaultDomainName -message 'DNS Analyser GraphGetRequest' -LogData (Get-CippException -Exception $_) -sev Error + } + } + return $null +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetTenants.ps1 new file mode 100644 index 000000000000..4d9e274b7aaa --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-GetTenants.ps1 @@ -0,0 +1,10 @@ +function Push-GetTenants { + Param($Item) + + $Params = $Item.TenantParams | ConvertTo-Json | ConvertFrom-Json -AsHashtable + try { + Get-Tenants @Params | Select-Object customerId, @{n = 'FunctionName'; e = { $Item.DurableName } } + } catch { + Write-Host "GetTenants Exception $($_.Exception.Message)" + } +} \ No newline at end of file diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index 02651d9a2a3f..7c1b9b56833d 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -50,13 +50,14 @@ function Receive-CippQueueTrigger { function Receive-CippOrchestrationTrigger { param($Context) - Write-Host 'Orchestrator started' + try { if (Test-Json -Json $Context.Input) { $OrchestratorInput = $Context.Input | ConvertFrom-Json } else { $OrchestratorInput = $Context.Input } + Write-Host "Orchestrator started $($OrchestratorInput.OrchestratorName)" $DurableRetryOptions = @{ FirstRetryInterval = (New-TimeSpan -Seconds 5) @@ -87,7 +88,7 @@ function Receive-CippOrchestrationTrigger { Write-LogMessage -API $OrchestratorInput.OrchestratorName -tenant $tenant -message "Finished $($OrchestratorInput.OrchestratorName)" -sev Info } } catch { - Write-Host "Orchestrator error $($_.Exception.Message)" + Write-Host "Orchestrator error $($_.Exception.Message) line $($_.InvocationInfo.ScriptLineNumber)" } } From 7bbf66123facaef3ad013d87bda847564e972b91 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 15 Apr 2024 11:38:49 +0200 Subject: [PATCH 48/57] tmp removal of durable limits --- host.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/host.json b/host.json index 37287e7860e2..b1667f1e9bbb 100644 --- a/host.json +++ b/host.json @@ -9,10 +9,6 @@ "version": "[4.*, 5.0.0)" }, "extensions": { - "durableTask": { - "maxConcurrentActivityFunctions": 4, - "maxConcurrentOrchestratorFunctions": 4 - }, "queues": { "maxDequeueCount": 1 } From 6eba0ba87126dd65b9b75bb6549142d0b58d4d64 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 15 Apr 2024 11:45:01 +0200 Subject: [PATCH 49/57] clear tenant cache fix --- .../Tenant/Administration/Tenant/Invoke-ListTenants.ps1 | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 index 3d2967edb591..19e388a39b47 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 @@ -21,13 +21,8 @@ Function Invoke-ListTenants { StatusCode = [HttpStatusCode]::OK Body = $GraphRequest }) - $InputObject = [PSCustomObject]@{ - OrchestratorName = 'UpdateTenantsOrchestrator' - Batch = @(@{'FunctionName' = 'UpdateTenants' }) - } - #Write-Host ($InputObject | ConvertTo-Json) - $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) - exit + Get-Tenants -IncludeAll -TriggerRefresh + } try { From 488d75fbf513c24c0ab147095ee113a2509c5126 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 15 Apr 2024 11:46:08 +0200 Subject: [PATCH 50/57] add option to trigger refresh --- .../Tenant/Administration/Tenant/Invoke-ListTenants.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 index 19e388a39b47..3c183a98af86 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenants.ps1 @@ -24,7 +24,9 @@ Function Invoke-ListTenants { Get-Tenants -IncludeAll -TriggerRefresh } - + if ($Request.query.TriggerRefresh) { + Get-Tenants -IncludeAll -TriggerRefresh + } try { $tenantfilter = $Request.Query.TenantFilter $Tenants = Get-Tenants -IncludeErrors -SkipDomains From d86a8c1243d8b390360e86df10f9066bb4b950ac Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 15 Apr 2024 11:46:48 +0200 Subject: [PATCH 51/57] remove durable limit --- host.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/host.json b/host.json index 260e4c628a81..b6359ce3214e 100644 --- a/host.json +++ b/host.json @@ -13,8 +13,8 @@ "maxDequeueCount": 1 }, "durableTask": { - "maxConcurrentActivityFunctions": 4, - "maxConcurrentOrchestratorFunctions": 4 + "maxConcurrentActivityFunctions": 10, + "maxConcurrentOrchestratorFunctions": 10 } } } From 7eda16742605578f9f732c91da781adfdc16859c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 15 Apr 2024 11:47:11 +0200 Subject: [PATCH 52/57] up version --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index d41f08f1f3c0..9af9a6a81c7e 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.5.1 \ No newline at end of file +5.5.2 \ No newline at end of file From ba6c2cb36b4bca489b50795fd0b4b46fcdf5cba5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 15 Apr 2024 11:48:52 +0200 Subject: [PATCH 53/57] added _forcereassign = $true for halo PSA if no agents are available --- Modules/CippExtensions/Private/New-HaloPSATicket.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 b/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 index 5e14f3e1e80d..16b4cef697f8 100644 --- a/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 +++ b/Modules/CippExtensions/Private/New-HaloPSATicket.ps1 @@ -18,6 +18,7 @@ function New-HaloPSATicket { lookupdisplay = 'Enter Details Manually' } client_id = ($client | Select-Object -Last 1) + _forcereassign = $true site_id = $null user_name = $null reportedby = $null From a730588840ff97827a5d10a9d047a75650f8b585 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 15 Apr 2024 12:07:26 +0200 Subject: [PATCH 54/57] remove host.json info --- host.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/host.json b/host.json index b6359ce3214e..b1667f1e9bbb 100644 --- a/host.json +++ b/host.json @@ -11,10 +11,6 @@ "extensions": { "queues": { "maxDequeueCount": 1 - }, - "durableTask": { - "maxConcurrentActivityFunctions": 10, - "maxConcurrentOrchestratorFunctions": 10 } } } From f15a4bd4e2cade0fd0f3413760bed0b55c2a2e84 Mon Sep 17 00:00:00 2001 From: Woody Date: Mon, 15 Apr 2024 21:27:31 -0400 Subject: [PATCH 55/57] Add Tenant ID to output object in Invoke-ListTenantDetails --- .../Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 index 85e3330926c0..9aa191e2e44f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Tenant/Invoke-ListTenantDetails.ps1 @@ -14,13 +14,14 @@ Function Invoke-ListTenantDetails { try { $tenantfilter = $Request.Query.TenantFilter - $org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $tenantfilter | Select-Object displayName, city, country, countryLetterCode, street, state, postalCode, + $org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $tenantfilter | Select-Object displayName, id, city, country, countryLetterCode, street, state, postalCode, @{ Name = 'businessPhones'; Expression = { $_.businessPhones -join ', ' } }, @{ Name = 'technicalNotificationMails'; Expression = { $_.technicalNotificationMails -join ', ' } }, tenantType, createdDateTime, onPremisesLastPasswordSyncDateTime, onPremisesLastSyncDateTime, onPremisesSyncEnabled, assignedPlans } catch { $org = [PSCustomObject]@{ displayName = 'Error loading tenant' + id = '' city = '' country = '' countryLetterCode = '' From 9fb0d67a5c4fe2ceaecf47dc2ac1d5b946b25a19 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 16 Apr 2024 10:37:42 +0200 Subject: [PATCH 56/57] Fixed Tenant retrieval and caching --- .../Public/GraphHelper/Get-Tenants.ps1 | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 index 2cb51e4d929a..02196d6f58e4 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-Tenants.ps1 @@ -10,7 +10,8 @@ function Get-Tenants { [switch]$IncludeAll, [switch]$IncludeErrors, [switch]$SkipDomains, - [switch]$TriggerRefresh + [switch]$TriggerRefresh, + [switch]$CleanOld ) $TenantsTable = Get-CippTable -tablename 'Tenants' @@ -34,6 +35,22 @@ function Get-Tenants { $BuildRequired = $true } + if ($CleanOld) { + $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')&`$select=customer,autoExtendDuration,endDateTime" -NoAuthCheck:$true + $GDAPList = foreach ($Relationship in $GDAPRelationships) { + [PSCustomObject]@{ + customerId = $Relationship.customer.tenantId + displayName = $Relationship.customer.displayName + autoExtend = ($Relationship.autoExtendDuration -ne 'PT0S') + relationshipEnd = $Relationship.endDateTime + } + } + $CurrentTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq false" + $CurrentTenants | Where-Object { $_.customerId -notin $GDAPList.customerId } | ForEach-Object { + Remove-AzDataTableEntity @TenantsTable -Entity $_ + } + } + if ($BuildRequired -or $TriggerRefresh.IsPresent) { #get the full list of tenants $GDAPRelationships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active' and not startsWith(displayName,'MLT_')&`$select=customer,autoExtendDuration,endDateTime" -NoAuthCheck:$true @@ -45,16 +62,15 @@ function Get-Tenants { relationshipEnd = $Relationship.endDateTime } } + $ActiveRelationships = $GDAPList | Where-Object { $_.customerId -notin $SkipListCache.customerId } - $TenantList = $ActiveRelationships | Group-Object -Property customerId | ForEach-Object -Parallel { + $TenantList = $ActiveRelationships | Group-Object -Property customerId | ForEach-Object { Write-Host "Processing $($_.Name) to add to tenant list." - Import-Module CIPPCore - Import-Module AzBobbyTables - $ExistingTenantInfo = Get-CIPPAzDataTableEntity @using:TenantsTable -Filter "PartitionKey eq 'Tenants' and RowKey eq '$($_.Name)'" - if ($ExistingTenantInfo -and $ExistingInfo.RequiresRefresh -eq $false) { + $ExistingTenantInfo = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and RowKey eq '$($_.Name)'" + if ($ExistingTenantInfo -and $ExistingTenantInfo.RequiresRefresh -eq $false) { Write-Host 'Existing tenant found. We already have it cached, skipping.' $ExistingTenantInfo - continue + return } $LatestRelationship = $_.Group | Sort-Object -Property relationshipEnd | Select-Object -Last 1 $AutoExtend = ($_.Group | Where-Object { $_.autoExtend -eq $true } | Measure-Object).Count -gt 0 @@ -75,7 +91,6 @@ function Get-Tenants { } catch { Write-LogMessage -API 'Get-Tenants' -message "Tried adding $($LatestRelationship.customerId) to tenant list but failed to get domains - $($_.Exception.Message)" -level 'Critical' - } } @@ -120,17 +135,17 @@ function Get-Tenants { }) | Out-Null } foreach ($Tenant in $TenantList) { - if ($Tenant.defaultDomainName -eq 'Invalid' -or !$Tenant.defaultDomainName) { continue } + if ($Tenant.defaultDomainName -eq 'Invalid' -or !$Tenant.defaultDomainName) { + Write-LogMessage -API 'Get-Tenants' -message "We're skipping $($Tenant.displayName) as it has an invalid default domain name. Something is up with this instance." -level 'Critical' + continue + } $IncludedTenantsCache.Add($Tenant) | Out-Null } - } - - if ($IncludedTenantsCache) { - Add-CIPPAzDataTableEntity @TenantsTable -Entity $IncludedTenantsCache -Force - $CurrentTenants = Get-CIPPAzDataTableEntity @TenantsTable -Filter "PartitionKey eq 'Tenants' and Excluded eq false" - $CurrentTenants | Where-Object { $_.customerId -notin $IncludedTenantsCache.customerId } | ForEach-Object { - Remove-AzDataTableEntity @TenantsTable -Entity $_ + if ($IncludedTenantsCache) { + Add-CIPPAzDataTableEntity @TenantsTable -Entity $IncludedTenantsCache -Force | Out-Null } } + + return ($IncludedTenantsCache | Where-Object { $null -ne $_.defaultDomainName -and ($_.defaultDomainName -notmatch 'Domain Error' -or $IncludeAll.IsPresent) } | Sort-Object -Property displayName) } From 7623f9c65aa0b79532f4bcd796c0f7a4fc14daf8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 16 Apr 2024 10:39:11 +0200 Subject: [PATCH 57/57] Tenant cache --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index 9af9a6a81c7e..d2ff458a0121 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.5.2 \ No newline at end of file +5.5.3 \ No newline at end of file