From 009d575cb26914c1edf2db2154daaf4200258782 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 12 Apr 2024 12:23:43 -0400 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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 05/14] 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 06/14] 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 07/14] 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 08/14] 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 09/14] 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 10/14] 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 11/14] 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 12/14] 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 13/14] 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 14/14] 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