From 79cadeeeeb2b6357aba23a6aaf1266aca9c0b1cc Mon Sep 17 00:00:00 2001 From: lsmith090 <47199231+lsmith090@users.noreply.github.com> Date: Fri, 2 Aug 2024 16:54:34 -0400 Subject: [PATCH 01/52] Update language in log events --- .../Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 index 6d9a017544c3..b35aa3f869b1 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSPEmailAttestation.ps1 @@ -38,7 +38,7 @@ function Invoke-CIPPStandardSPEmailAttestation { if ($Settings.remediate -eq $true) { if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Sharepoint reauthentication with verification code is already restriction.' -Sev Info + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Sharepoint reauthentication with verification code is already restricted.' -Sev Info } else { $Properties = @{ EmailAttestationReAuthDays = $Settings.Days @@ -57,9 +57,9 @@ function Invoke-CIPPStandardSPEmailAttestation { if ($Settings.alert -eq $true) { if ($StateIsCorrect -eq $true) { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Reauthentication with verification code is restriction' -Sev Info + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Reauthentication with verification code is restricted.' -Sev Info } else { - Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Reauthentication with verification code is not restricted' -Sev Alert + Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Reauthentication with verification code is not restricted.' -Sev Alert } } From 283fc245eacfc9d4423e58c0d09cf37b34283c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 7 Aug 2024 17:58:52 +0200 Subject: [PATCH 02/52] Explicitly set to off --- .../Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 index f18466b62da1..39dfb1cdfdf8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardEXODisableAutoForwarding.ps1 @@ -32,7 +32,7 @@ function Invoke-CIPPStandardEXODisableAutoForwarding { param($Tenant, $Settings) $CurrentInfo = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-HostedOutboundSpamFilterPolicy' -cmdparams @{Identity = 'Default' } -useSystemMailbox $true - $StateIsCorrect = $CurrentInfo.AutoForwardingMode -eq 'Off' -or $CurrentInfo.AutoForwardingMode -eq 'Automatic' + $StateIsCorrect = $CurrentInfo.AutoForwardingMode -eq 'Off' If ($Settings.remediate -eq $true) { Write-Host 'Time to remediate!' From d2bcc59ee2aaafd48e1654c5ea6662572b9085d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Wed, 7 Aug 2024 18:09:39 +0200 Subject: [PATCH 03/52] Logging and update comment --- .../Invoke-CIPPStandardAntiPhishPolicy.ps1 | 1 + .../Standards/Invoke-CIPPStandardBranding.ps1 | 2 +- ...voke-CIPPStandardDeletedUserRentention.ps1 | 17 ++++++++-------- ...IPPStandardDisableSharePointLegacyAuth.ps1 | 1 + ...Invoke-CIPPStandardMalwareFilterPolicy.ps1 | 1 + .../Invoke-CIPPStandardOauthConsentLowSec.ps1 | 1 + .../Invoke-CIPPStandardSpamFilterPolicy.ps1 | 20 +++++++++---------- ...e-CIPPStandardTeamsExternalFileSharing.ps1 | 12 +++++------ 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index 845587d9330d..156ad1eb2c60 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -21,6 +21,7 @@ function Invoke-CIPPStandardAntiPhishPolicy { "mdo_phisspamacation" "mdo_spam_notifications_only_for_admins" "mdo_antiphishingpolicies" + "mdo_phishthresholdlevel" ADDEDCOMPONENT {"type":"number","label":"Phishing email threshold. (Default 1)","name":"standards.AntiPhishPolicy.PhishThresholdLevel","default":1} {"type":"boolean","label":"Show first contact safety tip","name":"standards.AntiPhishPolicy.EnableFirstContactSafetyTips","default":true} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 index 605256e64581..eaabf85b9e1f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardBranding.ps1 @@ -18,7 +18,7 @@ function Invoke-CIPPStandardBranding { {"type":"input","name":"standards.Branding.signInPageText","label":"Sign-in page text"} {"type":"input","name":"standards.Branding.usernameHintText","label":"Username hint Text"} {"type":"boolean","name":"standards.Branding.hideAccountResetCredentials","label":"Hide self-service password reset"} - {"type":"Select","label":"Visual Template","name":"standards.Branding.layoutTemplateType","values":[{"label":"Full-screen background","value":"default"},{"label":"Parial-screen background","value":"verticalSplit"}]} + {"type":"Select","label":"Visual Template","name":"standards.Branding.layoutTemplateType","values":[{"label":"Full-screen background","value":"default"},{"label":"Partial-screen background","value":"verticalSplit"}]} {"type":"boolean","name":"standards.Branding.isHeaderShown","label":"Show header"} {"type":"boolean","name":"standards.Branding.isFooterShown","label":"Show footer"} IMPACT diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 index 88d8455b08fe..8826cd4aa0d8 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDeletedUserRentention.ps1 @@ -7,15 +7,15 @@ function Invoke-CIPPStandardDeletedUserRentention { .SYNOPSIS (Label) Set deleted user retention time in OneDrive .DESCRIPTION - (Helptext) Sets the retention period for deleted users OneDrive to the specified number of years. The default is 1 year. - (DocsDescription) When a OneDrive user gets deleted, the personal SharePoint site is saved for selected time in years and data can be retrieved from it. + (Helptext) Sets the retention period for deleted users OneDrive to the specified period of time. The default is 30 days. + (DocsDescription) When a OneDrive user gets deleted, the personal SharePoint site is saved for selected amount of time that data can be retrieved from it. .NOTES CAT SharePoint Standards TAG "lowimpact" ADDEDCOMPONENT - {"type":"Select","name":"standards.DeletedUserRentention.Days","label":"Retention in years (Default 1)","values":[{"label":"1 year","value":"365"},{"label":"2 years","value":"730"},{"label":"3 years","value":"1095"},{"label":"4 years","value":"1460"},{"label":"5 years","value":"1825"},{"label":"6 years","value":"2190"},{"label":"7 years","value":"2555"},{"label":"8 years","value":"2920"},{"label":"9 years","value":"3285"},{"label":"10 years","value":"3650"}]} + {"type":"Select","name":"standards.DeletedUserRentention.Days","label":"Retention time (Default 30 days)","values":[{"label":"30 days","value":"30"},{"label":"90 days","value":"90"},{"label":"1 year","value":"365"},{"label":"2 years","value":"730"},{"label":"3 years","value":"1095"},{"label":"4 years","value":"1460"},{"label":"5 years","value":"1825"},{"label":"6 years","value":"2190"},{"label":"7 years","value":"2555"},{"label":"8 years","value":"2920"},{"label":"9 years","value":"3285"},{"label":"10 years","value":"3650"}]} IMPACT Low Impact POWERSHELLEQUIVALENT @@ -48,7 +48,6 @@ function Invoke-CIPPStandardDeletedUserRentention { } $StateSetCorrectly = if ($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays -eq $WantedState) { $true } else { $false } - $RetentionInYears = $WantedState / 365 If ($Settings.remediate -eq $true) { Write-Host 'Time to remediate' @@ -61,21 +60,21 @@ function Invoke-CIPPStandardDeletedUserRentention { $body = ConvertTo-Json -InputObject $body -Depth 5 -Compress New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/admin/sharepoint/settings' -AsApp $true -Type PATCH -Body $body -ContentType 'application/json' - Write-LogMessage -API 'Standards' -tenant $tenant -message "Set deleted user rentention of OneDrive to $RetentionInYears year(s)" -sev Info + Write-LogMessage -API 'Standards' -tenant $tenant -message "Set deleted user rentention of OneDrive to $WantedState days(s)" -sev Info } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set deleted user rentention of OneDrive to $RetentionInYears year(s). Error: $ErrorMessage" -sev Error + Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to set deleted user rentention of OneDrive to $WantedState days(s). Error: $ErrorMessage" -sev Error } } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is already set to $RetentionInYears year(s)" -sev Info + Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is already set to $WantedState days(s)" -sev Info } } if ($Settings.alert -eq $true) { if ($StateSetCorrectly -eq $true) { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is set to $RetentionInYears year(s)" -sev Info + Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is set to $WantedState days(s)" -sev Info } else { - Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is not set to $RetentionInYears year(s). Value is: $($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays) " -sev Alert + Write-LogMessage -API 'Standards' -tenant $tenant -message "Deleted user rentention of OneDrive is not set to $WantedState days(s). Value is: $($CurrentInfo.deletedUserPersonalSiteRetentionPeriodInDays) day(s)." -sev Alert } } } diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 index c540bb46b37e..b4a1e3a404e9 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardDisableSharePointLegacyAuth.ps1 @@ -15,6 +15,7 @@ function Invoke-CIPPStandardDisableSharePointLegacyAuth { TAG "mediumimpact" "CIS" + "spo_legacy_auth" ADDEDCOMPONENT IMPACT Medium Impact diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 index 6c1de9082aba..db6c3dc8094f 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardMalwareFilterPolicy.ps1 @@ -20,6 +20,7 @@ function Invoke-CIPPStandardMalwareFilterPolicy { "mdo_zapmalware" ADDEDCOMPONENT {"type":"Select","label":"FileTypeAction","name":"standards.MalwareFilterPolicy.FileTypeAction","values":[{"label":"Reject","value":"Reject"},{"label":"Quarantine the message","value":"Quarantine"}]} + {"type":"input","name":"standards.MalwareFilterPolicy.OptionalFileTypes","label":"Optional File Types, Comma separated"} {"type":"Select","label":"QuarantineTag","name":"standards.MalwareFilterPolicy.QuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} {"type":"boolean","label":"Enable Internal Sender Admin Notifications","name":"standards.MalwareFilterPolicy.EnableInternalSenderAdminNotifications"} {"type":"input","name":"standards.MalwareFilterPolicy.InternalSenderAdminAddress","label":"Internal Sender Admin Address"} diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 index b30070a34da5..f26df8750ba4 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardOauthConsentLowSec.ps1 @@ -14,6 +14,7 @@ function Invoke-CIPPStandardOauthConsentLowSec { Entra (AAD) Standards TAG "mediumimpact" + "IntegratedApps" IMPACT Medium Impact POWERSHELLEQUIVALENT diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 index 8ca222457501..8edd604325a7 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardSpamFilterPolicy.ps1 @@ -15,16 +15,16 @@ function Invoke-CIPPStandardSpamFilterPolicy { TAG "mediumimpact" ADDEDCOMPONENT - { "type": "number", "label": "Bulk email threshold (Default 7)", "name": "standards.SpamFilterPolicy.BulkThreshold", "default": 7 } - { "type": "Select", "label": "Spam Action", "name": "standards.SpamFilterPolicy.SpamAction", "values": [ { "label": "Move message to Junk Email folder", "value": "MoveToJmf" }, { "label": "Quarantine the message", "value": "Quarantine" } ] } - { "type": "Select", "label": "Spam Quarantine Tag", "name": "standards.SpamFilterPolicy.SpamQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } - { "type": "Select", "label": "High Confidence Spam Action", "name": "standards.SpamFilterPolicy.HighConfidenceSpamAction", "values": [ { "label": "Quarantine the message", "value": "Quarantine" }, { "label": "Move message to Junk Email folder", "value": "MoveToJmf" } ] } - { "type": "Select", "label": "High Confidence Spam Quarantine Tag", "name": "standards.SpamFilterPolicy.HighConfidenceSpamQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } - { "type": "Select", "label": "Bulk Spam Action", "name": "standards.SpamFilterPolicy.BulkSpamAction", "values": [ { "label": "Move message to Junk Email folder", "value": "MoveToJmf" }, { "label": "Quarantine the message", "value": "Quarantine" } ] } - { "type": "Select", "label": "Bulk Quarantine Tag", "name": "standards.SpamFilterPolicy.BulkQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } - { "type": "Select", "label": "Phish Spam Action", "name": "standards.SpamFilterPolicy.PhishSpamAction", "values": [ { "label": "Quarantine the message", "value": "Quarantine" }, { "label": "Move message to Junk Email folder", "value": "MoveToJmf" } ] } - { "type": "Select", "label": "Phish Quarantine Tag", "name": "standards.SpamFilterPolicy.PhishQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } - { "type": "Select", "label": "High Confidence Phish Quarantine Tag", "name": "standards.SpamFilterPolicy.HighConfidencePhishQuarantineTag", "values": [ { "label": "AdminOnlyAccessPolicy", "value": "AdminOnlyAccessPolicy" }, { "label": "DefaultFullAccessPolicy", "value": "DefaultFullAccessPolicy" }, { "label": "DefaultFullAccessWithNotificationPolicy", "value": "DefaultFullAccessWithNotificationPolicy" } ] } + {"type":"number","label":"Bulk email threshold (Default 7)","name":"standards.SpamFilterPolicy.BulkThreshold","default":7} + {"type":"Select","label":"Spam Action","name":"standards.SpamFilterPolicy.SpamAction","values":[{"label":"Quarantine the message","value":"Quarantine"},{"label":"Move message to Junk Email folder","value":"MoveToJmf"}]} + {"type":"Select","label":"Spam Quarantine Tag","name":"standards.SpamFilterPolicy.SpamQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"High Confidence Spam Action","name":"standards.SpamFilterPolicy.HighConfidenceSpamAction","values":[{"label":"Quarantine the message","value":"Quarantine"},{"label":"Move message to Junk Email folder","value":"MoveToJmf"}]} + {"type":"Select","label":"High Confidence Spam Quarantine Tag","name":"standards.SpamFilterPolicy.HighConfidenceSpamQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"Bulk Spam Action","name":"standards.SpamFilterPolicy.BulkSpamAction","values":[{"label":"Quarantine the message","value":"Quarantine"},{"label":"Move message to Junk Email folder","value":"MoveToJmf"}]} + {"type":"Select","label":"Bulk Quarantine Tag","name":"standards.SpamFilterPolicy.BulkQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"Phish Spam Action","name":"standards.SpamFilterPolicy.PhishSpamAction","values":[{"label":"Quarantine the message","value":"Quarantine"},{"label":"Move message to Junk Email folder","value":"MoveToJmf"}]} + {"type":"Select","label":"Phish Quarantine Tag","name":"standards.SpamFilterPolicy.PhishQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} + {"type":"Select","label":"High Confidence Phish Quarantine Tag","name":"standards.SpamFilterPolicy.HighConfidencePhishQuarantineTag","values":[{"label":"AdminOnlyAccessPolicy","value":"AdminOnlyAccessPolicy"},{"label":"DefaultFullAccessPolicy","value":"DefaultFullAccessPolicy"},{"label":"DefaultFullAccessWithNotificationPolicy","value":"DefaultFullAccessWithNotificationPolicy"}]} IMPACT Medium Impact POWERSHELLEQUIVALENT diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 index 97e2059f3687..e6eb52181861 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardTeamsExternalFileSharing.ps1 @@ -15,15 +15,15 @@ Function Invoke-CIPPStandardTeamsExternalFileSharing { TAG "lowimpact" ADDEDCOMPONENT - {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowGoogleDrive","label":"Allow Google Drive","default":false} - {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowShareFile","label":"Allow ShareFile","default":false} - {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowBox","label":"Allow Box","default":false} - {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowDropbox","label":"Allow Dropbox","default":false} - {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowEgnyte","label":"Allow Egnyte","default":false} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowGoogleDrive","label":"Allow Google Drive"} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowShareFile","label":"Allow ShareFile"} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowBox","label":"Allow Box"} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowDropBox","label":"Allow Dropbox"} + {"type":"boolean","name":"standards.TeamsExternalFileSharing.AllowEgnyte","label":"Allow Egnyte"} IMPACT Low Impact POWERSHELLEQUIVALENT - Set-CsTeamsClientConfiguration -AllowGoogleDrive \$false -AllowShareFile \$false -AllowBox \$false -AllowDroBbox \$false -AllowEgnyte \$false + Set-CsTeamsClientConfiguration -AllowGoogleDrive \$false -AllowShareFile \$false -AllowBox \$false -AllowDropBox \$false -AllowEgnyte \$false RECOMMENDEDBY "CIS 3.0" UPDATECOMMENTBLOCK From af9ee119949c0e83113be20f7203e3ce7a75448e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 8 Aug 2024 22:33:55 +0200 Subject: [PATCH 04/52] Delete old workflows --- .github/workflows/dev_cippacnqv.yml | 39 ------------------------ .github/workflows/dev_cippckdtz.yml | 30 ------------------ .github/workflows/dev_cipphfjdq.yml | 30 ------------------ .github/workflows/dev_cippopy3o.yml | 30 ------------------ .github/workflows/dev_cipppwrro.yml | 29 ------------------ .github/workflows/dev_cippz6s4d.yml | 30 ------------------ .github/workflows/ninjaone_cipp426ns.yml | 29 ------------------ 7 files changed, 217 deletions(-) delete mode 100644 .github/workflows/dev_cippacnqv.yml delete mode 100644 .github/workflows/dev_cippckdtz.yml delete mode 100644 .github/workflows/dev_cipphfjdq.yml delete mode 100644 .github/workflows/dev_cippopy3o.yml delete mode 100644 .github/workflows/dev_cipppwrro.yml delete mode 100644 .github/workflows/dev_cippz6s4d.yml delete mode 100644 .github/workflows/ninjaone_cipp426ns.yml diff --git a/.github/workflows/dev_cippacnqv.yml b/.github/workflows/dev_cippacnqv.yml deleted file mode 100644 index 67a58b70c7ff..000000000000 --- a/.github/workflows/dev_cippacnqv.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cippacnqv - -on: - push: - branches: - - dev - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - deploy: - runs-on: windows-latest - permissions: - id-token: write #This is required for requesting the JWT - - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v4 - - - name: Login to Azure - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_6085081ED1124B799258E9FF743FF4B9 }} - tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_9BDB2DDBFAFA4BC19C20A58B204BFAF3 }} - subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_02B5224812794971B05EDD557AF2B867 }} - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cippacnqv' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - \ No newline at end of file diff --git a/.github/workflows/dev_cippckdtz.yml b/.github/workflows/dev_cippckdtz.yml deleted file mode 100644 index c81980006ad4..000000000000 --- a/.github/workflows/dev_cippckdtz.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cippckdtz - -on: - push: - branches: - - dev - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - deploy: - runs-on: windows-latest - - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v4 - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cippckdtz' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_726578DA8A7243BF9D82FE123C2F6E7F }} \ No newline at end of file diff --git a/.github/workflows/dev_cipphfjdq.yml b/.github/workflows/dev_cipphfjdq.yml deleted file mode 100644 index 92f528b5e404..000000000000 --- a/.github/workflows/dev_cipphfjdq.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cipphfjdq - -on: - push: - branches: - - dev - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - deploy: - runs-on: windows-latest - - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v4 - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cipphfjdq' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_194504F3E0EE4941A2206C7E71DA4915 }} \ No newline at end of file diff --git a/.github/workflows/dev_cippopy3o.yml b/.github/workflows/dev_cippopy3o.yml deleted file mode 100644 index b4f3617bccdf..000000000000 --- a/.github/workflows/dev_cippopy3o.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cippopy3o - -on: - push: - branches: - - dev - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - deploy: - runs-on: windows-latest - - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v4 - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cippopy3o' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_B5A7200CF39F4853A33349B80D54DC99 }} \ No newline at end of file diff --git a/.github/workflows/dev_cipppwrro.yml b/.github/workflows/dev_cipppwrro.yml deleted file mode 100644 index ad786fd522fe..000000000000 --- a/.github/workflows/dev_cipppwrro.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cipppwrro - -on: - push: - branches: - - dev - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - build-and-deploy: - runs-on: windows-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v2 - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cipppwrro' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_9E109464904540339CA94F283ACE1312 }} diff --git a/.github/workflows/dev_cippz6s4d.yml b/.github/workflows/dev_cippz6s4d.yml deleted file mode 100644 index d58532dffc2f..000000000000 --- a/.github/workflows/dev_cippz6s4d.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cippz6s4d - -on: - push: - branches: - - dev - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - deploy: - runs-on: windows-latest - - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v4 - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cippz6s4d' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_D27E7CF0887F4E4591F3957CCA96F0FD }} \ No newline at end of file diff --git a/.github/workflows/ninjaone_cipp426ns.yml b/.github/workflows/ninjaone_cipp426ns.yml deleted file mode 100644 index 6df7b1bab0e9..000000000000 --- a/.github/workflows/ninjaone_cipp426ns.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Powershell project to Azure Function App - cipp426ns - -on: - push: - branches: - - NinjaOne - workflow_dispatch: - -env: - AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root - -jobs: - build-and-deploy: - runs-on: windows-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v2 - - - name: 'Run Azure Functions Action' - uses: Azure/functions-action@v1 - id: fa - with: - app-name: 'cipp426ns' - slot-name: 'Production' - package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_198DAF92160A4302B5AA0C145DE72796 }} From a8bd43f53567fa287306592a183c63821d580580 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 9 Aug 2024 11:42:28 +0200 Subject: [PATCH 05/52] public phising check change to only alert when receiving request from clone.cipp.app --- .../Invoke-PublicPhishingCheck.ps1 | 30 ++++--------------- Modules/CippEntrypoints/CippEntrypoints.psm1 | 1 + 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 index 105bdf0df0d8..19d780a20ddc 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 @@ -7,38 +7,18 @@ Function Invoke-PublicPhishingCheck { #> [CmdletBinding()] param($Request, $TriggerMetadata) - Write-Host ($request | ConvertTo-Json) - - # List valid referers - $validList = @( - 'https://login.microsoftonline.com', - 'https://login.microsoft.net', - 'https://login.microsoft.com', - 'https://autologon.microsoftazuread-sso.com', - 'https://tasks.office.com', - 'https://login.windows.net' - ) - - $matchedUrls = $validList | Where-Object { ([uri]$_).Host -in ([uri]$($request.headers.Referer)).Host } - - if ($matchedUrls) { - Write-Host 'Not being Phished, no issue' - } else { - $bytes = [Convert]::FromBase64String('iVBORw0KGgoAAAANSUhEUgAAAbEAAAFUCAIAAAAlO5XXAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAALCCSURBVHhe7X0HoCVZVW2/9zr36zQ9OTLDEERRklkx54AR9ZsjoIjpGwBBFBWzoKKIfFHMqKgfRBQUUD5iQJSkwDA5MDOdu18O96+w965z7+tmhmGGSWd13XN2WHufU6eq9q0b+r6p0Useuqmjo6OjQ5iOvqOjo6Oj18SOjo6OFr0mdnR0dAzoNbGjo6NjQK+JHR0dHQN6Tezo6OgY0GtiR0dHx4BeEzs6OjoG9JrY0dHRMaDXxI6Ojo4BvSZ2dHR0DOg1saOjo2NAr4kdHR0dA3pN7Ojo6BjQa2JHR0fHgF4TOzo6Ogb0mtjR0dExoNfEjo6OjgG9JnZ0dHQM6DWxo6OjIzHqNbGjo+N+jtGmTetoRxR6Tezo6Lg/guVvSi0ewjpUlsVeEzs6Ou4HcPlDi1tC3hViW6ewNjVY1Paa2NHRcZ8Ey56b3OJOUNtIN4airdvoijnqNbGjo+M+BJc51jjdAFJgpVPJQ/mDqiLoV8q8Q5yKMLv6584dHR33ZriiZe1j4VOZYysLqx66NE4w8YjKOJB7Tezo6Li3oaob7/hcy7Sx2JWcN4OwrFm1ME4rMjfWzV4TOzo67iWIMqeK5uqGB9Vsw7s+/upYTGwW4nW0+eUSX22viR0dHfdMqHKpV8GCgFfKKmR0aQs7NtU2yGuwu6yt0w7II1m6Q9ooNsoMYb3fJ3Z0dNxzoBoVBcs3eqxxUHXrR0HtGkoYSpsI0Ckkn0lUHMPoSofWRiq0OI9eLOd9pY39/cSOjo57AlieXKeoDDULYLWS4LqGlgSUNqvYUNQUnnWNRruCAAEElVFuolUbriD3mtjR0XF3wDWIm6uYChketLhstVsSvBWHeWxXICS6tNEChwS2qKH6kraUqIMRVRxuvSZ2dHR8SODKFa1qUCFKmD45AcMyKPWSlq5ptt6cwXZu+T6jiprUlKliAxk1FOUuP34hWXeXYDCVaGp7Tezo6LjroHLDDQ+0vjVDKwvlEetgFClFWPWLWdPCpbu8iLJX2QDnYQgDxDG5Kp35NXQaTYgRTWbh7Ojo6LjzMBQdtK41KFIqPZRVyOCKOgVVluCjyxSDUXbwaUSjnOViHmzTyfeI5mgg2ykrZ4wiOTZPAEaY+3dxOjo6PkhMuaCoRcWhgKrUtJum+Emx1fCmi2/zqd7RBaMLpQjORg6YCNetH4udQmyPPMl0nlBxB1pl1K1rn5Lz3jBDaHQqbP1z546OjjuAKjSsL6p3/oEZSFFligALFaqAK5GI8opGb5YzchyohCTYZRmVEbeElhXLljGDChc6RKFlEk0vOJpbxHpzNRSHgf39xI6OjtsP1hTVEbaqWaHabouLTlaZ4NjuAiQBJocwKmtZ1C9ZJHHQ8PqmD4TKY75kEIbX5jKGHYJvRc1X7eaOKDkJIjOzjGv9tXNHR8fpgDLBtopLtq5E3qqasPToZq0KDQVnKKNkC21Oy3bHq9oslN7CYjWj0FGusdKOafhDauhVbUXnBljFXkAo2ZPP/wTT0dHRYahsoHG5icKkosPykZXOLYUqcChDVWXqM2Kp2CBRSDJfbmcho9fGad3rqai1sRCoglllMUO4eYatJd9G9C2hLU7oPDBZNo0GTNhz6/eJHR0dRlQHbKoUrBGoHU0ZclWKsiImrDRKpl00mlVffAcHAQmDjCbTOXNtzCkCHhiFFpc2qA5MQoQ0EwuLZJuitormyZBvIfNHiO9t09K/i9PRcb+Ea5Y2FoJqXX2wqYui5lKCTWq0Uv25CjaSFYsHU7mVS5kosE7pdoyjOHOSsQFFpgX8/Ooiw6v1LZ7GhYAHVYdIgj1ypp3DeXNsbsOIaVLTa2JHx/0JLgHcJKMooGvrCGtWuibINqIDBy3sbGWxC5YgnCqtW4CyvihDrzl+nQumX3QrSjmUUy5mk+zaCj0ymyaBLg1kvmPtbWXkMQFtlXVuSNjfT+zouI9DhSOKglrfZMkjb9YmVAfT0Na9VdURgMZ0kZwtqwmEZMbNHewKc5Q3MmHSDSBa+sWhN/OQIAsE2qXAa9k5Kee07UUgwAw1H9OqdGpKnBuJEhTlcZkEDdT+fmJHx30QvsJ1tbMcqK3NRYpGlw9ZIbN1NATVFDMppEwvihE2FxTY1TJVDkeOvIAtHstDMI9DxDSBW1XDSuXRFcgRJTNE9mgzAx4Ml9GtXQz0raWM3FQZ2zl4aLv6/3fu6LjvoK75qFNZs9AN1zyMfpXaFo7yWlXJ820jVZOhgiCZDo/XvoCVwJ87rDyisZERgjeWJAsqRlGz/GafZshJmuNUGhoSm6bUUshJ1qBhlLveQMSjaF4WKHQ1n4+zZZJeEzs67s3gxYwLvClDVLX5Itd1npY08pVjU5u8oZSYBiXqpmUTlIe0TEXBxUs5EWILWlgdhSFQ+Fz7IoMiKafATYEACd5q2oiFnHOz3eG0eCARIDhDbdwLeZkfQrM5YcjOH5PsNbGj414CXL1uY3NRSIHeLC6WbRxcvvJbY8rc4IViGvpMZdk11Hy7gubYoaCQYzseFFT1pCdHZAe6jNrPhJkZne1o0cHCPCrTAz8Hiihs6cWj7NE2hIhKlclrPpxmr4kdHfdk6DL19YwOV7gvXWz+fINGMCSb5o3GIrsumKMkdKYXD8q+pRq3uKLF3VZVFlUQGys/Q1KGi7eHVk3WWCSbpjlAYKuyCIUq+xQU4iE4E02ek3QqpTW7hmCLbH67UN5YLtNMkGqL8zCnXkGTQ7HXxI6Oex58AfOK9U9gqRxEIYBdVzsFFRRe4L7CIaTF5SCM423UFwgVhVy2SOVdoThkWsiN/GyhQqKQfGeg3YI2PMqO1vxhc1pxADNZ1JAwyXiE7FiHSOATgypabEWArMlDgp00W0wQOZgg2GtLr4kdHfccxBWLh1uo+X09X6+4ekuN67/KIjZXCgmASwB0W0oGB5tyyN5UBOQ06JLKTeXYUVV0QlXRYZJ8jUyCp5Ec5lHrbTBqXMairRtAuQAIkEwAM8q0vObAjkpHTptHo3NNJAwJLatlBq+MZKpoc537b4V1dNyd4DXpi1lXpq9VtL7+YTEHAooC7558zZspO4uRBG/mD2UiaxlVbR6CglTyMWLOgVt55WY4LCZkrDyZ2UOo5e2YBJMBJkcHu+YZm5LgQUFTZQZsssRwVb/Mx5aLANCo/BBYLoupFh2FZrZVDS2zAnqeymwXHhD6/3fu6LgbwCtTW1y0LgRyULWlaYPmEMeqIkDilS+jtzWlcglwLLOmik1WblSTaQ5pVdok+yUnN9jTSKZTWXAGyZHHat55MVb3dFAhMCRltppzZPZw8lKQ14JSBhzFEBsdLoGWGkhjRes7SjYxOumyQEXHENv7fWJHx10NX5O8ArXxusWWF2Rcw2jhkmxXbDBO648ryRjZsK3H/REUh1gYCNqYWWktu40bK09ARhas5NhCWR0a5vSHLZI9SW6VNquny1wIsCAkN1gdztZGyRBUlIbNSTyKCUGWQKN4zk+m0taswljzgT1pdjkWi+CVpxHknG3/jKWj464CLzlddbgI60Ll5UdDXJB2iS1j0oKgyxWt7SawbWTaTVMS05hEAh682iXTLqYJtEBWfrvhssDGFhlcUzyHCNSUYmtV87FpPia7+pQLNLvsDb4FJaGMmeRrbXoRDruTSHB+E7BhCHQ1AQgy5LgK557mNPBgHtGYB+rw0n5q9JKH0tHR0fHBgxezrrS4LKf0t0pgTEv6dfmVYq9pVqHpCg+j5Cld0nhQppkCLYKNVtk63JDDRsged5gqG9lRHjyEIG4Injwh6zBbp2qpZfS+p2onRXst0xyqgbIVUIZwZAgEjIV1cDkrDFqTKsK9R7JK02K2rnKo7587d3TcccQV5XsZ33H4Ew9dmdws6JaEZN0E8R5Hgr1xy6Prk0KG+6aGsmMhO7k4EwI2EsTkfZkE5yTBw8lCQTkHo0g0gu9AqTQ6ecrclCHmBlVvGgLBtDf3Lt6OzFjAdqo5E/KtaoOvDYktVc4kW8e6pWo5d3nYENvYOdwEJ+eAtJD7+4kdHR8gcNn4WnJd8JUpE1UY00Kj3qqzbC9d9eEDNl2H3mCEKVw2Sh7ICCdJxrySQaMFqvmi0qWxSMjNcnkVFqMMZdRGNA4XAXAdcUIT4AqmE1q1BZtvxOoFaQ5acuviSkqhS3K5wLRMwQRtNuLhgcJbHPPrw2UN533BxqhMyBYjalAz++fOHR23C7yU8nJlBcGmqxrwxYYrKq46e3l1sSBC8uVHjhzMo2uSm2W1TmJCWGorl6Jal/MMsgPRSrexbSl4MpKrZIddAjrLgGm0SLWdWwYCGJRt2gHm1144PAgqTKTl6JTRmuxw55clkpuTa1IhXiUIOBxOW0wAAjcPJ5eN8PrwAU5IDrLJRHK/T+zo2AhdVrpydMHEdZXlgObmYqvq4Erka5JRcusyCybtuozjutWlWN4hIXONBbYbjCF7CCZqMuSIvuDpcip73WYtMGwPl1KxUOYodsHC4SBDKBoIUqGYhq2iLJTXRgq1XNg0T1IkOGG5aMxYdB5UooyNBbDsUTxuqXZxF2SB4qXDWgHkwJXe9f4ZS0dHABdnXmNsLfmKlYhLizAtr2RpkuXhFS7dRsJqyy+OBWTDrYkF0QgT8FB5MujFVauqYQxCpip+uKzWiLKUjKj4JCds7ILTWNiImZpmAUl1hPlsVRQs06g4VjO5ZQBNTDURGSB59yFpBylqubykwVHnPULLWdlusgs0nLkXaJ3Emc0MeDjDCUXe1F87d9yfwcsgt7gPwiYdF4xlvtSCqivKFl+ltviFmAkMh+K2+JmnbnlIboZjNr/I1XVuAlqoaJ3fqjkV5YRB9gTSiw0Ijo1oaybJh2QyZc8KnCwfMDqnZUT5bhFTgmkYS0N4FHiBWBOmJxwLtV0re9ECjJKFLVJJ9lsT5JisaXDasJijiuYkVjkcFFvE8XwqiV0OYUIYZYeMuWWqXhM77mfwZTDR+rKhN7e4chwCwUwL4tuCR1xLslDFlvWCtOadfraOlYqOE/DnsxJ4JTvKluSbzPyVB7HmqDTQmEmcn5XLJU95OCuncpsDVf3yHCCBxgKhKKd2FJ0aFJLDza/XpOR4DjTLa7tDyg5jjkUjNk8mczpVqM6Aze8J5EDRKtz7FZacDI1ZNAG4KtbGEDxhedlS7a+dO+4H4PmPR5aPgCRfxvDg8qiXh7yMcd34Ja0tiJaRNJvTZSBk7HWoSBBMpkWXHDkwpcUgYWJ0u2zRT+NUcmSkYFcyw5UoNQR0WWLClNMDsEd4tWsvpwETVCgeSAjBk5diCzSsU9rUWhHTRiAmnxb69VKXo6TF46JjFZNgp12WAYeEqgzt6BjIx9HzrDY4ZKQgoKfX0xO40r0mdtyH0V6xgAW0w2Uji0nlDZhjfqYKY7Yy0WcmMIRLHsum3nFAeEu3RSoalgYBxqCIltxBACyjbZJpwhoUOeOyh1toixHbHM9M132qmaEtNAWoLkmUxzzK35Q8y4xuBAd4ks5iOluPJdDpyUNujDbZLhJb5x+ySfaIYVFboxsgODPN/T6x4z6GOtV9ovMsr+swHCEMF0V7SaQdra4QJcm7D9sNE6pyGRFVNOmUU2hnUq0tE4Eel6Kvc4ECeJY3tKfIrJ11KhuB8J6qWATNI1a6qolpS/MgAJZrhuHCAuE+N3U2TVr3NRam5BZjuS6TMEGWtXIMbQZSbFYM1JAhoE1azZMWedFp0F4TO+7VyDPeF4nEOO9D1PWDcz19zcUA+HoQfLGFakJeLXEvQ0PAZG6YQCanVhez5sBrz3w8WloVUylsxPe+mNmmdaqSbTfNeZzQSahqGkEbegmylwWA7Hlu9FqI0WHXmwmtNwLbQQ2RSPCeWgozu9oLWzgEWtOcM8TAINghwR80B012NOyVocKR3AtLu/PbJ36QigxWr4kd9y7oZM6TO2xptGylBF0DFH0BFS0DrA40PhhLeyVFLzsFqeYPgtrhapTFhUnNAMitClBVmC9da2wzsghoY1Ag5QgSIZgCDW2dsidnG9miV6zUQo3uFurghaS06xaS4N23XOzW0hJqbt7rpCc8z7Jj9Dx8QB0L08hp4i2SIhqLcoMis6nY2rv+2rnj3gGcsjr7eZb7avF53LRGyGijyXPdyFhfbzQkP8hNXTvlrVYxAQgxpSS0LsMCs0mAyvwe3alylGFijlHbVjR7mUFOz9NcwuESw6hu+PKjbREcKiEXzRqL6+zkVnOgNoRCKdo1Z2WnDGxtdGev7NUjPzNjq9vPyEJ4ntDAGVQd62Al2RFubYzimTntQh4sJrMpDzAWWEKviR33ZAxXlE5YNlJ9/vok5nWF8z7tNo5dPGlMncLYZWOzvbBM847SGUQKDkBBxnBp6InLjAJ5gRIppMI+wzlPhzuDZTHRxzyzpBIghCRAQRIJUcQdUFPKdYDaThWIGiEXGW7Sol6qJyktanT4crYak8S69WOj/ZLA1oxm9MGVKLlc4HIFMg8w8JXKHGmTLrcRm4I3z8WwQKOZ6HtN7LiHACdlXHLNuUtZp2pcS5YlFqiKbTtF3SzEuV+XdzJptjEugyAWDQhvZrClSgOnZEmgy4L4lgNS2ODRzAQS5wC+6u+Q1gIeThWODMwkHsgrll8Zkl2E0G1R8lhDO0WgQTo5ttMUXtql0pEzRNvm58Fa11eUHJsCyTlzEstCwwCoZRnCpQKTrtR9hjibPUA4awjtFFUFovFTC2IZqLAa0TEOVNtrYsfdDZ+ccdaq9QkKhOBzPc/g8gKM1ak/KcCnRLS4CGojSjnlO032ZCo8IMR/VkvCWDZAnBIcW2oI4jvQUoVPCDVbI4xqicwJi3PHLmiIMMnHlcww9jJaqAyBmiegIgutQswjwUNQUatsg6UlaM2JzIyG8zRNfTBhUFRMwHz5gqP1tFotOnpxO29XQ/DQzFlPFCLENLx3TRtfzNSgaBk41Wtix4cePonz8uCjqRFAyHWalgUCzvgkV8tTOhlOC6tP9LBY1VUBG0Oai5+xjgJEUB9ezs11SiZa0oecFDMkjI4yzJFd15ttkc0qIZOjYjJSamLhUovOgU5IjuBRKFSCDUOMPQfAiyYKAfMwqllwhmqV1FP3WwrWHZIjyS415iPVUe1nI4D9HE5kqwWoHsIfiLmS+ogHU0NbgUARFqji2OOWFM9HeuzmxgyagzSj18SODwl8zuWpONYakEPVxckrQh1P3zBz85ltnae4YsAkX7KTsLUKsmgRCGNznZhsC7z2p3WsgthWSaimwF4Xm68ugKn0P16cudIONy/KMzFhZq4kIhBSIcZkGiP7SE1wBdDVvsgezrwzKr4JYVQgXZgzZih+wXYLJpeTghKGpdlZu6KG5rhsAbH9hITMYYRZnCIzG62R1nnh8hzscWt6Ka0XnbNVzmonloscW/pnLB13EeIMk+BuONdlYmNLmNlaQEdZ5zftjk3QKIJjKIgcV2AZGw7asDQXtj0yDwg5Y2NcqwnK0ulVtWXRMDM87IZYCbCTHl3SIGpKvkS9MjGuZDTBrACIjZexUENp8ssYQdHRCAL96a1bP1OqVQ61MnlEt3YZMe3GQjT7ng0BAWYV2CGVVR6Uia92Cx49hnBXu2CCU6A1R8aAFhkEJKdWMTkoMKyenKT0mthxl0BXBc/AONFs5bnoc9QtT+b0oiFX57Hhu6pQFaLevDib0UL1TYfLgfluiSbQye2ibTyQBircHEIh+kAQLLuThXI7kFU0sqDhRViBmja9oZNBWbqHCI7CrYbgXhxusjvWLkLZrHogAwLoLgcD2YTK08D1iEK2fA2rGsrxcw6eG73iscRIIE3ecqFRP4AnQJMBD2d21fPxrRDSMk+IktDInKoUEyimxbCIllPCywvrSpLoNbHjg0ee+nj4nLbFLja2yABAaE9ZuiCNV0DaTZCpskGzc8gpgi3M48AJQW1dG+6dwSpFZFPIQG4IBoWipb8Cx658ZYgE7dCZk3K1eIAjtaqAc1pGR4rywN7e3KF3mavEbCVZtQMyNpOLCdAohwl4OHMxOFyGY3OsBXoh2TJRv6KXUMUxyaGmYGuE5DqERVHtjMJrQYppFmoObtExPx05Q8vjXjRNde41seMOoU41nE3oecLZrkecc7bnmYenfX6AK9mhpkUqStWMn7g4a6XSkuF8WDYzOYYtbNNbxhjY79/XlSyLhZgzZEEettw2uKpjk/OpNoDMkG1MVCw9SistQEEZYpKqZPb6qrYyJJyYlcfSuOQ2QzvUm10YmgdFPhonUimgzUCIYVq0tQvmywhQwEOxwfSUcncI8WGMG0Mp9DaD0htz8SMGAsJeq5T2iQxMa8GjNDtLD9RYq14TO2434qTS+RQnXr78lDic7lB0eulUg08nHDVZIMUZiRNa3XBqNqAqZsgNSNYQlDOhSRBwbfiPxEvXMKkBnpWkrAWAJlBoyZ6AwVD5Yq8zqubgHTEghCwvECo6L4stCiGlaKdaEMtkes3NxENrG3BaPMDxN89bO+AuP5IaBsKj5dhek5QFCmfowMzEKCmDS8IpMjvA/IZAOQOtkoP5meM4OTjn0NlwIHTKE2VRDtoNMQEHYtw0RKDV1qjYXhM7Tg+dOfnsXZbm3PJ53HoDkPSrf3LL4BNOqmkRpfd0UMF0FRCO4KB+hVgZ9HAsABcziGCZBc5utxqo+EDrxHyUlWhpECoNgFlNzk1XIABaVUYO7UDN0AI9slMY66JPbRAMXsO5OOgiSe6swV7yxBCxDkaFeCkkRGZAKx+5TZsYK8QABSnhqjcWQc5feCR8A66ZAENC9fB6ZQD02OjXcJRFY0g4Ys6SIhWHG2wN06JpMkas+AYFu6wqkM+g3hGa8K/XxI4GODN4QTZVwOeKzyGeMe1FZVecTTrV0oGeZnW21QlKOa8lymwICKT4nE4LW1obHkSp1fragzxRC2AvC5JA9K5RrVZJQhYoaAd9Z0GMCzGiBJsLNVXaNVIEAsmnvx20kguVofIUmd5W1oMWcGtP02KauWy9Gpk7OPmmpOGERgyUc5M4rB5gYtDN8d5pziSLatWIM0ruYloOsyaPEX0zyAwio4XdfJMBCuVFo+EgOchwNkJeZpbXtqIhc4wl9JrYofOpTpASdSJSUYs+zstUjfDjUReYOLZLi85GPJTANsKqt5JjIAGqfVUvaCkkM4zqcIrz/ydoxLgSKrVeUw/VwWNZ9QyBsoRGhXl88QyRQYvfhkl+ICcWgnxBkEpZgRW1cQcR6KUIsiMqc9jCWDtFiySr9KokOTD2wjztF1vG0BAjUgxAFiWiINfa6kEM5VIJzWdmtHJ4PoUQa6DM7CyVwYActBLU2W4aDd6XBJOUT7ONdRNngu8+jCD2mni/hq8QI8/R4WRKQISRZ1RyCEt+rQRZJ1x7IlJN2VwKzanpy88hzD/xKkZSqONnMLY4xQseC73Gai8Ag1Gy2FbhVCVDix0R0EcCeFFJbVIbmR0uwXNjkvQmNzoasal4gKxQemm3pGnXPhpDrTFZ6uQ6qCU8DekWQkuZ8ExClFHeiWkbY4FWYah55sC1C9TKaEJoQzsM5EFtzyja841gNOTbiMacZNoLFUlqlTzDyJADOcRk+WVEk/aYkgC118T7E3BO+KTIcwJngs8n2HlyQJWRfp0dpllTr6rRQJTkAz7tJKPhED4dJUNiA45Lg+zsJPvkhuyQ8CZ8jakJlzljJ7Q5WXG4L00qM0UJJpTBIoJvhQDPloK8FhgFNK7y0Zta3TbaNETBILmSlzwM55Bm3ejVRU41o4AIkVdBPDYw2s5GacOlA2fj8J4AVJHNYTJJEeIRTVAIxFgfuoMAF6fqtGH2g7QYVBpaHwLvWhmNEJLtGbLRWOFVeCoERGxOG7qPe7mKAEFKZbDXQihB6zXxvg6dAEQJAM45n0Y+n9rTwmK16OI08kmjc3ooNynAy1YxZcTGIGcotaFZiMwSAQjBURRdKUTTjsWGGGZS/MYbRhHQ89o2Kkl6qSoVBbvwEJ8XapvEWtYLE6sFgk9JRsk0sst7z/H3HAIpg1s5AKt4wMLRMbQm4ZSGZUeFXV2VJ8c6algx/Z6C+bYQCoi1IjvM6Di9UOwZXD43KDZDBGfcAthIcuohiICNhFxeThvQ6LaTL45dsQtNQqvtrlkohL2snECvifc98HxhH+eQtLSpTb0s5QXKaAebPNXa87XNAPA0hYoNduphNKiJzCAHFs3WRnQSsbgVYIcxLrnMEIQmoZN43ytP8kQTBw4yk29/tbSbWb7yyoKmlhdowwmmYIs+xInVE5IVAquPfdYllIHQnPmQzpyZIULbadt+yl3AhpXRDjhJQFJlQCwo1DIDA9HJNRwmx3sHoZclguSykGmtJotGhChUadFB8diCDzrteKQRgEY+LLan3I4SRovyoqlj5310LCBCr4n3CWw8/Dy4ssZpDbl5p4YPnIg+F+teAPD5ZL5OLITTokCgTiaS/G0MnXweV0Hs4iR2WhHk0HCi0ZghEZV2KB69ODSXRX1MgwFqahqSBzva1qV2mFgaCXEYC1FWq5wwWulOLGe0QAjoTBOcgQIeCisXZUzJrtyRU0xJHRpsMa4JbSrAtBpCnGH+DUqFwAlUHs/Hh8xqmJMggSNolLK4i1kBfuUOQW3lz4jAYCRpcA/28bYFLTmivR6F808vwBO+nViSaSE7BTUUxaTB5zOie02894JXrA+qT8G8LOtK5rkuiwEmCY6iW4EwSvCZBLOTBCdhMQzw5Ys+8xFL4wTNszJyoOn2C9XqiwOBOcTUqR0u21saOfUumyGJgRbwQPjglgWPImRyIFxAE8J+QlXLPU3VBMoqKx69ncOY0RZ1cUtoFywlCCVQ2viuXCMbISjDRq9B1QTvVH0yBtRSRFc9Ia7eJIUMqVATzoRhaVQCxsw/MTe2loQSIWDj0uUhphxOCZpGHRoAIkbxofHaOiTImoNpcUQMy4qKOVPrNfHeAx8/HFQ/pfNQ4qC2P5SfoJgHGxgOvFU8ZIEw5JELsJrnR7Sk1ckk3S50YRSBrTOmEbKftyuEootCA7o8hwyv3WFC7KAujzjRHVsEWXyuO8o56JIURE+1iQIvZiIjkzSF3mYmTEt1ZOZwcMazjgJMCwIl2sMIMedgghdhMCYtxo6m2alxGjSubU7DdgACLbIzxHCAD8cEOUTNCitgQbEhZDZy5EWHhudhzscdD5MGIBktmmbCLaCKMj5J02RxYAiecc5BFMVqa1U8nM12GiVFhrJ78WV0lKxS+2cs93D4YBE+eDhsgg8kWh5HnXwB02Rvz362eaZS4bFPH3pnqEtL3iQK48w4X6GLFBb/xxVYYKNPQjMKQNFDVxLJ5lR4cSRKRdsyU4wLRnJwXKI0TxLQ0hEzMUeivJ6DaSQN86kJhEy3G6o0pMVOC7ZYoSAMq+FWAWiqHNPYjiiEpxmdmjllhIAyBOhgt0ecQuWEIIsJbS1zHqiAazRAsgnyomWgstEosmHRmYkmykMzWiQzqaqdUC23qkFZuo0xW6nYhikVPAEz2alFT3ZqJXuSyQG0Mr0m3iNRBy9OLJ0HFHQI0fqUDZqPq576fJag5fmd9jrwFc4gfxkQdp0f8rN1hgFSy06aqZBpDTXsmYrm6MKoh5togTGhTaXOQnEAz98CZy5tIHjmOX/a8WhlNxOrl0Z0FPDw/kKUxUvE9aznDNHaUTifxsVGXsh1UEiTDQgBRlcpWWjMWM/QOR0LhaeB7JWcRonogqaGHvGDCdhFx8CkJcVhkpkcgoczSgAqCoAw0DToMCt4MWenTSaNJiSJrlwl2z3B8EPRKjkzraJRlRcCw81PGYIPmSjJp4PyQKYhkgCIhdJr4j0DeUh8kHB+8IlXh5DQkQ5BPUBBx9IHOOxShiQZFU2bxOcBNgsKZJNzoE3MGFoWd5G5mRWbOq3Nq6ikOWGhtQMQ6yR25hjdhglX3ZhA1ss9CPa7cyVyW6kYq4E8LlWlHVT34ltwgP9fsL3GMFWBYoaEOVWnAnhd51hpIyBji18Msp4EuvKvwbRTcto1tKLZEoL7hhZeCM2Rjc6WZFLOQLbjquEerfeFKhUZk2MU053vZ8lJvn001OhhI0JtXOg4E7vw0OjtNE4xJTZC5XdC0CYuHIPHutfEuwljR0Lg6aKjVK44wIYOZOnsxVeTBQJye7wlGIx1hPI7Cl6cqfUmGq1mpsA8bZLoTyFAagPL4cwTGUJDp/mH6AxGuxeeKraG4CiElByuxgKEMJ6ZPxgDS+gUnJ8uXDASoNJuH2QJ5FDR3OT1JMOsLGwkwKwgqlhhoMooLa6AzfI6ahhOqFQG7DBwkjINs1VUyywjLeNJQm53B5rqPsD52Khp16xMrnXwAkqbnCTlHNrRlt2FkKNYph0WqNq1cOWg9LpvLB43mNgg5zQ8aLgMmFJrXWhqf7NB22vi3YFc/WiHq8vHE02eHK09nUQYLQi8lbDkkJaMh4Ip+Dp0oAVxGNucfCGVrBkGsnZULDsNaopDgp85oVXhJlwUDHG4v/ogRYYABfqSoMxWw4WmuZAo50Bhd4gteqQWAltxihhtEwiBahlSpW5Zuz+g+DBWYEUBkjE9iQEeQfGDrNWgQWTTvKeWmY3u0EONRp420MtiZSPZB7TOIQUOBBkppB1Nw+VGs1y2hJCjm0BBGYJoDht2DE+MGdE6PzZ1bPBAyfbv8WgyXC0dBS+RA+KJBwSfDGkHM1Rq0cqDR6+JHxJgrb30Pk2huLWLbaqUmzPAz9XpEdpjqUPrA19R4ZLqywwqU6U9O9lDDGGYFQTHaqqeNjwQ6GUTLiJGkjEz8CyUkTRxKknEi2nRNAN949ejieJkkFyyCdycnLaA7SElPLeaoUGm7WFQ2qQxM0xYZDnA8V4QG26jAPJht+KPJmgNSwQqqGLQY3Na+DmQrFQ9lpim2+gr3Ebbk8WOxhyIqmTsETmVbQjIHp0mDCGYtlVC8cSShVqAcg4UUBR7s/MIUmyY5rgzcxioyTacGzZmcgOrwZ+lkIkr4wROwoZAIGTvSwszxwbtNfGuQLvucTibQzIcAJqboyVdHiK82PDAk2DWF0KONo/bNBClMk4KYic4gJNEquJAzQserbQg0KJAikloXRRlZKCub/IbZnJlN+xKcA5STSB/oGZgcioVhehEcAZKmon4VkmxDMmryiBFuNxIoJFUhpSXgRAaewGyaTVoue0yOBN2mUo5/eRhGeEUS1BUeGX30yQQLnW2QB7mn/kNEiznBAIVbrsNJiOb3meg0fzkDIJd5uBhWZM3wog5K5XOCFrFatIqsFIZjmUroz3l9zGFHvaSM0lkq8lYlkCXwEWWUU57e028MxALOr6+tOschU4711vGWHvJoqZGe3njsLUHvrH40ioOm7LbKAtDFU7VjzwtLKiXZSMh7QTSNm/DeQjK68MuBzSB2lmg5gPg1Rn4uDDgzxfKEe6Zl2B7jFjD5QSKbGRJoZ1FQQ42tS/0SRZKADzVmiSJOWgsoLxhtCqhBZOIzwAROBO5mEGzlVWxkDVjyhWi/BIp1IjkJMsykTOUqNYMSjmWkXWNBruEoCe5QtjUxAT3aEHhrKWj8QxN5ugNSmvN5ProSA5XO3TOxEPUMeXDo6CHIC9RuyOLCQPKW/aJscYt1GLNe0384FCHYRAgaaFr3XlE5TYnlz5kS2JFbHuGFa1C0FHNE9QZ0JY3UjZ5sk8ypIgfLJWKNDaNMS3AIJjpieXoNUna87L3VqmgsLzqZsf7K/d4wiAGSgDKBRqiMEjo2SNfXU4c0eMmAahBOQHnafy22GWEBRKioKLVADFK8oKgWPCjNHt0eZM4cGhUCMCslj202RlOSJZfMhq5TGaowwUbzQkzHk3mSGujwmMlxRZFsg8TRM/ELgnRqmGPh36hJ9YlnGxJlOJBB0tlLmMKphlgtnPgqqbXizww8VAWTzVNWtiCLEUwC4BgmSl7TfzAgJXzuVWyj4HXVBa0lH0kIMlYF6pbCqJx86FtkkAcDiSpbOlKQpHpSTV1iSZkiZRtkCkkbeDUGaaEkNs50CKaND2sKwNRgswpDkSDE9P1V0OgBThWksjJMNsq3DPkVrMVBkumhclpsFFQN8wQcmYL2GuaVHTMhrqQ3rKXVyY2ZFrTkfZwno8JbsOCGSqHQa+MRTNCYNhghDTQyuVwjwjRiwCzhxOGfcdDfMCcIX8mN3h0RCQHQk4ycjZUGnM4mqmEvVquJF4dQJXuSRYBdJ0XghfQ1uY8pJYhiK1bYFuIlDzDctVABmU8cq/DpQ5nTa+JtwEdFCIWDkKsnZBL784nDVBnvF3t4QfCqA12yhkIwUZqZWTDzkKRC2FXC7S0IONQ6zyjakKxG5GCoyDlKJMhJkgE4I0FyX3MuIaTHTl5HZrvhSq+5VIDNVyOBd2vwQnEmNyEeVZo4XVRq+mZEuHWs6e9kYGaITDYRbIKtHwCkg6i5YGWqUuwjHkOBnSKpar5YwKG+4h1fpnSH5ZhWRoXpEFuRhwjtNOTJWS70JttSwoAGzxyl5m8dsHI5GKN29Hm7cJAMBlt1VDY65kpU0CIA6o9ogWiA4MSCK8S+i2biGrSSpHc3088JbjcvpCa92KAuKnRCtKARawrJigEhGD5HM2DR0KSspfdv4ygPGWPkInk6Ta5OBbY+rgC1HU2yGIjCPTILk+SgdQ9Iu2tnLDdcETsnY05umkmhIpW+UsFYPGUuDviF2Jfcg5UZQcshKqOTQ4dcLgmgDZcMvLIeji0VsMZSTAWdwoWKuENvjOgtVGgLJ0EAVoZYwY2ilDhbo0qZIDHlZTMZlBPT1JDUDtk9rSloKFdIfYCrcA0IigfHyab5ImFCx2Wy+8Ey1vMmJKMQYZRFc1GApn9+hqPNMbEXKYjMp2aCTYfsnQSQwLztT7DGrrO2pXzoSaFDTIrijYNbYjWa2IDrVicH4X2ZCVDbZwWbsSfPO206ESenfSGKXsHWs1wAno+eUKkpmykAbKKQtCYsZaDkBZqdNBC1TKbaAEK9LEdplF2NOnyTMJsy7hq9yBIdlTawkuLQ9IOUEhqJbEtzdqdZv25R3lzVOd3DVFyIEccBDm9LJFE9jJGeMnsBg5pZRlP7r2mW8eSLpdjQfkClFOv484QG/MUcqZAjVWhzeiGeyZpmVKDohCP4kHllFddWNJKMrla55qe+UCGABkRSgVaDqRMV7irTwFdO5BWdXjGsjH6QYAUo+TQw7GQkRlsQavCbY6E+3dNjGVCV4sIeLESsXymobHPxxiPOtjhJ5nPmTqDYeGWh5BtHWC0Ges2RhGzJmMXkbMy7XQhtMsHF8Anagm2xwRoIGx0YJuWsOpYzZ8Waslx55AaPfeacrNuEWIXHooKpr1Fy3g1SuKCYtWPmmeOS0Ghda4HwWkzp0mxVg4QGQLvRPwj/rDYKDsF8xNcE6nmjA0nlMwoTd6zogXkPCJUHZ6jhKUGpcGmkGNohXhcGvHIHXG24MvNEKAmTFKQGy3VTAvjUJqzRRdTtSuszd7hAYLtnokAb5TyzOg81iqPe2eO2JactDrERgkAZAbKxEYCDLwepcLLyWzMYFdG3B9ropfDa+GF8Ir4PIDuwwB47bzlkhFUc1mLTChW7mAXp1UBCnhwvJDJsS3lMCqmzVAEB3qsIEDQ817IMGYg904CZe0vCdYh+mzOVGDyPM5Yg6K8JlpuCXHqA002wIFllC1dxWHGlAcxcmDzghfN3soWaiqmQYv0skcshGAHrPHKaZ7JDA/KUdA1Mx+mLcMQJZfHiQXfMMMQ0kK+JDSR1g4PKrg3c7iklTneCkgvLDSqpaD5QNCD8BAtGexKSwKbpCfTi0NDBsZ+SR6GIyV23Bwik5hTvFCFEgy7YkyFD9CzF/baz/fwxHCZUw8344Hm2KE+RHReSaVisvtDTeQONysCYVgO9147e92LbD+8DFeG4WCnl0DCVDZ6Y9wKNEdtzcoWE4E678emLRfHKjk6giLI7Y2hM0NyhnyBWbsAwRYDRu6gTBC46ZxjkloxJ5NiC0MwYTNB81s/HkJeI1LFaReInDkQXGP7q8yRJx7hbTnByB6tR4dn4koGOLr2xQOZKc+gShHNA0llthDpKmbwBciKY1cTY+PVk485ZSFqOEBewDNkpZMQBCVsZ2tQlguCxx0LsQAxJ0M5W9i4Pj5eg5+g3I4oN1VMTv+dzuFjNOhpYSOVsKqtRnFCSaHSqyQ2Gz4rwkgGWxOs+YhEMjnYZDZ4x8cJtDKUIb+AMe+zNRELGouYh7bU2H0RLHv1KeAhYxwPGd0hMEW2kVMoAYBMZuMtpu0AzsX4I7k5ije8bkJHChUx24HKC0EH3Hbk8c0C1JDrBVrz/E/NIemCWojYHAstneY4iYx2mxPkzMlBdZI6ZLDnKLyi9EIyjM4ywZQa5po8CE2IGtHSGBahmO0bXgBFDcS0jYyOfDHGnlHo9g4RsbMmTwyaIXDJ01jAzb2zyeGcpAwZQASt5QteBBvQiqJW2WjXWTRBM0oAOLQI7NWxqZCJmZhc7bAQ9CZRAh6m2SRQ1m7CQ5fnKdjInJINEiw7lVra01KoiZSxnHG2CO7RIjpClDaSye1RKOYQYDLHfaYmaqcEr2mIG06UXAi61IbdS5Zr1BpTTGRCAAL8WsowDjR7NzxfFZ+qHBFiklRzysiDLY3MmlIyFRSC548NOls+CA9EkvJCTc+4YI5MaDwQbWlpQUKIEjQxjzDw61wXQgRT1Qcq16fZO1tIc2A50Di1BXYpQHQFFJHhuXdOZRmoq64sgGW4PHnLRrjUAm2UFTaZLWabk7fAdciJjaGZpGnDTqkHOB/TbLJAtryyka+xUhtgmS0epxrLFgOZYePqZraaDxuGDXxpNFrA4QNqqdvnAAcalIWqXGyYKOW29QGFVK1cCORwUmwBPFU84/Lrijz5Ghce4pOjW29HB8EuyzLKcG+uidiHuNvy/jS7BwxG7KgwHAwvENpcFMeaWWrItkuK46qc4PDwYPOrDw/UusRsgXByLEevxEqu631wDIQcV0NpPrIDFCpnE9gyixxMuQdjEcpeO2J7wqpYzY5I8L47NkZVY5dlg8aBQpTFpliEJNhl2aihaa/RpUOlUQFcMVjEpBuWrMIDGhVMhowPRwEcJxFgsdeHeGzvdAQ9Gc8KYIOHF0dyCLVcgvuweNoQcnckJkxwLxlSqGg9W4dHE63hJaqyQkuR6zh6XMhYMTroh4VCTVtRjqWmqLLAS1oy6UqNbbM48svuWMvZRbZGoOyuXPJ6zlarJWpK1jQxjO5jVzQaB9a9rSbWbhDaFTTsc+dlC2NSQg5gRWCEteFY8DJRzrVjm/WlqozbIDSrrLjxM8DUaAgKnmoJ5auKIK8PM5z2F3EiA5uUoVVI8HW6RGu7aHRbN3/DKVKyYdV2hwJje6rOu+P5MKf2J6YnGsNzAjGlZiZMPT6NWmHKNkqqoQGe306QRvbiONwm5hHBZHNsb/fdiAMB2KU5WGWjnBSbcNqkYhQagyuyo6QSyY99KWOmRe+TzbpzFkqkIPbkEDaWLm+cnxrafAeZAiXeEU7ECshtjqcXUJ4WTAWCbzWM3KOiRp5sB2cxW7VhWBxKuYRgygBLCBmL1nRs2Gt5AhbYWvLyqmXIveLvsdRSDGcqkLtB0QyvhUUI+dtqNOYOM4QRYUTrZwwZ1ErlGcksNKoPb8BnmExM4uHYa6BGRUeLh5CRz725GybzVteqNuQbpmSHmGxNEMmeIGgyzNbsb8FRBkM9XDtPZ+MjVHT0om9SyZ8hAoTwllFhbDKQdnud1m2qRvYE90iBPgThkoWBGcUl9EkP1XYAQvMd+yCrrXE9MeWI8CFWpuDYrQQGB0oVnphAftO+AEJwnAQqhyHZKiJjRCMnwFnh8DXzIVSqPBO65CUhvRQqw2DIPNka2UuwK73hslyLMOHVDOmyCntxcu/YNoy4jjKDPZbdIRwEryFXWG7mlJ9zgGQjbQHLbj3PYapoNc+6dfVkyNHV4SjyOTnqw3B23cP/H4t3wB0rRcpeAogQvCg4O62GBaTk8KFKVLWGNnkpWLYkuU50UnLJyktBbQRK5eqbk+S4VGwvZgJiWSC0E44jZXJlToGuMjqBVO8Xcwr2o6VgxS7TypZ7Z1q9VsqGmT29wcIZKFvz5gPtdotAf7NHDrFaqWiUFAlLlubwcNmY3nBRpKvGNZPaRkFyCMrpRTYqcxDgkqUCsXFlKoOZyhOy4NlCdXg5otdUWzvJGpeirmHWWaMZCxjkzFCBgNNODBo72DDtaQYnHEE5HZUkhMxgP2QMRCMUEcb4IQqSfMUNRpihtEypEyvjUbJkBchp/1OZfGCaQD6iRAsXGgllrJblUkk8ZynB596huefURO6wpouNsm70COrR2jJhtmxMCM4JhWqxsx/OQiHWC75cNYbIXrFo2DcqHr5mrHuhwy65DhgtNBCRk1LO0KeC2qA10/COp5YzFwF8dGw8DWwCe2e2LtjYZsOWE5EsFybDXjJ7hbQuhtSJqM0WM8PaJGFjo+C+FsEq/c4gFR1krwZcGCsl2R0Fr79r7RqtEMcazKYLw4DZOSwYYXSsryJ7JYUstELwJXBuAvcIcIqGD0B2/sErmaJV2cOfOy4PQcFri15Dt66BnJyCtTFbzpxQoPoBJUPgTHTDQeRsY3qesHdHCT00GzHBIUF3JLS3c87ahM1/cMYoAkBZOoeQyITJiTYXyjLTNoSQc5JWaREzkFOliwPdA2oi58ZJhVwqm2ZvIQ27JFCQoj1Je7ktKgYCOEM2tQAEGh2ug9RWSXs5EwV4Dra7TYPaclU2tBUb5uRXqnSgp0FzqOE8N1K8C7BmIICp1utuV1LA6sRUgbIYEMGPnxXRKDGEBbqZn1PL+QD05v145WMrfgjyQqBNmWUjn2vbhJEWYqPai74Z16h5hpqZ0XrylRwNmZKYVpec18oq20LanTlkv6qQamPI5lSSbNEAXjGHAIPg+HHBTIfTos1H3yy36KrgAg6MpwdA/NCa/bKz8tgVqyTE5CGlgN5n0cDJlsZcbXQ1Z8JqY7AUnOYYBcT3KMHJDIbt7TSKwJWpgzK4FZJSxBYNrVTIzAEZLaTmVU6FO5D93VATPQ/NWxo3TMjgleNpp5sT9YHPXWKs5dgNIhLYrgyA5ZaJwDrJbGdCtcEWQI4o6xAt1fraksM4p2flCTfOsBhU5UK4qzBpmh4pGQh1LJszuJXAPi30OnU0Aco2ttmEogXHeVI8hV2CVXpq0USz2F5UbmWN2I1Dl8TenFITYxZ0HteaE2oFY3poaqDMFpmLYEtI6UJfgYCZDlE7hJtTySGaE/5AGCu5HibQ3O5F0w1k0NTVuZq8mAk1CFjbfI2SfoJyDk2I70A1jSyaVTNplZ1o5gmQWSW4jfBYlFJNVwwkOy3uBMppB6hVoOYAhSP6RrJGVR8EIALUV7bMYyRFMCdNzoMmyg4DPyQ1sYY0MB9MBU/FdORuF7xqbOS1hRnwsNHXgDS25tgLpFoHr8ZqQ2jMFanjlP2AOGzlcBIHcvk0H3ltAVwXaGEToJxRhoemWjmVzKtC1YQhgoLnY8V2hoyPyHY8HOKwI7kOhKRhRL3GGRuX2TNEsaTJjlQcFDXd/JzPQGtami0olc68pNEZLQAavByzYhWCrb3RoxGtRIOBmryczZ6a5sNNkRbSQPKeFzO4iRoutMhAjqMEWzLNMPPwJw2AC3A+RxkNhbArZos8VGhiD7UOh2ChVGBwNVaIXoGQZWRWSMrG3bRD8zMBnaNiqcFsaMERLMs8kFtGiRBIQ872nbEEo2zw/krgCZbZ2EDOA2eOFxaA6gwDxAEpZjVkIkJoXLqU7sqaWDOIsQ3MUla0WBrunWbccihL9x5yrrZIdWwJNGewe2bzsRQtnSFZHeqImTICELAhzhbK9voksLU5DESuOBB9Dm1UYLIIy6S1szUNbck1rvfUosge1BZvBN3RV2e5HcsCjeKbQD/sEqLSAQ2hJmZ1TAYkO3/w8YAqARtfkNZNDccYMgOO8qwgUbDPt9Ky0JYuJpSfURKAEixFErUajRoNltyU4FYuT6O8TU8U2bOiTCWsxYRgL2g+3yIQsu/vmmegCBwfFw0MVmMgx9YRKYIUcqSEJYXBrmkMFmz1flHeQ1CEhLbuKmQEKGietLd7ZBWtfDaayQcsIvsQhMtDaDNEVAarDoREq02MSn8YB4Jdzpz5ZbIzjNxkjMknbSgI6O/0mugzGIjJadwY3vNAm9eGtGg5S00xPWJWuAChle2yJew6kPFOWXrb+bQC5RpXbZgbL4xFKCMeYUGLXoLbcm0MBHj+wa5LwnnceLZMQgNRJ0cZYUGyqFYyoSkNXo4lS/EDzX5RwEOWGBTw5OHJYAgmUNaDLnlt9/N87aCHLsGcugaUhi4mrBCYTCZbQjwIezlELlpa1VcG2ahFNxhbhLGSNEWKQQoMgkzIBtkER7SYMHpoNZJtjJ44HXMYQkY8ilmwvZ6icqZs7QpLk8EWG0OCVzKNomWjOUgxYShbSQiXBDZtHgEWqnjIJRObmjPhEDOTRQzSmCvMGtpZx8Z1Z0vZsRR+1RIknTYGLLAPHoWY6XAJ6b0zauIwkuUaD42PeqOaYGPZZRuXE5CZpCE4inZOf9gZt0AI6oI8MRBULRjtaEWgaOa44AYaWm+efOxXUPJiEA/2olmlLD9bG6UankCAkfKKEUOY7bZedAASeHknp8bCRuPGsbKLETNqoOXkJyuajI4azrb0ml8gWa29hVLtioQeuhkMRuyOd8RqzQptCYTDvWKtHWZJcA2j5BBlJMdjNXLkFBcIi+V4KJa91CYt20jADYiBUrA1mKVWCDoxvZhlt2IC85hph0k6KN6LSCUnUAJgLvfoVATm1MBD8nY3ZXf+qHcwikmnLP4bZFS9d0JkMN2Tj7gU1NWgg8CmOsITIMShSqssAkTOMOdJ1Z2ZwdA9meQaq8EdromZbmxIq0IdVxh40cIlFbBQbcxKOpvM4J0fCLZIwS4xoQm2OCpDjEHIFJXBowShCbcl7AmojMAeZVTQzavlNpSNlvLybKVY0zDHS2RbuJKQLO2mY9MSnZjl8n4VmS7ZKyGQvbwNjZQ6fDI7z0Y+W5xqVpMMhNeBJaQxIAtVupMpi+WCZbGG9cEDZDBxCIZ33Cuh4J7j1oKLw1j0ICtJ2IFWJivGskCLB5WPgWrpVUKaHVCCIu1CRyO2eoHcMtEkWb0sTWuMzdMPhXP/IGg324uL/pwkELJrxHgGi2xbVd52QYoMoLfFw9mMtqHETkGPXTbMl2UyNmfIKAemEVLsmgIdEvzUHRerkRY80IIGsk0OgcqEQXw/uN01MRIpZSWN5bZFAufhSUslxztW9tbYWNhL9Z4M+ymaQc3l3xa7PLow2NUDEEQJxXyKGyYQLhib191ypEvJbTehYCNQY4VXHcOFoOFhi5IgVXuo2GrpTPHVbVd0OWfPCnIQ0OU7TTE9pTA5msZbs4ICufbILVACgPmYz3CrsgMaVnuhdYOjmAFb1JIg1+CF3IwOxFjQHYVeCwJgIMN+INok10207QYsk3xtMFqugcJKiXNQL3hK6EXgzlZGIL1GTEZ+ilLbWbFtRvFRkyKmBcBRark+EBRVa+Wo4DvQMCFzBk1uEUOotjgUc608VhjrAJkJqRkuBLliZaQaMIrOzBSg6iiGbKIGrQpIl4fTWCa0IwIxDfHZymJbQMYhYeu6DZy+JnIMDzaeMYTUfXKwkUC+Ag2rRXbvI20lPUR4GxfEuAySZy/tSYPC5c7ZGhbYtoHsNswn1bQN04ucDcGWiesB5LpWrbrHowaNxsc1Ob4YrHkUDy1RKto8FWJWEoIgQAjZnfOMD9QSOBBrWBoFJ2frN/5h8sTGd99tGGz0gjTPIhFiwAWLM0eEkBaAfXlrtniIM1w24VC/YReCg0dzGtCYk2fmsku2p5jAEAifjbKQaXY0MppsJlqdAfbCSEEKXNwLCbVERgmQQtYoltF6TKqScpAgoPM8A1JjrIqNbmAy1vFyMSTJVCGj1Xv9ESugD5GMRkbT7GwmYlPhSIjrBSIMdrnlQGmKaWgCMTF5jArho2EiyoPYUiU12CV/wBiviTGeckEuUE5dZ6Mm5JmFmRID6Utjk4pm2SevkPANO0B7qRP27EJujXlswI+xMtDwoDWWVe8CjXnCDbALEHmIdgYoeHhQiW5j7+yy1RzLLVMtBRC8pvbKFy5NFVLQ5JVfU1IILRYEcrIbCAqpHXTOkKMfjJ62Y4f1sSAKNXVuwYkniaTVuAbGre+W83rzbZH4yBD7RR/tlDMwe2KQi4YkdYclgmcSc07EVCGNJycqlWTP36I1gItmu8iEVBJU8YcopWpXDJvrCwXFhiunbXKkpc8NMSbgoYSU2wk3PdrK0yY3ioNwfvqvbIWYmywQWy96a1zY9ZCDn+46DQAsCL35NEA9l32YlUME9j6FrMtijuWCk0RTXjPLfudgavS7H6Z8Hkb7IOkU07JiCkQQvBDeQ/PNUUNQaI6ihQiXzK5ZNbss2xlQrEcsIwSyRG6jIn/yig9Qbg4JwJx5v0ZV7cQo2JAz1gYuf+Rqsgb1UawQgCFllGPCO8A1xdC4NdZkiHwhCMhvI9pIIpf94TVBsu0UciU5QxE82+LQJouNbEwIX0SZZpsxNo2m5AWhRtlwKAvFtyA6yf4eDwXZPRl66VYrsPcQiqrhpMtbgslDl140Sg7wQFQ29OpMEzFQo3inTGCA5YRd4gacEHprD68Slp07jvyQZQcsGzT6/i4MhKMoiE+RpiYKD2W3wS0MXGHZw6TedqyG17/Y7MX0KIMgeCxYYmXkgtHppWSSEBWSO86x7Gs47pwh1DsTqIkP4Xjc25y0BcMC92R88EmCwn09WLbAx3Qml5eo/BJ82VBIewyn1hYKoGFuza0BjHWWpIFqHLMEnXK03hJsrBabHgTVXA16ZR+e+aHAmnYK0YtfM99wLLkjEjyiO+9ICLCGj01pMsRK1qCARbZNZq55Q7OdSweJI8nizvZEJcdAXCLl9E4DTFt5RFaXDR6ZvDjMYAFALFpbkum14riSW6Nhb9DslWVAkgdvqq3M1svu2GYIYKDBy/2ULKv3hWYJdGWe2BHRyNSDdqnwQPIyFsxxA6Z3Gai1haG+oE4nHuNDpCihXIo1gqAODe3okhleC4pqcgxtewh8WpYKhAyjvlWGzTvSLsggquM6WEzHxA5ajRC30rnaai3fxZga/bZeO8c8NN4wqGapfnw2aQ9AdZ97E97GzvVqo3Txh1sCxRwizA3fFrQwxMo2yWmXykyOkhJCBqIzOZh2REMMdikmFwZa8kCoSdbihFPdEOLWF5JltUBMElvxIfh1yoYMpfMczdvbGK75jg6NOf9hGYMadk8G0OWfTnRo5ZDILgQDsUGVqyYgJwMlWSUmwpvh+NBAEHgyJGAeC6/sgkUYYwc3LqlDpNIlNRYh85Rsb0ABpiSxlXTiOZVUtC2t8jQRuZ6SjVPIG2Ob2VoolZboA1Cx0V95UvBUy8hGecIOXWq87mkIQBRBLQg/6KeNkJOAQG5FpckWC7A7CVofd7Ya3QgCJGWIWGUymFlKWe56uCY2o3ISuQCU2Q0zq30eppg7TySHIWMaU+JeI34MrlZhIlBos/GmzGtpg8KDmyHuK4EFHgMx2/Re/ajFciAhNVsEyr4VLaGc6kxGi2xpC6MknUylIb8nbN2rlxlorJ1NypBKoKgQcvP6N8dxXh7SnDzJemRyyYYphoVQlaUhKhva2h2rbNgOiRVVe0FkHrYRHLJ33wei3VP3bMtir/YllnrDIuBZwQRyyAhOIXZBssPK6xCf6ZyPvU6tkySMAgc1QUaKzmwCjJ6AAgNNNnvRejMqf6WCxh1StnYUM9tYg17vbyOAbJmqYBoFq5kwFDTgm2OVTbSE7GNG02qJ5M1ogpdeKuTUS6sadPxjyUjSpijy3YCp0Yvw2rnB2ApK9jzRUkuL+hCm8wcHqQB+JQ4tSdg99C6IXCNx0MWZBxFC/RKJXEb06kwOLdO6jTsmOGARY3ClHCHyW/DdOL225J4CjqBK3nCAgwxgxPbDVp/NFWKmBTyUOXY8ATk4OYfQSBVDOSPGrWcIbxZr5PTKI8rhwUk+BIum1RxqegSM+Q6p+eFtwkNVHx457AXfFs8q4NgmiduAw9uVETM9wwy5sE0SdopiL5MHjflIJjwZG2UIeeJYQE17oFyOcJLKZn5llhCxsvvXqmGpu3iDTjHDIjJaZ6DBqpxA2dlCRi+hbAXPn4I7c8aNoaGTFzPh+SA1fdVTqMkUy6ci1XYXEgwJkWj3pY31JWx77SCaFNnRfjdDNZETqtl40tjz5jKjWYThZLIqgjl0yRiulMXlllqgTRhMtMWRWscbsNgYKE+oBqPyzHDiGki2aGOGkKnImjI54jfz0qEFiiBYoM3Z1E6MJbf6do9q9IpNIymisUk7G1ItNsNRCWOp7GBkObGZfcwfEDvCMVsZZJt0oQ2vOxEGr5EypuQrzdyg5XQoSIXOVhZZx5ZLBgmwZ+mH7mwU05JNkxBDK1VwAAdalBxpHJ6paM9ACLKlEcmTTFfSgmSvWhbunLBsbAEeBEsOVE62NpXcCjpwlE2WEBaJ7kyumdtilX64igyUHY90hFfzt32slKOTKzTLXofMw9Z7LVfYrTU3PYHxbHBQG5vlPQFToxc+WHOFrJnF9HL2bLSrQFgm9hNyrkXYla3lILB9FWwOuqLZwpaPQIkWQvUKtsnHp8c8dVnaKxWwf2glsUm3XQD3sTm/iVyQMRoeLQewV7pfMngCMY1WFSLQUYlWBqyixcYM469E6KUjvF7jyN8uuGChVdvDVGcw7cxCY6jWclAK7lPlBGwSaG+NamGkzUlsUWZMswCLjfJnpzyOoqERIELyzIFKFeFGE0VN2dohYBlUCDkWrCFkYC3XQI7+9MLE6BKHpW5yAhQ0es3HFqO1cGINuM6yDPxctCG/kkJmbHIgeNHKZg5UPsPJyLXNbLF0TqsYRjkg+zjv/BxpazSBVr7nYWr0m7hPbHZpcgewz1nOYByzKyR2Ho2WyTR66YwWiBVsLBC8vpYJrThAJkpSvjg1SgA84SKDHhYNBHC+jm0GbVtRQzmFV/vCI6oklpNOeGibSGhd0ceUJNHo3TebnORlT5Cm4YJsr1XxuLN1qqW3MNCsqgUgIArgXqTV5I3MAtaQn356rJzDxBAALXr+sCfs7rynCSxIZMvTia1j/PxBkgiyB1mWyG9vstBi87ghSWCgGBYY6+QyW0gKEUKOSNVR6BUoc9IgZHAFRpNH1rFxEsroZUdDOSKIVvAkGWsBVlE9XA1aMKd2rUY3JqMsSxvLJLsXFgIpDbMWHGCgxiIt+WTSND7VZgi2mfBegqnRb+i1c+ykbAZkq1wILzcs9QTbrqMFyRbabG4NXQvDQju2CBSa5Ruzj08jaFA0DdzU8M1KJ5cfoKDkHCUtNjLK5Lo4jRx9zIJW82zLIttcCm8Dcm4e10xCU6VRAaxrlc4uiQAE76wHHRwpOj+jzJSDRvSyA2EXMWj6zsRgVxLLA5qCCwS/MgvoPTcrE+NSjj5DvMIai2NqZwMyhebJFJnUGAgiDhQ7RegRrQ8KmWhxGkzx3e0YoskAlQkFRzupRzHch8WxsgAbXfb6LekiABRydIzrwxdCA/gdwrlBds40Gubwhh3It+zRxEUkM1vvmvRhPrKY4xhahDACDixLk2fgWFbadLLzslNNfnmLwyjL9zJMjX5d94lju1QH1aeXrCBwIVxEqKRQS58ZDCWQBSto2eSwBpgzVQ+KRn2yUq+ZsHGqZghaCu3RVZTPPLM9ChOKAGRPDLKStGo2g3FCJaC088SWO0iZ/QCrg7H2Is85WmQALMDI6gA1jw5lWQAI3FPvGlzpcF80SA4EmdnGmZUzpi3vECuXMRg9aBPCPt1FK0FUXeQQJA8cSzmlwV6dXIwdM/CBcceWWj4ZiEFAgBbRu0Z4xYxGRj/MJ7ls8VAsR0yOvcCQNvtyGVAR4mJHVy5v0cYEZ8Nmvny+amyOt1Jy0GF0tBt2P1SPKCb56Isn+IIF4LXLwzEhuhoiCY1hEO61mBo9/8HaDe2KF4hC9JQg2xxXY6NyLXI92OChQ0SXjaJ61RyedGJCQBtH2uz8wh3UOiSwD5P0wVBGt9CdBIjZVu0wgaa4Y2qPJQUnARrBLVSLRKoRK5XTK446NjVtqeFN2MJxxHGSONc1OvnNDK2Grk/2AzmBKDFACly9nKqPSGS2vVbPFsByqk5L0flpI2jBQ2TCnAQTZhSHEBONOdQkDVe18sRAQhhLt1g5yzJ0EmryMhhjMjJgUEqya3qOKxUyF2rc4iRsk20L5GFVbfL0JAcnBbTBsteKWyX0OpTNsjtPm2ozlgVrY0y1YQFMlggEGWFSbEfLPJXZqs9GEQiRybGch6/QyvdyTI1+zTURqDVqdjhWASgLjkRek0HTMQCRRpN0PhUakbLJNCJc+WkRyV6A2SamIZmW9pALw1g1bjMB9LFHmcRWDx3ImuJXHzFKDco+aI6i2jjCm0Konuc4AXDaskOloaWJEHIaSSN7sMfVm3PwcF4xG9jSFLKNtQ7k2wStkqAPngTvguGhxQxjyiKGMVwCMnjvSDOZipC8OMobJg+EkHpkGB+L+aWERT5YeDRt8TFNArqYQ+hhgRYrE7Yxy1is2pqqDANI0IGpZYwo0zLATmez2SgOmxxibAxcetijCaZEjxKjh5OtHoSNsHp9mF9zI81GOGWJCHV2MbnmQ3ZzX3JfxNToVx4UYiyEd977DAHWZt3HXHXkwjcIBgm2O9ydVURBqoNHRbIyoOXNnV8VJIGijxb1ZmLlYidZHTLVhQE15jaRofiAOYDnEL2QLqgtv7JJDFoRwtWcUoQIdtlmMwChLBUS4SZkEhJ0bQRMBtC299HZhcV5JGPf0ZvQtrDGN3igJ4ONlouTCZ2I6yo0CpTx8BAWaWKgZWpKKzPBuUl15iqRUDGRcMhCORqCHvCbD+J8ShhFI5q0kN2i4emRvMhmiw9iHaCkTcwhvAqEqwRCU4JclvZkc+tn3+DIamZlszHHjNawK+a5QTVKpsdDSOFSWE5OnQxWLZPWXoM1j/s+pkbPU03k/mq3J3acqo+WHcmhxRdPnltFAYYoUXhyAwqUjTKjdFqgHXtrzFna4y3YNWQTamJslC0MeCiDnAMG16nONghyyiIpXO7kgBhGYZB9fWpNJjiEAtlnEtLGFw1wFRi7r7Ggxawzm1GyhCoZiITK3KaOQcUHmRb5PZwJSqBDaqYYHtHhUJ3ftICH1hFsCwoCeVFJjZwRMAiGk8eEIdqtJIS6QZ7Oj8U8Q2fOcdF60Bo6sgE+OpqG6fbUuNCRxy7KshFOUqkgoHUeD21YbplOFRnZ0KlADuQhFA7+kAcQv3q4KhAs7T3lYKlrp2ECHkhbfLSEI9VQzDk4VUyJ0uBK0/0KU6PnVk1Eq1XwImKNeJZ7sbxwEqBKDLJhb70DSDnX2q27kDMQDXufxzRIz7EGmiw+Zo2ZHQXpMRkxaZQsyU1a1HsveOXolKFFlOyiZ1tJUqCcbUxJLrZhjT7gSQomD0+/7bTVDpaEL28a7PWgyYygjDV81KymjQTSkunyXaMDXv8huVRGoQ1F9pw5XBECTXyXcoCyLFaBVvCYVihkHgIW7W9qQwtMqpY8W+0CLJHcRtqSj87L1QzqgUxwa0DGppRBjsxJioNSixOjEug9mRhI8JJiY0gzSRMGS0LcYQjlG8iE8rSO8ObZQlVJbQcgwOWZW41ZCZZNuN9javRLD4r1AmqZKLBL2Ys7fhKw12EzoNJrCY1TbYxNl0QJOE7yuK0zD7BolwXmScJwftjlc8tZohnactmSJqkT+2XVkFSqBRIzkOo4B6hCNgwainbWQ9gCSGUbFNnKlcYxr1qAghRncMJhfaIPGpz+uS1slT+aVCsEEHEY3iEQ66DTSz28gwqc/iV87X6GRguwOJYCQcWyxkJIlV3AuxkqOtEoSvYQlQwwP0xq0dvGIZIXw6U6oKkX9laQx6LOQksrLRLpjQeRvQRxJieph+dAxdmiJ3hqQW5c5aOtdionkDMSZIpYG+UIb0dgavSLl+cxMLRC9Xyi03KAl4/8XEj01NSxldfHpgB7DWG7oyzYEldaBg5DpNFpIcWpmXwDPTS7PHN72JaqigDBqUwgNgyKxiI17Vd47PV52VrYDFNlr84todkGTfyJ/YVYSx1eSAoZZIcYKXOIBMlqKadUfnpzuMHovfPcZGXCXCjpkbQSArzSHOhpY0Gadz/Mcpw7WpSBWs3BR8FRNoFQf+RIR7PIAIVShKFATHqoDnyPC36SGGUp94staElw7ygam5nbRdlPMGpszC7WB6p3uezeZU8bRh/xYRGkAg6BLdZZqlOBTE3JZU6MJylmXSzeO/XiZIaODZga/QJeO+fqDCvoBg+tXS23j1/S5XLfLDF6MlNmi5OgfidiIklm4DGT6kyWh6/j+JGcdjjDGjJzFBDyfIXmK4FzQAvZBLRCnPEYVwJPUwhVFPhgM5ziopFPj/1Dy1EUKDofbCsWonxMYoI6JPflYYTdQjEziVv2yQsaTCkHv1xopA4rb2MJuVxozbQGQPAeEbn7AC2y2hUEW0QgMpAWSGppgeA3EOijBYRhFHHdmWzXGEcdjX6X0FbtWhEoeCaQJZhGfqMyrasPFUY5sAaVzgaW9iMdYCDQLUv6YgjJfrekXECJMDKBkgzGpm0By8DxuJQoQ8TEpAXanBDbE6zj/WJq9POXc82wdjxmWDxg4vDI7oPnp7jw+hlPSh0h0Cg3iAzR0Fsh6H2GheAr06Mr4XBch1HZp0hYtj+m4UBDMmw0OFXRLOMhNVxqgTqDi+w+aE0GCFbRtpywh9gQoJivnlejZCotU8bKYCMAe5zftkQKyXZAkIv5lQQQJbMxb8howQGGaQA63GxtVELA3uAU/DyUcKCjvIYxEMz500dhtJd0iTWleEyOW4hDo+kFJMXTYSUEbIFSp9b4s92QpEaH7viaP5uwuI+u2UH2JmTryThE7shM0a4cEZA/1r8INoITxwUP8SNzwnOokNqLcVbH7cfU6Ofw2rmWD53ffk6VxzvviWjXQSULLpHVEFB5/CSoCyY5eYTQ8qqQiyF6OMoCOQ4UzbFszc9AWIJmNQW09YcukSFQUYZcnk/Y0WkIqm3ptz1IBEVZHB4WgYIVETx/8ynYKD87DWeUAFCWbmMkycG8dJRxjNJoZmhFrjlAM0OQh23Z6M0QtHSNq+TUvkzIakkDGX3otDMazObFATDBN89RtFhgE22x2KJrFg0YmJJiYuLUDAEKGsUj1EBoQ3ZaGU2o37hWF0KRDYtIS7v0EGQsjCWRWJ0z1HE0aBTZtmoHVs0kQTF3AS6Eh7HjDmJq9DO4T/Qi1nKjtYomT7VQB6da2R1ki7tS7TIo6OykVKep0tFlYyi0qx/U4UT3fAxUQH8DRkTb67RgEo3CENwg1Bv/aCGLANEnMV0SKDetzdjCr9ExxMTV7ulNjgjUoAIzqPNwdkXy8FH1EBTggi+ThIUPCnqokXdQs2UHpmZFjp7uwgVIKtVCq2LjxFI1ykghd6RAUUbb23DKckGILx7LPUSPq0MqPdVZsAOCuLLU3tltVXDPtiGMDTo+IkC5jHh40I18CEBzcIOQgls4dW4GYmIOUR5sdeYEoEJWJAR7ocY+KoQcU/CwRGvHnYKp0XMeqKX3wdPK+kgAuoKG1eahguJjRl1tvQcnb0tGkvaEAJIlmqlKxUvFsoaMgWTxqVBnOezObINnEnKGkBN9uEIpFYLyBMQuFyTLbhkKS1tS00WmLGg4calM5gwioYGR65lDDPsihFFtAWpZar+GHQQ8tAdq1sczgZaskLkLOsoKcBMt4FQUpAKTgsP1TOBsZSSaCVQqylL5bppp0M30UoimhjlrAcsIQMBW9grhIHywpU12q4ajxp63HOtj4QiSFOKzzkZDLvcI1FABjzshW7AZLUOl0kJFRhM0Sdg0pizCIJAaOhpmNhTuQJ2MHXcRpkY/9UD2w4HIs7aOAY1upQ9etGnnYRYPTfiLkybKvjxwkjmPwKNrmhDhKVdrUJaOPOjjamkYFnnpmibVXVwhkCWEC8L4SQ9Qzh2UOHRO4vM7CBkZvXLRaxVQKqgxW1nYSPZ6hpcGoZjiOtDe4lBIpYxAyMqmuVjLNqcXaqHqXUaxFcOLyQnkQkGOzw00N3YyqpdqOfToU9OuNS5kiCHSwtZHMN/JGc4W2wFNaUwwTcnHXBkLmaLUJPJR4Z6JCYD71NJLSVFyQazqxl0QYZBFM8j3uiUnIOZYiAiESGPGIazjLsLU6CezJnrReWDYjx0GHxVCFpx50GibOJb0xdvYhi2ghkEdGtkiPHwyDSpkCdbILlnDeZ7DxARa8BDZqtvaqYHf5CcB9o23gRY0nEhq2tHDnBwBllDlo9ogBpW3QnhVedGUNgaSQY/JC9Vz4JZFKixBEZwqYXLITp4TiFhbBRIzIbzU5DIHFlcBc2xHX0kcTgsEq3zei4Jie3iBHKiskQewKoGTlA2wAI5dBuVkoOfE0NUwSmsBxopli6naUiEyOg5oBVJqeoJFW5rRArQrYVg0Coz1vnwLqhp3mI8xweu4azE1evZlsfo8GLn+cRTqRAEkQCxaCcT4IeST+YQrFLrG3sNG08T61IfqCxWAWrKurIhqeoJRIQasOtayT9yaWT29Y6shpCQziARpNSu0YQ4GGprr46mGQMHDREOUMSw5uq+WssNIi2U8PDFsuUdGDKrwSFtJHKKZ20616q9ohATXLNEHglVqRQYyucShRRdkbSVDwgTi8NkydGOZsw9hbFCLmhXNFnKS9GB0qQPH9rSgtdF5isEmFwqKbAnnT3tGNLQcPSxNTog8NMocHCC9MZbHFSK08nTcPZga/bjuEwEfj+FQ0Skj0By56MW0nJ6Q0PAcMjLDRgsMVZVoNAGKzyGf7mlxHyLsuqrLaxfgENkIG7kvlhLUksQ5OFVdYCJzPrYb4mdQzNBuCuyigeo8UGQLmSxZxgLHhZJbS6jpsjoMYZfyx41YU32KFl+sM017wfTqIi0aCakFjQS5IpUsQI1lMl2Zl3Kz5g5H5wxoEQsjBHqVx1lgtxFgFEKcx2nDk7RmCIB8dDJ6IOoeyF66iXZE9zG9lJlBchiTT4tkTylauWRVK4tFdCEkDakqM9s2c8c9BVOjZz2Q5xBPcRyZ9oTwURd4LPXSkl7fDfkAqyVLgjMwGyz22m4oW/CFEMBxLyZzJDMdGTU+DVpqXPE5dE6DLpqJVvCczeH07IAFkOo8wbEgVzQeXTQIhJgWAQg0NPzwasJ2OfnYQIJ707zIjjKZfR2dbAuD0ZIDpY2FSKoJSKHNHE+JtqJpGnRRJGCMHZexkhtZGMNnF0UlodwEZB/gaUNqtN7Z4YDKC9EZvHRFl1MPGG2y7N6yQoKFBwQxJcrqRlG0+wDJEl67EpBB4C4reRnd1Vg2xYTzLI3MHfc4TI2eifvE9krLE8IWbD6W8kmgiVp7BvtIMzDD7aJcmgiuZbTXEO5k4ehI5bf2nDad6Hh5CDEljygUh/ZSEzxfbU+He7bDAOpr0BwCiv0YtCZgu8l2Q4hR0NpSudVVrJzsBsJ4G47GGEuXBYJODV0Ed5yMRa+tpuSoYcXE8SckVMUHhlSSTPY0IDCVhGwE77KECdfAySHY1gSg2IoMHiLfzI2E8sPAmY8b3Xk+zmQLU6VsIQYqTrCihA0j2u6JGc7vdcZDCW3ZCIcXQlY3LLsM9elKxz0bU6NnXBYHHmBjIQ9kATKMOK46U6S27iREjFudpsFSF7IFn8R4OKo5KWnzcylkkpr5ZCU1FCdLnrKQPcdwVVp51cjlfbEqNi2KHSAjQ5hIKuQhNSk1FiEjpuGERIULzJayQS05lZiPzBDXFUXCAlsQmmtsgsBOrlg9PDJhEFJAS09auQi5m2hhpzdjARol2GYZLVQLtmQXsalRoEFLVFnbNak64qpNT4VnKnuhMlDzHFKpNY3GNkS+ks10C1DAow5QBgIysy0PTxJZbXEnw9COnRgd9yZMjX700rGj69MoLLY2cp1P1HRqRuWik63PPBhTl0tp60QEYpRogiC6LIN0inMLcuMnLIRauwA4Jyw2yg4Ljdk6P+CIcJmfOhp7h31vYLVt3SFD7XLJQbAAC27WagaCr3O6JsiJQQZHTIoWHKhsvrYh8gI20iVR89nA987aBYv8ZrNRfwomJPIb1UG0lJADESmLKBVtBY6vVSWkXKwaSOPqQcDoaVPWgxZF2cLJS6m9I80SmnRNgAY8lEo9EWdmkTGWjiZaFnSTOu59mBo9HfeJgE8yC+59aupID+cozwK5h16CCc15UOecMVyZQGUwQy3ksdMUyHirSQw5oEDraGLOUJpsdlUbcKCMtg9kdb6GI8S+1iKQKQvIQD0ToK/9relRjv7UwrDIGjAuuQxnQ1IIrd00adQdK5GgNyX0FPFwuFqmMgeyXlBjJt4XNEylVTUzwqPJDFLb99SAWhzKaiOtlGH+PmSporUlFtNmH0pxgqXMNFhvXWoBCiKZw/DGoogBlj1nI+Q2bbrGjAIEbOXvuDdjavRU3CdSGI5wnKA48BNvOemwxynbRLC15GsAIb6K8ooKb8tEn/Y6WcPj013PtBSqvphvk8gRWOZmqtKbSWpKhvfC16GjgHZ3AAsMySf84uNhZpCljIVL4lUHvseFI1tbKIpdOQPmJ2weI2BGDaHlc7+waaAYIuMiOroI4QytZks7YhVIiyS30tWYgwUUQbZAKwNQaTFZrWVa3eaRTa3hNAsaIYnB47kBDgkx0E670taxHlzOZgJk1WJpEWJaNmrDHZaO+xyyJuIAY8vDHWqhZHPQmUC+YkyYPIcspBEaZJ6XCpEtELcPOgsh8ryHrIockKtaGTwVnv1hMyH8bGnAw3VNVgp2eSzxPaRdACzFt4oHQ6pAKNZyZAhe5jctbAEaRfDQtoRKrcmTVzu9ZRcoe6rpCK9zZpQtY3lyUDKDqBympRCQ23wfCyP7cFHVZCI5Wj+JOlW2Na6b4MsGUBCHXGWIF54ZyDTiyxYWKA7MZgCT1IgTK9AI9kdXqbFDKXsOdFHJxrE2dtw3MTX6kUt53uMw81zRSaATQOdEPinbQpcIdWbYPpziEGSkZZwmzQ89FSeBkLtEwAktx3CVDX2d1p5PAqLTwhi5lZaqdJ/uaEh2TtFiMpINC6FmtkmvrRJiPjJAjnGdOVgE50ZnuGixHZ3fitI0YKVd5FCbsdKqHg+FlBwCmmaRzYr7o0xednRxc2QSTbGwasS00ZoyO7iWkXAIE0Ubo1iOJi0hDmrNljYvFCxJCDUDbZRVHFvQyjgIatGMVTrt4xAFiwGT9qi0Qit33NcxNfohv3b2NdPC51YCcp2FdoVqg4QiUPV5nC6G+ETMCgK13oo2BUhu2w0DoSlZqdMoQ4GqXRs4bjlmzQcP0TwTCmgtQ5O3rhPAdHS8zCBnqa1wNhluxOLgoVQIoUAbYSFa1195owTI0ZJp8yQBDURZjhioqYbhzTaQdvbjM5kM9IQhZ/JA7i9tDJOFBuIUApK0b2XQNMzTQ7RetzUZcuyVIwLFYetdsC4v5NSIki2grYGch/JpdrDj/oep0Q9eqnMCj/EvY/PeQWcJz0hfGGaBkK9qh3OUXRCkpMt8CPqgBnBaidGiQ4QrCxsN5zkEzfNRYsiwui0CYnmbBQV2euTyhWROc8azp2M8J1qFmE9jBhqORmtBDAmKqpbQuIQzWPBwaJOFlrI6WiyENWXBfWjuFEJRqudPBKn6ECKtVO+gDTXnyKCnKE8VdsuxRkAxlcEem2nLpRNLHEfZYpJ6ivK6HeMjY3O/HExILa1VjVQ4U7jK56erBMyRTbDAgTR06+q4f2Nq9AMPYN+ehT5LohVsHytbaeHJJHsx5Ut5Qxv8tBB5OtKL1l1awphqdWx8AcgdU9UFHLTksPMQ5sjNXWj2xUbSstxLkwUE/R/tqrk2WmDjIdSoH0AVj5wSRc0EIZHAEzOqaGZCtDWQo+IXc9NiYmUYhMqg3oN6SJeMcDb7bkvYxXXJY6ZmGg4xLdJC9XBSQlZLA/hpB1/Hx0RaSBTZHBqzDZp1Z2ty2m4nAMEbPOjIFEhIUoRnLC3Rd3S0mBp9f90nGj5vdOpEyZPmK6RQISDjhK4zzeSQSxLi0koLA3FboOvEaLiDPBg1E/Y5KwMCtlMM6uFSoK0tglKzCZRsMgcqY42u1o4ai5e0xgqvYGO4LNMqn+3jfChOyDYihszQ63BEuMlyGpDTPABMqo6d2PekejIeiKLn0D7/+d5NMlUQ2FF2CCyhWlAGGmUwLDAhMnsIWxjBdhDqjDIZsnRaBCxFnTkVa7fHtWVwlYQXQzmxjo5TYWr0fbhP1JkH8HzyCygqstPs06o5L32GRUPt1HUNSmUQKEuXJ9Xk2Fh/XM0YczlG/FNfb36YUGrr9VjWi5AJo+ggUvbJ/W3tQPtXt7QCY7GyegWkRAK0NOpuVGbBPntlsLe+zUOZA9jX0BoB0mB0Nulo+BHThrduAcgwciUztrwUqrggUNOgKqEwxpcSNGkaNvNkEvtCcKOEni1hFb3y1MLC7NMscwxpyfcqUcrjSPNA6Oi4fZgafY9eO7cn1gCfcxbTMRQOh8hIrVSfnXVSyoLOKhACSeNGWdA6eXs2Fw1STQBbjQKMCVIGC+41PBYeOejgzY4jSiBHrX0uR6ZRkMQJSI3AMtY9DvXISbWsnrw0zB/2WjpxtYNQaz4A5JACVqN1ZPGlttOw3wXFoN0+j55zIK/1Dr2S5zQis0fMcWHh7tQZQjEQsozgFgGBVM2AjKeZ5j+BOPkAH3r0MpY3BLWmM+34BDo6bjemRk+5hOdTnExqAZ5jzVnlkxhoT1P0cfJh08kKk43mDKcmHhpCZp29vgjTDkC1nSHyDjQTog8wWhzKpuW4w2zT5qIGCUxzpOVb8p6D96J2wfD8HSQm+yw3NnD0YBCO9bQhYjJweadoT6JnyHIgQoxYSaKXoEmSJWvNjRoeCo8dFBwbZDzksDGQO+VYyNgczuT21pQEZ6NgglqrXjHGu5ZBnfjPbaplKeZSKMrmzB0CWqetnQp78oZBBQo+jh0ddwKmRt99SZ6jE2eoRJ9wPgXDU0wgT03TDJ6dSlhMn8RxYcjOk9iyvMFs1LBkJrtY7CTT71mJY4IoBARGRWR6FWs5mvELabBobkiOzvkBeluVuodJY04AGi/mtJtZgUHzEKWyz2xWcg6u7wwXT02q5lu24HW2xxayKTAJZFmchHY0DjcyBIYwZ+ypp6G2EMa0ss+oWgSoAJ8JpMZHRm0IHtr3gCZpcHR2kWc4nTo67kxMjZ58SZzxgC8knnD51RmfcnHiufMpi0dz7k5cCeho0Vnr3peBPH640YluaegHAVIR4rKU2UPTk1TPnEJDYwtj3m+YHIEUowVOKQQt51CZQ7DRbMHyYJFUKvkWtHl5uVMb3+mjO1XLUorm0WvfDarNEYEkA9syDt5TGSeF3EdPwCLNkjwZqhUGVAhgGhoI2kdH0CKBXXI8hGGV9gxwTjU2dHTcdZgafSdeO7dnm089bLrA2OdZa0bI7NTkKWt+Aac4LwPzoTbhGT24TlEXIDtzIq4Zj4IHXIhqvjpjI2lqh1hbJEhMjp2m+aKtKDFpoeiHGk3VdyhQw5uyBTycJCYMwTyRhqefcsHs3Vcgej5/iGyLQb6N7Vo5S34dOuAou0DXatQ6xcqI4HxlN8KYBCMS5nxsx47guRMKnRE2eElPi1Uvr08JroMICIxwIA8KIbKjxvauo+OuxdToSXjtfJoz0npdxoD7gSZAtcWnewVSkOhwunRyTwgBmTwTBxbJNPMNWwr0NhO2hS0doVoMux9y2RSXqIyxGsKwL1KBwZkSOBQ9AQmEjEhFW7nSidaJqcrUTh6gK8vHYK59VNsON8wf7XgqIPaomRI4QSmmdtwaWyXBBJDZchm55S4PQjRCzq0sFpwtjpSNGQgL7JQ9hJB9R8eHElOjJ148nIg+WSFTs1GXCm0w6gU1NJnFyZZxOtdJ1vntF8uAczoJXbrNgSZihANUc9wwWpUAY9wsaBTb7K45O51D2DbZB6O68EKEZFpNyfnJcGOn5NZV13Zla4WiaeO9lL2A9w4PrdVUu1/ys4U+fucIo72EJNjRxzjpG8atUQT09KgLuwXL7mssTwxqcw/uvto4UuVv5iktOs4nxJgAXeIFR2MFWrmj427D1OgJF8dliROSZ7bOV2zxmaxlnd8UpNroLs770ImqhgYJ+XJvqCPKRmg4gCyNbvD2pMk5kD2NzEObBbRJq0uLguo4p6T7I6YVGJFDG1Gb5IoMzqmxINLIrhlLtGEmKZtMOSE/MSm0c8gQWCKlvJRruSDLxKaJHSYJBR0W3O8I+9bMe2dO3awpLRz89iLU6U0jRTFBPm8xg2nO7BHFB9rjhT5F0RRi0I6Hd8EZCuPMjo57AKZG334x+7pOcO6G4LNZCAEdzmwpaKTJlVda8qoXR9lMY5u+6BXli5MtrKZltqQHqJqfszUDTYiShpI04c0WAIGydHirlNsIr2fu5wYam7Eoh8Sm5sNeRkiDUKGyoLEHoJBMDgfZhcbrCchFA+ySyNENJj1icnT0kB0ic3QaOFSIUimMq5SzDZv0yMwmLRIBuKp0SnenE6mlxYPwcOXq6LhHYmr0raqJgC+AqE2Gzt9QfTbnBen7DpkDEEomfK1u4Bgk50XORpcWi8LE3ZzHos96NeKnjK4m6RmydULYUqY/Y9jInkrIaEGJvXOqcqlaBcEkGQ3PnMycABQHpiPUaNPItknihPYQ7a6N9RRK5sRytkAlZ4XSNGA5xQqodaxhFZ0/PKEFga59nljtplqSm1WqPITGZV9G6x0d92hMjb7lIp7WPIPzZC+hldmYRq38IQRLChvoEJKEHhs56tKcyZEz36mkUQ+70h8oPl4RMxW2LEBxYY+3Q4jAStdaNCJUGKTJnGQgWFlW8GCs7JEjLVLcEBZKBYrc0DnzCAdgquLiEWUxgp8SmkgFmmQATST0QHLR6v8ckmT28OYf0ikjFIcjEHAs54BNOdF6AesJI2Jr2pLZyStbR8e9DlOjb754OH15MtdFAjWugDjpfaKTI822IOtyCgFtXTAJyzQ3TDbJVE9wQI2Imfg2x+MCbIuas4prGzbnMVkuA2JY7MqR1BDMgy3/ShwbjWtCGDWG5bE5225q2g0a5SXGo3ibGeaE11kcwHa2VlHUcLMGQYFmIX/csEutCXNBZOUodVtHRkxpQqWWgnxsPZDE6HwsJmYIWLCrjB0d91pMjb7pIp7KdQ1AwfnNl06Sx059KRuvFjYuXrq0jfAW2gJXl1ySqGqsuOrkETcvV7UVGExIGRicaGQUk/Z6pxLNhOC6YFiNfmwsw3ZLplGcqJ4FsT152ms+SXKfGuERg5WBFgCPMkxbliiImRlkPyVg04NwX4FoSRiftp1GCJqMFTYORGs0XmkdHfcZTI2+4eKxkxtbne1spdvoemcvLe29A0C2G8IEMh2QbVzk5TLVjYWpp73z2I6ZqR0zektrfdPM1NSH7Z552Ozmi3dsJsEQF/j3o8tvPbH69uMrq6NN4GB77Bnb6A5CjsiGxqe++/jOmSmX/Kdfttu0o8vrP3vNSdk2fflZ2x+zZ2tN5t+PLf/X3Mrb51bXkH/nZmyP3Qsv8ZaTKy+9dTEHAjRQBuLxYTu2fMPZOwaLJvCLN8wdXFkPItsMEZ5z4W4Yf/S6kzu09w/dMfPl+7bTMZp62ZGFf5tfFWvTc86fffHBxXcvryg84TSRb/Q1+3acMzP13EMLsjYLkvLnz2795J1bf+PIws2r69unpjbHoRxhwQ9MTz9ky8zH7twspkMiL5pfO7pwcC2e/b533459PFLE6+aXX7ewYvlLZrc9YhuP19UrazC+a3nt5GiEnB+xdeZTd8YCGk89OGfh0i0z37FXO7tp028dW7xyZc3yc87chfanD88vj0bbp6fqJNgyNXXG9NTDtm5+9PbmxBjHc48uHM2pGpdvxRw2e26FV80tv2kxZv65u7Z+3PYtlgv/vrjy+oWVK1fWj62PLtg8ffmWmW/PqRb+4uTSGxdXD6+tY2IXbp7+nJ1bPlp53ra0+vsnlrZi/TZt+s69O87bPL2wPvqZI/PUN236yG2bv3wWZ+ymnzw8f3x9tDoa7Zie+qkD3GXgXxZX3ra09vZlnN2bHrZ1Bjv7yTvG5vajh+Zw7LYruTE7PXXuzPRjtm++cPOMLb94ZP7kaNMO/fKygfkj1YO3BqGjBWriRbrd8Ekfpz3bsliVifUFxmm043yIdeK1FlUBMi2EI29wABvDhY7ec/7mfbcsj53Hxteev+M3H75312YdWYU8/X+O//SVUcsKX3jWtmdctvtj9m1t7puUWXN45D/f+p8no7K85GF7v/68HZjeFfMrD3rTQRv/+GH7vurs7Zr/1NOuPP6c6+KKLXzRGVufcfHuj57d8mcHF7/yf46G9VR48rk7fvXSPZSwApwCp/K4/zn2f48uyX8KjB5zzvpofebNt1rdPT117Ucc2IddHm165k0nn/2+uJBGjzjrSdedfEHUu1PjLy/e+4At049475HQN+CF585++74dX3/j8d8/fur5fMmurb9x9uy5HF1HLfFnJ5e+8n0nLH/v3u2/fNYshJProw+75sj1KkAP3TLzlov2QcCV/7NHFxaHUOIzd2z5rbNnH7AlLsipK2Llv2PP9t88m6mAb735xG+fiFmNLj8T7WffcOzVWXAn8HWz2150zuw2HOsNuPTqw1evnuJc+sKdW/703D2osFYfdM3hK/BEJXzuzi1/c/5ey8Yfn1j6mptjfwsXzEy/+oI9H7Y1auvXv+/E75+cXMYn793+q2fNvmd57cHXxlH4w3N2f83ubS87ufTluYCP3b759Rdyrc6/6tBNeOLNKAg/dHDu549OHmIclGfs3/koPQ1gzXdfecj2CaBKPu/MWT/HPO6m4/93btn2Fp+yffMLzp59aO5ChzG9CU/GOBC440PhwFnBy1anMC2qa/zY1xYRINOod99kzsAMx4Oy3LZYhVA0AG0Y01VzOA3+4MaFL/y3w2Iy7Xe//djGggi84talr37rkREuTqTF3nnayOw9bfDN7zx2AlcCjON2Yn3Td7376MaCCLz88PLXsBRq2u8fLoXc1KFZO8V1OwbvXeLE+uirrjoe8/eTkDFepE4NDvp+oUm1aXD/dl7e9AF/Obf8u8d9IzyW6itmt33FrrjX+7Vji29f4nPMTx2ed0HEFfbic2ZRbp5xeP7Hj0wWROA1CyufdsMxXM+hJ3wndXuAC/78Zp4oRrgRC+U0mJ3adGET8or5lecei3LzhoWVKojAa+ZXrl+NW1QAd8RVEFF8v3n3tn2qpDesrX9t1rVnHppzQcSTwdP37/i0vJXD4vzmsYUHNbdjuNlE+zfzQ4X6x8V4hnZBBPCcgfY7bjmxsSACOChfu6FAG3uzxANY9qfceoqro8XrF1c/6fpjoXQk9AEujgU2lCR0vvb49V3L8vJClQwFMouLVJYbEejKcF7AzbWtIOakKi83R8nI4cxB2fInA4HLdsxc82lnv+ZjzvisM/niAnjd4eX/d3gJ5L+7ZfHXro2C9Q3n7XjXJ559zSed/fRL4y7jqoW1p13h8wZ5AQ+h0RtgPz7/v47QbpbBndr0qoNLv35TnJHfdPaOdz/6wNWPPutpF8Yrmvcurj3tqhMP3T7zYxfu8nbR1uF6+7ELdnn7xN1bYn89OjZIzVijR58zetQ5o0efPXqUt3NEHcPfnVj+8ZvmIkNhfdMn7tjyY2ft9BbGTZtwY/hjZ8KyA+1D8Pzf7O+vnD07euiZo4ecFduDz/z2PdvHcm7atHT5mTdeesaRS8/Ynfdcr54/9a0Z7h/P1BWIC/pJt578n+XVX8gLGK+m8doThRKvW215xNaZPzpn9z9fuBc1xRbcuz3j0OTzzSlv9E6JhQeeecOlZ7z74v2hc56nuA9qgTp+3aVn3PSAMy7fEkfq/+Vd5++eWLTgYoc9aivsy05GZlSc3zt392+fs/vHzogFf8fy2jV6gf8XeRf2+NmtP3lg1z9csPcBejWDdL+rVI/Ol+quiS8avzF/8fHF/8jKCHzKji0vn1v6reR86+5t77l4/1WX7P/hfTts+Z+VtWdtWL3v2bv96GUHcE/90zk9xL92w7Igzxsu2IvKbvXQ+ghV23KHgSOHA6fLFRtKkgVegXUxW8jKZdgFuK6hkEWZ0+ZL0YKZvvZ8JwhdzVBPKUimi13h4m1bPuOMHb/5YcNrmb/WW3ivPTScVb/8kL0P3jlz8faZn3zgns89EAf77w8tZ1psLvE5sQZvOLbyY1ee4F4M4B699uhwMv3ypbsftG3LJdtmfuqS2c/ONxP/4djKR+zc8qwLdz/rwllsFzf3As+6gJZnnT/71Wds0w5qT7HvUfGHHXzTyWVtK2xPrFy5sMoZDvcogWfdPHfD0voikhRGo6/bt/1ZZ+/yFkbWxJlnnbXrWWfuetZZO3HP0o519crqm+ZX3rSw/KZ5b7gIdeyarMZSY7ksK8gEzpyZRlm0/IbF1c+64bivaQz6bF2Tr5hftgX3dHgp+tW7t6FQoqbgpZ/MJFgotC/htt6O+niyOVUuy1fi7x/nbp6ud9l8A4tVxUtjGTZVsfvdpmadOxMzObY+wsvbb7z5BErnNZfsR/XBU8glGvdA3qD9xJGFT7j+6I8emnsRnoEuP3P98jPfqNfFn5F3jihnNzQ3ocbvnVj6t6Uo0FidfTPT/5BPRVCfd9bs5VtncGR/5sxddQf6D6d5GwHI+RIblwV58GyKyn6J34PatOmvTvWy+v4MvHZWSeK1gbXUR7S8LH21wOIi5bKl68cbHmjbUgh+bVBx3C20LqRiiMpreBWufAN/DCxnl+7cfG7eiL1nbg22d87F8+ojd28+Y7NLOWLXH7ozToKr8cTrsWpfPG7mryvwJ66du3W5OU11pb0zT8pH79q8b9pvFHCVHpqf81y9uCaml862BCx+HcQJoPVaicx5Dufsx7/ryMe/+4jaox//niO/duv8KbIJH3vFkRP52opgQrTaKQgtOI53WU9yiV86svjx1x7jdp226496T1t81LVH9l156MKrD59I1xeOfx7Son0FPfGqGfI7lmJJz52Z5juSiY/KJw+8XB2r8uPYMkz8FHjktUc+/rqjj7pueDP3808/T+MNiyvffPOJT77+aH0K9BGayZ/NLZ3ULD5u2+Zv2xMf1qBy/Xt+5PLFs9uqouHl7UtOLH3zLScvuebIZ99w7FB+evO/9w+36v+8uPpTRxY+88bjB648VHfKn7UzMgCogBY+MT8aeu3Cyh+l8TPEfGeekI/evnlX84r4IVnjNr5J+oq5ZZTjy64+/MOH4k3nx+3a6pJ9Sjw0D8R78rOsDkMnKy4hXqsuWL7YdEXZQllctHTZLtnntC0hiEZNMlZ78JbdNLS+dJXHLgiwtIgRN+1F4RNu5sU2um4hDuQ5OOogrKlsrU/V66+DK0oVA2XbJD9/23Chfs7bmw8iRLtuKajn8EYJsbCy35bn50GclKB4Fk1awWM14ypa80G8lVPBTI8lPHF/fLh5w+r66+aaWwMzOcRAJsJowfoHgLcur+FuyM82uD968dmzqAjSTo16BW34VbPl92W9yON2ChzFVE+D918T/3N57U16ExO4bPP0y87d/UlZtk4HlODfObGEW1qr2Lun666wbgm/YNfW2empT808fs1rvOaCvb985q6PbF4KAK9eWEENOqbd/MJdW9960b7/Nbtttpn24fXR9x2c+4GDfFPvM5uS/ZOHo2b9TH64DLw+J/bpmsB1WfLOad4DBYbTL5e38N7VdZTjqzLwK2e3/sW5uy2fEn6jALiZn2l3DJjmlYPNlxAuMMtUUbCySOF2Axc/a5Hf8ku+XUMlHXdR1vUJUFUqGquVF2nNpFcluAVzwjhcPfzmx/oIr5StvgevAXk3pI9KNo2O5Jvle/kSAmXLBcITG6sguABe9lC+rgEmb8FGo4uzYr4HxTdmy+1InkDMb7tn3iSIvWAIRvTzjcLJbHmb3vKg/W+5fH+0l+///jN3tDMEPmHHli+cjcvpf9qbWWdDXjSNmeA8NSWgSfbD+3a85aJ97RaOBs87c9eleU+3e3rqkeNfWNkIvIJ+YvOVFL9qNi7MPIueSaJ9Ye77xx15ndcdHPDGhagR7bdMCr94YNf+vKTPmJm+zXkCKIKod9i+bnYbCtx/X7wfk79+de01Oejc+uh3ji/irLCKF9TtbSzK/X9dvP8dF+/7uQM7H7Utzr13r6z7/UHg4ds2/8G5u0888MyXn7fnO/cMa/LyfGV6cS7IXGZFHf+YDTP3V20uzrcsJm7ijuSU2o9TjMdu3/x9zbE4a3p66v2+//DGrML72hfbHfkZCy6huh/UZeYr06WE57Q4gMpTGGWI8GGzxV4JLnnm28srVpUiwtnTgs0hthiUR6+6Zfnd83FyfNRufmnuMbvj+fy9i2v/dHRJ4aOr59f++JZ4v/yjQcD8AQ6hGlFjCbtnpr70jG2PPzCcRgFN4DH5xP6epbXfu3WBxnWMtfInhzL/LuRXQk+7Bd++1HoC2FnLnEMaE4/YvvkRO/hdOQrbt/CtLiYML4DT9eUX7j1jwwUQ2ZhwPMDwKONjXbB5mgM1WzgaPGXfDn8LBHjPyvrX3XxioakLp0R771RfbQE+Nl8Y4mX1q7IuHF3DzVos4Ccl4cPy/qtujoDrU/6wU736+/79mGfcZP370irmafn94HN3bn3tBXux/d65u+s7lS86FpMBfuboAl4U/2VO9eD6yO947r/y0MwVB6euOIjX3Q/buvkH9+98dfNNnWtX1//q5NJ2EbC9cm4Z94zPP3v2mfvj85DaqXor0Phk7f5X52cdhY/VjfZj8ui8Y3ntT/MrPu9cXv2TvHstQgFPDL901uwX5ov0Xz+++Iv5FciNuGJ5bYlnDnF7nlHuV9B9IhaH6+N65AqiashV82WPXtezBRrTjg3w1U7V1UebL1omNr9SMUBeWcIoiXnoNt63tPaEdx794rcc/rK3HbYFrx2+8Zwd4D/+rKGWPfY/D3/bu45+3xXHP/kth47nHR8JUY61LzErjSjM8l3CqT958L4Lm4+MA8h/xpD/G648/u1XHv++a44/9r+Pnsga8fh925Uz97cF10p2lnEJsSy5CIkvuuoYt6uPftHVaq85diVeEoJZkPzS8/UlxxYewtla/unxwuOLX3TjsXb74fyydAu8hPzqvDN9+/La0zZ8vnk78XW7t9fL6s+76fjn3XjsSbecvOjqw+/LA/SEvKn5tB0x3I1r6w+8+vBjrz968dWH/QYl8OmneaPwa3dv/6ysMv9vcfUn8gXpB4T2BfJG/IG8X7t7m6eC192/dWzxqpW1H2/G+vSdWx43uy3uuDZt+tkj829ZWn3z4uo/5V1YlcJ6VW58/W7u/nePf/G73i58fPOWxf9634nvuOXE99568tNvOFbTbQktntHcqv/gofnDG15iP+GWk19+0/EvuOnYLXkg+PWDjgb+9ECXK16BonVV8ob15PcQ88ILwW0VzXT5sgecjTRKWQ5UH4OAIUxTHpJThcyowPz6phfeOP/yQ0sLDsSLu8v3XI4n0tGmB2/f/ILLh0rxf9638Nwb5q/Pb3o/77Ld33Y2XodqbuhiJho3wXesOPToxZeNVxxFPXT7zG9cPLwd86KDC8+9ZeHGfGH+KxfOfotvMJl4bM4EWMPu13o2K5B4xcllbSspLMcXPwdwhp+xc+sP531HwEfBG785cNtAgXvF/Eq73dDcl7Vor6vnHlvc+NbV7QFehL74HNzSB141v/KC44v+NAPAq1cUTcs/ccbOh2UtuHJ1HdWk7q3On5n+yWYyE3hm4/qxD7wmvm5+uT6p+KNzdl91Cb/vgu17s069Ym4Z+/7sM3ZelC97v+PWk5ddc+RX8u7y63dvw50jhF/S/7QB/nFx9VHXHX3M9Udfm6+pvyl3c+I+8Yv18dTmqamHN29TfnJ+gveR2zb/SuZEcf2t40vPO7Z4c1axXz9r19efppB9zPYt37YnyiXYT9rwFUU8Nb5sbhmv+q1iZ3Fja7nD0MH2/RSu22hVO7houph5JesK9DKWCx2bvDgrajAq3F5s7jAWX1pWW+EVNXaF43zBi9xHz27+6rO2/8ejDjzh/J0V8oTzdr7u4fs/f//YEf2E3Vt+7dLdTzl3F+cDRGYJDBwvH7J/5u5tP3F+vGAklByTeeJZO1/74H2fx//nN+ATd215/oWz330mplE5/XQShMCwGl5AVUaTJpgT4FsTDYOBXP+fOTD72HyxeVcD1/l3Nfcv33HLbXz193TAxfZPF+79kl1b68vSs1P8QvKLz57Fq1dbgJ3TU2+8cO+X7tpa7/oDYOJ29V8v2tu+Hp/AJ+3YUre0AO5DQ7p9qJtEjPUVs1sfsIXfd8H2Vfl6FsXo908s7Z+ZfsMFe5+4Z3s7PezRD+3b8ZJz4lnzKft2/Om5ux+1bXhnDilQ7P7xgr2Pz2yXbJnZme7Lt0yfk3X2K5s7vk9q/k/hd+/b8Zrz93xO84E1gFfcLzhr15P2jj9BjuNZzVPFS08uT3z1B6PumOJn7ljwV523x/8HqaPF1OhxF+RVqpIx/Ce8vHpZR9LEHgT/TIsuezoVMqXL3mQ0uJj5X9kElIaACc7jLlWPa6/dbFxKlFM2EqhZCeLNy6vH1jatro8esHVmJz/mBDnvtpAWo8fcPEoOETMRDwROkoxI2nhvXl4/tr6OM+sBm2d24LSnreFUqjBKpNEnfTPuRKB3zTtOVXao3GvrQvb3auCG6+qVtUds24w7ozCdCjetcqkPzEyfNf556z0EeCl67er6g1HdTl+p8eIaPtTW0D9ovE9rgtOTp9/px+24szA1+uLz86rDcvvyVutrNVzobEeTR4W2up5tDHYa1QKtoDRRDmgf/y/JaH3N4F7JdhI0H3LoaSCd9pwSFNdiWuBAi9qNq0vBNg4u9YMlQVEzIUd2iqYVUaoplu2w13bWYimcgJ5CwodmIn+tdlo6OjruJkyNvihr4sSFDdS1SlkPciSTi06SCcMV7r65vMEcC7SEBvnlZZKyeMS0ANFPqBZydJCHzDbKIc2iWimhpowRqeqdU8NGarV35lhEoNEaFYKK3IKz8o6oVS9+TVttdh0dHXc7/P1EX7G6aN3aCIlCqrZgM4eC3ymDqAykqaXFbxdmrGUAKiTbHcJs8pGT4Xyt0NDoUp7wlqBYh9vCKaGF0XUn/x8LQzzJjB0CsQhycbN9U7xFyLf2ENJGafO4TJsbjUyWSSTAwvdqMxaTGZjKENSOjo57Cvy+m75yyGu1vfL9PUQpbLIKVLEIsmpf1QjRJSeNgWnEBhLTYlBXKMj6QngYHQumkzvEgpOYZlVt0JzBc9DoTILZovUoGTtEYctR0Hq/HCtJuwCCbv0iSe4yQAGtsjmQVRiSXLb7tpCxHjTJNJLY0dFxT4NeMPLy1tWOR1z2EBuBVUBXM3tcz+mirFahYwTKVm1xq/+Ex4QQZMRj8OquirLKkyeANqqwOE5Iu1ywQLcKIWgpR5lTabKEhoUpE3qDbG/1tkPiiB40K6MJFCSXkbMdnwn3JfmydXR03PPhCuXa4YvflzFadLy+Jbs86cIOb9rJUjmwUKnYYlPgkASVRVngAiCycPhmyhyKEnD7GZHcPHQZPBxosNgVsiqXjbTX63d9B2gouJ4AWrkrT5Q/tc7jcG7pgsz9hay9Do5IZsJiI2BLR0fHvQe+T9TGC3i8ornY+crn5lrga97lwExXCmSzUVt45UIxGpKYKS9LmDgsWE7uDJBVXjmrHIit03pTZoabXEa/lvdksvxZxUYZ9sxG1bFiwuiN9mozQ9HCgkcZi0BbR0fHvRdTo886L67kup5xzbNY6Z7Larwvpos/PqXFQwUiXLQRFGQMi7pIVRmSNshpoZ4WdDUHm6ja2MRSaJgRmwLbNA0ukb2bFJoW3UQqtPEZtACBlpQ7OjruW/Dnznmpc9MVH0bUAtUItKGKEy9CrZrTeHHnBTIkWCxTcAY+BiYgg/gQilxTasb1TR+ZNurWkkkyG72WJTCJxuIcxOSOQBaHbg0Xm4fAZmZlsOzAzKMxQ+jo6LhvQZ87x+Yq4KqBG6GqJi402uBCQQRg5Cti87NYkInOXhgzqjbTkAQ+NdGicyw2qjDkoHhwFEUVP9rkeCzbncECi6CTVwYb8yP1GE5GRpmplmYRIhU7bR0dHfdl6P1EbC5wuOarCrhGtJVoKBbYVDSxBT+/YTPQkoqeRVMaAGEoQC5S9WZibY6S3LbeIFOFpNYuPIJQgozhVQkmPCgMrs5oVb4Z5UC1HqKjo+P+h/rcOWtBVQQWiNxCVR0BswiuKdiIKiveWprsa1mhSFMqqIxrooLgTQk3WiDggbbqNTa/jsZ8hntGe9FKqI18cJQKusNdIr11dHTcjzE1euy5rAQoEwArAh4Tcus1XJhs8Q2jMBjRolMUdITXJzP2FoKs1p3LpT/W8LjS2dJfOdVD5UADRXb7xLSXAh5OghalUaN0dHR0jMPfxdG7iigULhmWabesugOBG4S0YyPs0r1YcLSFKwPh9RZqO4qNSkuOQihnUSNBr80pw4ubO3Nkp8D45uU/G90eWs4X5rRAgFGjd3R0dGwAqiEKhKoM6kSUKlQNVRM8yh4lydUwaw0trR2vYa1WiOpU0HKDjAeTYMvCF0xqIsjCbA7J/PSqXJpMu5IwxJayK4TMfIHc0dHRcVvwd3FUZVw4UM6AkFVrWG5cv0pQjfNt11D+ZAfId6mSy6mYzYIz5P/Y481dVkw8zMTDCcOiFhZvzkCXNsdyDlB92yiLo/hQ2e3o6Oi4HVBNBFxcLEeVkWTBluJQlsp7MZU/6FFAJfv/MsOCzrWJbYW7fsnOQVQcI9yENDrEd3x4WChaG0JvMaV2dHR0fODQ76260LCmqLSxtVFFh8XLFccFyMYsl+holGCayxktaqMCKjzyoNW9W3wJRq+jXUyL5qI5pJVsgQkVCJdHcWBHR0fHBw19uuLbOpYbtS5q2KIMWdX3+CAFTUYw8OIXEpj+iCOi1Do8VFfSsivQXg6nzLTbkjk1QkS1OaF4Av1lcUdHx50KvHZuy1zJKj2uPqxEqmiUZaQl7aCgja/OqGahg4BHBdpYAmUVM6gR1YQ7P2QTvNnb0dHRcRdD94muhi5zfj3LSqR6F6pKIQWoskcIBJUzf2DirUqYQyCZA9CbVRIbKmlVQ2RwIFrANEd1dHR0fKigz1hclbzhEbJKnlU8WKR8c2cC2qaKufyFXRwYq/yFpThO6/z2qg2XW0sdHR0dH1L4//a5hLkqudLJAtU3gLbQiLZKIdkKURvhRUZyhVj2tslVUn9sgEzfXdJhd0dHR8fdC33uHDVO5Sk+b5Ec5QwtileWQttNZoGzUZtVSEVjVPuyOl1i8SGxo6Oj4x4Cv5/YlkVVrrFKBxXFK/83SLlc7PCgtyl2sHMzU1SSFUK/OB0dHR33SPhzZwiqawb6tqhBBlwBbeGWAl8Fuwi6kqa3iqC3jo6OjnsDfJ/oEuYf2vL9oMoYWtiho96pjwJKAa1fFKP1rWLZRe3fHOzo6LgXAjXR93fYfIvnSpc1Dg/fIfor1jKI39RBmTs6OjruA9B9YtVEdFXj/FrY5c/eUHMD2Frq6OjouC9gmq+XUddc76Lq+V3CZvOtIjbAbUdHR8d9EfkbENzyVTMKIlB1EJseHR0dHfd56DvbLH/r8WLZ1a/XwY6OjvslcJ+on+rCTWJHR0fH/R7TvRp2dHR0FHCf2NHR0dER6DWxo6OjY0CviR0dHR0Dek3s6OjoGNBrYkdHR8eAXhM7Ojo6BvSa2NHR0TGg18SOjo6OAb0mdnR0dAzoNbGjo6NjQK+JHR0dHQN6Tezo6OgY0GtiR0dHx4BeEzs6OjoG9JrY0dHRMaDXxI6Ojo4BvSZ2dHR0DOg1saOjo2NAr4kdHR0dA3pN7Ojo6BjQa2JHR0fHgF4TOzo6Ogb0mtjR0dExoNfEjo6OjgG9JnZ0dHQM6DWxo6OjY0CviR0dHR0Dek3s6OjoGNBrYkdHR8eAXhM7Ojo6BvSa2NHR0TFganT5mSHeRzF1xcGQNm166bm7v3J222fecOx1Cyu2jDZtWtMKbL7i4JpNmzb9ztmz37hneyh3Kv7P8cVvu+VkKJs2rT/wwNTUVCgdHR33ANy/7hOPr6MGbrp2dQ3lz9v6pk1H1tYX10dVEIHLt8yE1NHRcT/Dfb8mPnDzsI+uibeusS0cXBu9a6UtiZsuG6+Jx9dROW8b80q+ESea8E/ZvuWFZ83WdrqbxDakcGztdk2jo6Pjg8F9vyZevnUocLglXB6Njo4Xr1vW1t89XhPPUxn9u/nlL7rx2PYrDu698vDWKw4++JrDP3tk3oT3LK+deeWhA1ceOuPKQ/uuPPS2pdXPvuHYN9x84im3niz7JVcffv7RhQdcfXjPlYcffu2RV80tI/D1iyv/++BcbaPR6DZDgF8/tnAWBrrq8COvPfKHJxY//JojF111+LyrDsH4j/kmQEdHx52C+0FNbG76Dq+Prl2ZvNu6cW396vGaCLzs5NLn3Hj8FfMrS1JReN6zsv4jh+a/9KbjMmw6tD5CtiPro2Pro+++9eSrF1Y2656v7Desrj/54Nw1qxzu7ctr35pvIx4fjWqz5f2HvHt57btunTuoOv6fy2tfe/PJd66sXb+2/r61EYybmaCjo+NOw/2rJh5aW79udbL83bS6jmIUyqZNl+om8aePLFgFvmH3tpA2bfrLueW3Lq2Gknj9Ii2bN429EEYN+5rZraGo8qK6hXIanDLkTYvDneCeqamvnR0mA7gQd3R03Fm4f9XE61fXr23Kn4GbxJuat+oeuGVmfTR6cxa+M6enfvec3Y/aNiR552lK20R1+rjtm19yzu5QhCO39b7kKUMONu9+PnzbzO+fu3vi3vAvTi5NXXHQW5g6OjruKO4PNXHYR5Sz9pbQuHJ1vf3UBTX0RPOGoz+WadF6C2fPTN6w7ZmetJzuQ5jCKUPWePsY2N6/uNPRcRfj/nWfeHh9hBekls+fiX2/cmXttc0nFQ/cMg3G181u8/b42W0vOrb4H0vv72Xv9+zdfvOlB37v3LFbvI31b/Il9wbcgZCOjo47F/f9mrh5/N7qprxP/Pjt8RoUNdGCcemWmf0z0yhwP3fmrku3TKNcfvutw7esT4lvO9UXvFc3VLiNlgncgRDgwVtmnrF/h7cwdXR03FHc92sicF7zwvZf9HkI8Ok7t1iYG687D9jM+8oXHls8/+rDzz6ycMPa+sTr1fUN93N7N7zmBVbzY+XCbXzCcodCgA/ftvknDuzyFqaOjo47ivtFTXxg8/IZNc7CY7dHTZwA7g3R/tjhOavAGy/cu6spessb7t12nrImRv8B4Pa8uF4bjfoL6o6Ouw73u5pYuHzLzIc3X+cunDEzvbg+el9+6nLm9NTHbd9yTr75CCxvuJub/NRGuD0veydwypDZpuBes7L2N/P9S9odHXch7qc18eLN09unpx6xdfIrz7OqP4sbqt7+pibentezwJ11n/jhzSTfu7r+Rfml8cLbllZ/9NCctzB1dHTcUdxPauLkbl6kL2ZvvE909dw3M9ybHVwfPf/owq0f+P813vjm4G3ilCGP3bHl8c0XuR+0YV+uWFn7qSML3sLU0dFxR3E/qYmTte9c3fdtrImXJhM3khaAJx+cu37DtxpvE7fzdrLF6UL+5Nw9r7tg76+dues15+959yVnnHWqty87OjruFNxPa+KDZPnwbZOvnf0f+4BfO2u2Pq3+mtmtj2j+H8uHGL98dGHqioOfesMxlOanHpp/0+LKrc13v/dN3y+OYEfHhwz3/d+U/WDw9qXVczdPnzkzvd68qp3WFx43WowJ++loLd5/yFuWVh913dHQx/F5O7e88vy9oXR0dNwZ6DXxXoBfP7bw9EPzEz9x9tWzW1989u7t/XV0R8edil4T7zV4w8LKwbX1VX44PvVx2zfvaz4K7+jouLPQa2JHR0fHgH6v0dHR0TGg18SOjo6OAb0mdnR0dAzoNbGjo6NjwMyzztgZ4geHpx2ae9Pi6r8urrxR29uX125eW3/QqX5k4a7G0w/NvWZ+5TN3Dv8fbiPeurT63KMLj9m2+fZ/l+WfFlZecGzxM3Zs2fgHSP/i5NJLji+9/xGfqv8M84gN3xIH4FocbXrwXbNWSH54bbTx2+l3Oo6trX+gXwz605NLv39b69bR8SHGnXafiKLwo4fnf/BQbE+49eQX3HT8sqsPX7vhT+JtxGOuO/rbxxdDuUP4pptPfN6Nxyz/3fzKK+bjr4CeDletrP3M0YX2T53cJt61zJAjp/r7Af+1tApXKKcBCKiqoYwDro1/9+rOApK/5baSr49Gj7r2yO+fuOOH4HtuPfkJ18f63378x+Jtr1tHx4cYd+Zr5wdtmR5dfmZtf3zO7qtW11Ecw30a3LK6/ual1W0f3FeP/2Z+uf6YyYvPnv2j8b/0dFfje/fteNtF+0I5PRaa/6DSArFP2Xd3/kT2TWvrb1le2zL5V7Y+ALx+YWXjH5Pp6Lg34i58P/Grdm/77B1b2puUf15Y+c1jCy88tvjvzd/nfOlJ/gnlNyysvqS5VXzl3PJPH56vP/pu4CXq6miEl+cvOLbwBycWb8y/SorAufXRdavrznD16jo2u4B3Lq/+yYmlnz0y/4cnFm/zpnU0GmEUCK+ZX37O4fl/a+ZZeOPCys8fmf/bZm43r42N+Nr55V89uvC7xxdx/xgmYWHEv3yA2zHM8x2NC7HIAAFT9ejefcxB/gFYN8zqH2QH8+Rp/ujV+1bXX3x88a+UagLvXl77veOLWEAsY5g2bfqzk0z4jwsr7SH46zmuQLubBg7B38wt/9KRhTo6XP/RaOf0VBv+psWVXzm68Mcnlib+tAOAgbA+uDU+3ZNER8fdiLv2MxbUqa357tvTDs19wg3HXnBs8UXHFz/6+mPfpT/o/val1Z/Xq6dXzi//+jFeUW9YWHnktUfwuvufF1c+76bjn3D90Xcts3wsrI++7H0nvu2Wkx97/bE/P7n8jTef/Mhrj7rgPuPw/PJo0w2r6xCg/vjh+afmLwk++/D8h1979McOzyPka28++dBrj7w5//bAKfHqhRWM8vj3HX/Gofl3LK998vXHvvZ9J8InfOstJz/xhmN/enL5c286/sU3xk8Z/tGJpfpZwyfccvLTbzyOkvQLRxcecd3Rpze/aYgX7Bddffj5Rxe/8ZaTH3Hd0VdkTUEsqjaEl55YwuiffcMx7P5fzC1/1o3H60/sA4+76TjW7R8WVj7jRi4LmH93qrcI8JRz3tWHn3d04YcPzT/smiNhFX7hyPxDrj3ym8cXf+/EEpbx+3QLjzX8JR2CV8wvv1BFDTd9H3XtkS+86fgbF1ewm590/dEr8s+3YvUOXHn4ibee/Ov5ZRydL9D7FVj2q1fWQdaBYJn7pptPfPz1x1BVsf6Yw+9krVwajR5z3VHs1P+dW/6o645iX2zv6Ljn4E6uiWujkbeDa+u4OP97Ze1xu/gOOm43nnNk4Sl7t7/l4v3/etG+vz9/z68fX/yNYwsfsW3zv1zIV52/eOauN+nl5zMPz2+Z4v+uefn5e9ceeGB2asqVznjtwsp/X7zv1Rfs/e+L96MO4l4MxmsecAZeen/Wji0QTDNwL4Zszzpj5/9cwkH/6YK9uFP7k1PdPU1gBre0F+37/XN3v+HCvX94cuknmwnsn+bckO2H9+14+fwybnnCIeCeCGXlD8/Z/R8X73/bxfvB+ekjC0fytxevXFnHHJD5yKVnnDk9hZs12yfw0ds3rz/wwL9dtO/nDuz8y7nlt6nuP/XgHNbwzRdy3+H1b53V803hdfPLT7j15Hft3f6fF+/HXrc/HIk8P3ho/pfO3PWGC/f9vwv3/fOFe597bBG324/ctvkNF/CHJH5FLgjPPDS3S7uJQ7D8wAMY5Rn6Swwn1te/5KbjXzG7Fev89xfs/Zvz9rxyfuX/HF+EevHm6U/X+oOMyvu7J5Zeed6ev71g7zsu2f9ls1uffOtJ/wDlV73vxJZNm664ZD/24h0X77vxA/8Fto6Ouxp3Zk18z8r65vce8nbWVYdxcX7Hnu2/etYsXLgveOTWmedJBj5959aHbJn5Q90cGf7o8b+XV1H1vmb3Nmn8VZgv3rUVN2XH86/Ff/3ubQ/V704/aOvMw7fN+CUnsG1qaseG97MeunXm787f82P5wTqu20s33643vb5pd/wdvsds34L68qrmdux78o2/75NwXb5+N/zHqv785BJutSD8zJm7UFnqN7rx9PDxO/hHYPbNTH/Rrq2nqwhYNH+0/bn6QNbVBPfR379/x6P0twbhRWGCsPHg/cfS2nkzU7+W6/x/z9uzJ+um36N48t7YtY/bvuWhW2ZeqlfNBp6K0KJ0/uPi6tfMxiGAEYfgj08uz6+P3rq0dv3aev2Rws/dtfU3z5r1r65h/etvT78EN6HbNn+enguB5545Ozfa5JviV88vf/ve7V6Qh23d/LH5pxM7Ou45uDNr4oO2TP/ymbtwu/F/zp597fl7bnzAGb95dlyc71pZO2v8Nwu+dNfWt+crsoIt339wbuqKg96++yDvUN6ZzPa3YQ5MT9efn2+vycLM1BQ4P3Dw5OffeOyyqw9fcs2Rq27fjcmHN7+WiGn/R/Pe39m5F/4j9xN///7AzPQLz5p9zcLKp95w7LyrDn37LSf+vqmnlzV3bftnpjb+NX1g59SmS/LXHs/UWCfXR3hB+tbltRoa+MTT/IGtty6vYg6hqHqemb8C+Tat4db3Hqq1/Z+VNTwJ2VvwIXhKcwi+Lw7BKpJDeHjztZ7v2Lv9saryuE/fofVfH40w0L8srVb4OVcfhh1H8D3La/OjTWc0z0pfnpW3o+Oeg+H6uVPwvft2fPe+Hd+yZ/un7tx6XvNT1bPTUxNvfS2d6v11Xy4vUUmt7XUX7MVNjTyb2r+gMto0Wswcp/zY+j+XVh99/dFfOrr4+Tu3/uG5u09eduABzZTeDzY3n8DiNu2CJgo1q8XShp3AfdCtl57xp+fuxp3gy+eWP/PG46gFduGVe4uNsUDtUQEWvCDFTVdbfzf+xRhj59TUyrin1XCn3C4stt86e/IDeu/fH5wzeQhwy2zXSjM0bh4t4DnJtdecr5vd1oZjw2rs1Cr2dxA77uG4k2vi6fDwrTMTnz/iJuXRuuNo/vZy/JkUFAuUVG+4wfxh3acYy801jhJTpQHXpIUWf3pyCQPg1euT9+1AVcU1ffXq+sSvEJ4S/9J8JvtfS6sff5qbMmDiL6i89MTS59xw7Ja19a+Y3fbCs3e/Wj/4+udz8RZB+zHrajP5FhvvY036mO2b2683vvY0n048Ytvma1fXDuZbCjesrl2Tt8YoiJgY7vJqbX/+6MJzjvCt0s3N6m08BO9YjkNwkf7ydf2BbOCSqw9/wvX8vdt6TsKdKTLMjUYV/uCtMz96eB6zumDzDG6u/faocdd9K7Oj4w7jQ1QTn7R3+3Wr699xywncdh1aW3/Wobm/mV/5/uZLeVeurr9ybvljt2/58l1bf+LI/Ovml/GaEe0PHpz3q7ONQImpuypckgif+MRj7/QUrjl/QoKK/BU38RPk031/pQWu4b+dWz6xvv4LR+bfsLj6Lfn+5m0ClevvFlYw0JsXV69eWftVfYryGTn/9hvimvxtz6TwtP07X72w8oxDc29fWn3yrSd/sHmeaIHbsfNmpp90y0ncnB5dW//fB+eqyP7Avh0oxD9wcA5LcePq2nfdcvKV8yvP2D/8L6b3rqz99dzyJ+7Y8iW7tj778DxKMNbqtTgEh+Y+fSd34Qt2bf30HVuefngON+DXrqx9w80nDq6PvmcvDyLW/+/nV/5Z6//M/Tv/Ym75l48uYAKogBjx6pX1r9NbtN+1d8ezjyy85Pgibjb/6uTS8/RNA+P5Rxe+6MZjbzxNre/o+JDhQ1QTz98884rz9rx+YeXsqw6fedXhPzi59Ifn7P58vQ1/1sz0l+3a+n0H577iffzeyW+dPYsL79NuPL77ykNoP3XHlp87c5dyTGJhfVQ/0/+Vs1vfubL2KTcca//A3g/t3/l5O7c84/D8GVceesg1Rz5u+2YM9Bd51/Z+8Mhtmz/3puN7rjz8OyeW8EIeNzvhuC08YMvM88/aheLymOuPXnrNkb+aW/7ts2c/+lS3majmp3ztfDp83q6tv3v27J+eXH74dUf/dXH1pw9wTSZeyAPnbZ7+k3N3X7u6/uBrj+y/6vCBmeHPtVyyZeZvzt/z5qXVB15z5IKrj7zw+OJT9+9ABYQLUY/btfUpB+e+Rl88etHZs5+0Y8tjbziGQ/DpNx7/rJ1bPRzwO2fPnjMz/cjrjl5yzZFXzS0/58DOr9ITxuNmt92wtv4JNxzDbfW37d3+S2fuwq0lJvCR1x19y9La/8m3lZ95xs5v27Ptew/ObX3voSfp83HbgePro1f0P13dcQ8Av3IR4ocEt6yub57i35UPPXFsbX1maqr+vjvuI3A7g0vanzMY4OwdV9GWZW00wuvi9hMG432r67gpqz/Id3x9fc/p/67T380vf86Nx9960b6HbZ25amX98jv0f5BHo9H1q+t4OX/27Xv7ssXGfbT6spNLH71t80VbZubX+e1o1KPPu+n4f1y0D+XbzAnctLq+f3rqlP8B+fDa+rH1EZ6K2r+mD0wcguXR6KqVNaz/xiXFGh5ZG9VnQQU8IbWfpF2/uoZceMkceoMrltfu2Np2dNzV+FDXxHs4qia2n67eE/AlNx1/5/Lqmy/at3t6GiXpM244fnBt/arx72N2dHR88PiAb2Q67hb83IFdeK2Nl/Mfce2RvVcevmVtHS+Ew9fR0XHnod8nTgK3Yw9rvvFzj8Lfzi0fXh89atvMQ+6pM+zouLej18SOjo6OAf21c0dHR8eAD/Y+8eQ6f1zrrctrh9fWL9sy8/jZbXfLb2vf0/C6+eW/nV/5gf07zpyZfuppvksIPOdUXzNaGY1++sjClL65EqZT4TXzy38/v3LKDKfDq+aWX79w2yGY8KO2b/7K/h/vOu6X+KBq4h+fWHrSrSePro8+YuvM/umpf9L/cHj+Wbu+U9/jvWN48+Lqp95w9F2X7D//VN/huD34ghuPPWTLzC/l7yDcLfiNYwvfeevcOy7e98AtM9vfeyisG3D8sjN2b/hikGMhvOfi/e/nCyvPO7rwvQfnlh94wL/dcHvwC0f4E+i3ecSnrjj41P076juJG7Gun/z6/v07/E3s24+/PLn0pe870d+u6bgn446/dsYdx9fcfOLDts7g0n3bxfv/8cJ9116y/xt3b/uuW+dee6rf9buduGZ17eQH8mXmjXjl/MruU30v727BNv3umbc/1q9//945s2XZWBCBFx1f/Ex9lfr5p/kxsTuMJ++9Xb8HDo5/9ed0uMO/y33F7fhDFB0ddy/u+N+oesqtc1evrL3y/D31Gejemekv2LX12UcWbllb/9q8g/j7+eVXzi2/c3kNV/85+R3mf1lc+bfF1Qdtmfnr+eU/P7m8Nho9QF8Aftfy6p+cXP63pdVLNs8sj+IXYt6xtPoPCytgXru6tn9mqv3GNV6w/9+55X9aXFkdbbpI5JccX/zLueX9M9PLo9FHjX/H8M/1Y1ntl4p/5/ji3ukp/3QV6jjm+XbN89yc5z8trPzz4upHNHlwa/yQrTMzU1N/Q/Lq+mjTb59YRG24ePwLzP++tPrX8yvftXd7O9w7ltf+bG75y2a3fuTpv/z4dv7m48JPHti1vmnTH5xYesaGo/OmxZWXnljeOjWF+sL/7XfGTkzmL04u3aD/1/xXc8tvXVo9a2YK1RapqC6vnjsz7SeJG1fX37Wy7j+GhZC1EX8h/FXzy/+6yL/9UN8w/9el1a2bQr1VK/w3+q+WddP64uNLr5pfOXtm+tDaOhYZqSC/dmHl7+aXL9o847FeN7/8irnlNy+t3rw28ohY4b+YW/7vlbXLNvPr4v7e/j8vrLxyfvk/OJdRvTK4cmXtdQsr581Mv/D44sEM7+j40OCOv3be8d6DX7Zr2x+cO/nDKkujUf0iwxNuOYnT+mO3bV4cjf5ree3p+3fgaof9u289+YJji4/ZthlXyAM2T8P1v2aZ6lePLvzC0YVrV9cv3jz95bu24vXvsw7N/fiRhYdumcFVhEKza2rT/7twn4vdH51Y+l+4Ud0yg+v5P5fXfnDfjmcf2Pnga444HISJn5jFS8LHz279k3Pja31+Hff2i/Z9+LbNT7715POPLX70ts0roxFS/dC+HT+rN90wz187tlhL9J9Lq4+87uirz9/zmTu3foV+hhqF+9D66BFbZ95y8X5zjHrt3H6t509OLH31zSdwn/h+XnJ+760nn3ds8cRlB3Ab/oU3Hf+ts2a/rfkPcJ9xwzE8l2yf2nTr+uiRGHR5za+dH3IN/xvffy2tYkn/cXF159Sml5yz+yved+Izdmz5+4UVFLjrHnAGalz72nnzFQc/d+cWFO5HbZu5emX98Prod8+e/Qb9NmK9dn7z4upj9BMPX7hzyyvmVzDin5635/j66EtuOu5Fvmjz9Bsu3OeF9U8xvujs2Y/fvhnH/Q2Lq5+1YwuqNow4lH923h4sJsqrA5H/iXt3PO3Q3HOOLGD1sAt4IvzOPdufr/8F6Hl+6o4tqIxQ7/Ap2tFxBzDcxXxAuHZlbXHEH0wMvUEVRNyFoSD+9Xl73nTRvv+8eP+fn7v7p44s1B8JWd206Utntx697ABcz9y/4w9PLiHnd+/b8ZwDvDP6twv3oSDiIkdB/Mkzdv73Jfv/7aJ9rz1/z1z+UDZKBgril+3a+s5L9qMe/eyBnT9/dOG9K2uug0/cs32iIAJP2LMd1239F+nfPr504cw0CiJqKwriX567+18v2odUf3Xu7p87uvDS/L3b93OLctPaCKVz7rIDuODD9EEDBREFCE8AuOOenRp7+fxNN5+4bnXtnZfsu+WyA9dcsv/k+E9I4H4W5f71F+57y0X75kebUBD/4JzZ11yw9z8u2odahXvn4DVAQcTL+TdftP/QZQc+btvmlzQ/8Wtg9Edv24yS9PLz9x669AyU4Gcfnt/4u9wAbqjfdOHeay/Z/617tv8E/47N6snLDvzdBXsR+1WzW/98jr8K/KtnzX6Lai4ODQri6X563QkB1Hc8NyBt6B0dHxLcwZron716/2/b4bXq43Zt9Q89AF82u+0zd2xpf9j5O/IXm/0Xfjf+ZdGHbp352/P3PD1fP166ZQZVzEPilu3M6ak/z2L0Q/t3vuTs2XqH65T/z/cH9/M9sj/VBI6urb98fvmpZ9DyspNLn7Nzy+PyY9Yvnt2GquTKi3u8naf/BAO3YygBO6enHrjhf/7eMfjVvQsH8A27t+OmtX72FS9Ov23P9gv1AhMv1T9h/KclcLf1hVrqR2zbjNvqL9619X/pbhQlbM/UVP16WAvspn/BAQDNr75b7JqewtPSk245ecPqGl7qHr/sjN9p/iBi+9nOl89u+9jtW/z2xY/u34lqiFi7/EPcG3GbP72O0wPPDUgbekfHhwR3sCY+ZOvmA9NT78hfSz0l9NvaYwXl7Jnpt+UVfv7M9L58r82/BT1x4wPgltM/lP15Nx679OrDD7jmyPV5bb8NNbF5qw74+j3b62tAp7wKUbk+Eq/+VHd+T9fe16sicJ7jn3XUPDGBXY1nojpedieVwoL/VtS33HJy/5WHsOHeGepPH6bxyNo6XnW2P8eAe+SQhPqRC2DLVPwMorF1im9ohNLAf8XBwCGY+M1w4Bn7d+LZ4gXHFy+8+sin3XDsuUeHn/aawKXNlwQ+Ytvmq1bWvvHmEx9z3dG97z30k0eGW7/2bdTb/On1S071KqSj467GHT/tPmb75lPWxIdec+Siq/hz87NTU8vjVxmuzCorG3890H/yrf27S34/65eO4uXk1j/WD2XjPjF84z/4DIxSfT+F6ol7t+N13C2r6y89ufSVs1v9sS9uRtqfqgWWRlH+tk8h2zCfiRE33Nd+UMCs3ri4+hFb+R3P2j5x++bfV2X0L/t7iYyJwdt7PKztYlPgcFN/yt8l881+YeMROXvz9CvO3/ufF+37qTP4A4rPPDz/LTeP/RXDwnoznSffevKbbjmJIvuj+3e8+aJ9v3cO7wT9s5XbpoYTTss+honC3Z4JHR0fMtzxmohXZ/+6tPo83doU/uzkEp7/v3UP778evm3zu8eL5ntW1uq3rSYuyEL7VwRQuVCVRpef+V37duA1FDy4TzyqUoQXj1evrtefeL52ZW36vYd+RN+ORsjp8CR9cfIn9UuxvkkEHr51c/15AOPdOc/9M9Pt3et7V8ZeXZ5uF+4Ynqu30n7vnN2/efZsbX4d/YJjC9unpx62Zab+Lg3wX+NzXmiK4CJ/xDtkYGE0VkwLbd2EtPFe8stuOv7EW05+1LbNTzuDL4e/bnab/xJ0+7vcRv1ZBQz0G8cWn7l/xy+eOfvFs9su3zrjc2BVBNx319F5Pz+93tFxN+KO18Qn7t3xuTu3fO/Buacfmnv9wspbl1Z/9sj8k245+dHbNv9v/Xrzk/duf9PS6tMOzeF1H26CnnLryatW1uvvxk38cZIJvPjEIhLunZ7Ctf0z+n38966sPV4/Ousi9b37th+Ymfq+g3O36secv+bmE7g9rHfiblxd3/gH441v37Pt144tnjU99UW7oiZiSm9ZXvvBg3OH19aR7ftvPYmhn6Lq+Yhtm4+tjzCBq1fWXj639ET9QeTCnVsTn3Nk4cO3zmDE0AXv0U/r5ecT9m7HzH/92MK8fv66/QurQDsZ1Mf2zvcUbyUK7QLpDzmEXMC93m8eX3zpiSWszD8trLx+ceUz9YPbhn+X23INgZu7PdNT/7i4Cu/qaPSyk0vP1uTncnoY6HeOL960un6bP73e0XG34I7XROCvz9vznXu2//qxxU+94dhHXXf0Rw7NP27X1pedtxsvi+D96O1bfv+c2RcdWzzjqsPnXH347+dX/uK83Y851Vvm7RtZH71ty4dtmUGqHzk099T9Oz5n55anHpo/cOUhvCT/pB1bcHOKywy0PdPTf37unrcvrZ2tH3M+vj763XNm/UW2b9i9/WePLnxW/kH6CXzHnu0YzV86MXA/+yfn7P6DE4sHrjqMbK/iPPd8gr41/Sk7tvzIvh2YwKXXHPnWm0++YPz/xiycrth84PhbFZdvbWZVwN0ZasfbUab37fjf+3b8+OH5XVceevz7TtRfVTXaO64P6K8aGCij8UZvg+efNftFO7d+1c0nsDKfcsMx3DtjkWGf+F3uCfzCmbveuLBy+TVHtrz30HOPLvyVvq31Mt1gftz2zfunp775lpN4mnk/P73e0XE34s75XZzrV9fm1zc9YMv0Kd8DwivcvdPT9UEkwBuZ0eiUPyhtvG91ff/MlL/WAxkXub/UPRqNToxG7de2cZeBCjDxi9bIj1eC9RlOi9fNL3/ajcf/86L4kmML3LzMTm/a+H9L5tZHb1te9d8ORFrPauJXvj+U+J/l1fbjEWBiMhvVndNTE/8FcIJzYn0dz0yn3B3cxB061RenkcG/yw1hIhDZbl0bHZiest1/nruO2g2ra+fMTNcL8FP+9PrGnB0dHxrcOTXxXoErltfO3Tz9UdcewY3hX9553yjs6Oi4L+F+9FT81ENzu688dHBt9ON39L8zdnR03OdxP7pPfNfy6juW1z5jx5b+oqyjo+N0uB/VxI6Ojo7bRL9j6ujo6BjQa2JHR0fHgF4TOzo6Ogb0mtjR0dExoNfEjo6OjgG9JnZ0dHQM6DWxo6OjY0CviR0dHR0Dek3s6OjoGNBrYkdHR8eAXhM7Ojo6BvSa2NHR0TGg18SOjo6OAb0mdnR0dAzoNbGjo6NjQK+JHR0dHQN6Tezo6OgY0GtiR0dHx4BeEzs6OjoG9JrY0dHRMaDXxI6Ojo4BvSZ2dHR0DOg1saOjo2NAr4kdHR0dA3pN7Ojo6BjQa2JHR0fHgF4TOzo6OhKbNv1/Oryz0fTVz6kAAAAASUVORK5CYII=') - - $AlertMessage = If ($Request.headers.referer) { + if ($Request.body.Cloned) { + $AlertMessage = If ($Request.body.headers.referer) { "Potential Phishing page detected. Detected Information: Hosted at $($Request.headers.referer). Access by IP $($request.headers.'x-forwarded-for')" } else { "Potential Phishing page detected. Detected Information: Access by IP $($request.headers.'x-forwarded-for')" } - Write-AlertMessage -message $AlertMessage -sev 'Alert' -tenant $Request.query.TenantId + Write-AlertMessage -message $AlertMessage -sev 'Alert' -tenant $Request.body.TenantId } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - ContentType = 'image/png' - Body = $bytes + StatusCode = [HttpStatusCode]::OK + Body = 'OK' }) - } diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index aae4096c53a1..11bc4be9bb15 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -9,6 +9,7 @@ function Receive-CippHttpTrigger { $Request, $TriggerMetadata ) + Write-Host "Request: $($Request | ConvertTo-Json -Depth 100 -Compress)" # Convert the request to a PSCustomObject because the httpContext is case sensitive since 7.3 $Request = $Request | ConvertTo-Json -Depth 100 | ConvertFrom-Json Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName From f1f1a1f2e4c8cb452a42da3b21d2f33ba5b561d7 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 9 Aug 2024 12:03:38 +0200 Subject: [PATCH 06/52] changes to phishing check --- .../Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 | 7 +------ .../Standards/Invoke-CIPPStandardPhishProtection.ps1 | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 index 19d780a20ddc..7ea80422ab0e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 @@ -8,12 +8,7 @@ Function Invoke-PublicPhishingCheck { [CmdletBinding()] param($Request, $TriggerMetadata) if ($Request.body.Cloned) { - $AlertMessage = If ($Request.body.headers.referer) { - "Potential Phishing page detected. Detected Information: Hosted at $($Request.headers.referer). Access by IP $($request.headers.'x-forwarded-for')" - } else { - "Potential Phishing page detected. Detected Information: Access by IP $($request.headers.'x-forwarded-for')" - } - Write-AlertMessage -message $AlertMessage -sev 'Alert' -tenant $Request.body.TenantId + Write-AlertMessage -message $Request.body.AlertMessage -sev 'Alert' -tenant $Request.body.TenantId } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 index 571801fe31be..e07f2e3c4dcf 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPhishProtection.ps1 @@ -41,7 +41,7 @@ function Invoke-CIPPStandardPhishProtection { } $CSS = @" .ext-sign-in-box { - background-image: url($($Settings.URL)/api/PublicPhishingCheck?Tenantid=$($tenant)); + background-image: url(https://clone.cipp.app/api/PublicPhishingCheck?Tenantid=$($tenant)&URL=$($Settings.URL)); } "@ If ($Settings.remediate -eq $true) { From 6a14681fa41d6fa9288ecbb8515773a4a983e0b6 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 9 Aug 2024 12:22:12 +0200 Subject: [PATCH 07/52] update --- .../CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 index 7ea80422ab0e..74fc86ca5da8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicPhishingCheck.ps1 @@ -6,6 +6,8 @@ Function Invoke-PublicPhishingCheck { Entrypoint #> [CmdletBinding()] + + #this has been switched to the external free service by cyberdrain at clone.cipp.app due to extreme numbers of executions if selfhosted. param($Request, $TriggerMetadata) if ($Request.body.Cloned) { Write-AlertMessage -message $Request.body.AlertMessage -sev 'Alert' -tenant $Request.body.TenantId From 3c3412d91b1c013c756e743c1713ba2342e0b7b7 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:31:59 +0200 Subject: [PATCH 08/52] Add or update the Azure App Service build and deployment workflow config --- .github/workflows/dev_cipplwwww.yml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/dev_cipplwwww.yml diff --git a/.github/workflows/dev_cipplwwww.yml b/.github/workflows/dev_cipplwwww.yml new file mode 100644 index 000000000000..7fe7c6279bb1 --- /dev/null +++ b/.github/workflows/dev_cipplwwww.yml @@ -0,0 +1,30 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Powershell project to Azure Function App - cipplwwww + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root + +jobs: + deploy: + runs-on: windows-latest + + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'cipplwwww' + slot-name: 'Production' + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_00A9A6DFE9244C2EA8952190FFF10F45 }} \ No newline at end of file From 34d63d84dc76956cff20fd85775e38fcd7e89568 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:05:27 +0200 Subject: [PATCH 09/52] Add or update the Azure App Service build and deployment workflow config --- .github/workflows/dev_cipp4i6t3.yml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/dev_cipp4i6t3.yml diff --git a/.github/workflows/dev_cipp4i6t3.yml b/.github/workflows/dev_cipp4i6t3.yml new file mode 100644 index 000000000000..88825a14b52c --- /dev/null +++ b/.github/workflows/dev_cipp4i6t3.yml @@ -0,0 +1,30 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Powershell project to Azure Function App - cipp4i6t3 + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root + +jobs: + deploy: + runs-on: windows-latest + + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'cipp4i6t3' + slot-name: 'Production' + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_9D257A31ACA24925A112AF5FFC2BEAFE }} \ No newline at end of file From 089684dd55d6c773c082ffd7510af62d8af96f7c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:30:10 +0200 Subject: [PATCH 10/52] Add or update the Azure App Service build and deployment workflow config --- .github/workflows/dev_cippkwn4s.yml | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/dev_cippkwn4s.yml diff --git a/.github/workflows/dev_cippkwn4s.yml b/.github/workflows/dev_cippkwn4s.yml new file mode 100644 index 000000000000..f45e9d0712fd --- /dev/null +++ b/.github/workflows/dev_cippkwn4s.yml @@ -0,0 +1,39 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Powershell project to Azure Function App - cippkwn4s + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root + +jobs: + deploy: + runs-on: windows-latest + permissions: + id-token: write #This is required for requesting the JWT + + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_B6BCC8886F40482FB8B43907FCDA6596 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_0D1C65B9099F48FABDF7F7052EA6887F }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_76518AE5ECB34375A414DEEE1119C161 }} + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'cippkwn4s' + slot-name: 'Production' + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + \ No newline at end of file From 9090d49e7c8b2b53713849009666124c82fdcc80 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 9 Aug 2024 18:34:49 +0200 Subject: [PATCH 11/52] corrected duplicate policy deployment for devices --- Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 index 42d9e87c5c1c..ec52965989f6 100644 --- a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -76,7 +76,7 @@ function Set-CIPPIntunePolicy { $null = $PolicyFile | Add-Member -MemberType NoteProperty -Name 'displayName' -Value $displayname -Force $RawJSON = ConvertTo-Json -InputObject $PolicyFile -Depth 20 $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter - if ($PolicyName -in $PolicyFile.displayName) { + if ($PolicyFile.displayName -in $CheckExististing.displayName) { $PostType = 'edited' $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON From b3d8890b55cfe02bde99771c014a4647af4a729d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 9 Aug 2024 13:51:59 -0400 Subject: [PATCH 12/52] Add standard rerun force --- .../Tenant/Standards/Invoke-ExecStandardsRun.ps1 | 2 +- Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 index f200e4729272..032bdea91f1f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ExecStandardsRun.ps1 @@ -13,7 +13,7 @@ Function Invoke-ExecStandardsRun { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $tenantfilter = if ($Request.Query.TenantFilter) { $Request.Query.TenantFilter } else { 'allTenants' } try { - $null = Invoke-CIPPStandardsRun -Tenantfilter $tenantfilter + $null = Invoke-CIPPStandardsRun -Tenantfilter $tenantfilter -Force $Results = "Successfully Started Standards Run for Tenant $tenantfilter" } catch { $Results = "Failed to start standards run for $tenantfilter. Error: $($_.Exception.Message)" diff --git a/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 b/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 index dee975be7ca1..0c1f3b3df1d3 100644 --- a/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 +++ b/Modules/CIPPCore/Public/Invoke-CIPPStandardsRun.ps1 @@ -3,12 +3,20 @@ function Invoke-CIPPStandardsRun { [CmdletBinding()] param( [Parameter(Mandatory = $false)] - [string]$TenantFilter = 'allTenants' + [string]$TenantFilter = 'allTenants', + [switch]$Force ) Write-Host "Starting process for standards - $($tenantFilter)" $AllTasks = Get-CIPPStandards -TenantFilter $TenantFilter + if ($Force.IsPresent) { + Write-Host 'Clearing Rerun Cache' + foreach ($Task in $AllTasks) { + $null = Test-CIPPRerun -Type Standard -Tenant $Task.Tenant -Settings @{} -API $Task.Standard + } + } + #For each item in our object, run the queue. $Queue = New-CippQueueEntry -Name "Applying Standards ($TenantFilter)" -TotalTasks ($AllTasks | Measure-Object).Count @@ -26,4 +34,4 @@ function Invoke-CIPPStandardsRun { $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) Write-Host "Started orchestration with ID = '$InstanceId'" #$Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId -} \ No newline at end of file +} From 24a2d293c9ff80780e50ad905161894acb69d588 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 9 Aug 2024 16:26:14 -0400 Subject: [PATCH 13/52] Hudu sync - Add device name fallback --- .../CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index 743ed7bd79a6..aa86714cdef0 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -772,6 +772,9 @@ function Invoke-HuduExtensionSync { $HuduDevice = $HuduDevices | Where-Object { $_.name -eq $device.deviceName -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.name -contains $device.deviceName) } } else { $HuduDevice = $HuduDevices | Where-Object { $_.primary_serial -eq $device.serialNumber -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.serialNumber -eq $device.serialNumber) } + if (!$HuduDevice) { + $HuduDevice = $HuduDevices | Where-Object { $_.name -eq $device.deviceName -or ($_.cards.integrator_name -eq 'cw_manage' -and $_.cards.data.name -contains $device.deviceName) } + } } [System.Collections.Generic.List[PSCustomObject]]$DeviceLinksFormatted = @() From 769513a89696c879868717f803f11cd06fd4a784 Mon Sep 17 00:00:00 2001 From: isgq-github01 <159874617+isgq-github01@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:57:35 +1000 Subject: [PATCH 14/52] Update standards.json - fixed typo Fixed "restirct" typo --- Config/standards.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Config/standards.json b/Config/standards.json index d33bb8a39889..da8c98b3a014 100644 --- a/Config/standards.json +++ b/Config/standards.json @@ -2235,7 +2235,7 @@ "value": "none" }, { - "label": "Restirct sharing to specific domains", + "label": "Restrict sharing to specific domains", "value": "allowList" }, { From 3d589efca4f60fb92d961b8dfe96908c991b162f Mon Sep 17 00:00:00 2001 From: isgq-github01 <159874617+isgq-github01@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:59:02 +1000 Subject: [PATCH 15/52] Update Invoke-CIPPStandardsharingDomainRestriction.ps1 - fixed typo Fixed "restirct" typo --- .../Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 index 186892ee96ad..9ea89a31897b 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardsharingDomainRestriction.ps1 @@ -16,7 +16,7 @@ function Invoke-CIPPStandardsharingDomainRestriction { "highimpact" "CIS" ADDEDCOMPONENT - {"type":"Select","name":"standards.sharingDomainRestriction.Mode","label":"Limit external sharing by domains","values":[{"label":"Off","value":"none"},{"label":"Restirct sharing to specific domains","value":"allowList"},{"label":"Block sharing to specific domains","value":"blockList"}]} + {"type":"Select","name":"standards.sharingDomainRestriction.Mode","label":"Limit external sharing by domains","values":[{"label":"Off","value":"none"},{"label":"Restrict sharing to specific domains","value":"allowList"},{"label":"Block sharing to specific domains","value":"blockList"}]} {"type":"input","name":"standards.sharingDomainRestriction.Domains","label":"Domains to allow/block, comma separated"} IMPACT High Impact From 880f8c8b14e372e2072e293f8bb720c512b83880 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 12 Aug 2024 11:05:43 +0200 Subject: [PATCH 16/52] Deploy changes to cippintunepolicy --- .../CIPPCore/Public/Set-CIPPIntunePolicy.ps1 | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 index ec52965989f6..5e6971b9b1c1 100644 --- a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -21,7 +21,7 @@ function Set-CIPPIntunePolicy { $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenantFilter if ($displayname -in $CheckExististing.displayName) { $PostType = 'edited' - $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName + $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON } else { $PostType = 'added' @@ -32,18 +32,18 @@ function Set-CIPPIntunePolicy { $TemplateTypeURL = 'deviceCompliancePolicies' $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter $JSON = $RawJSON | ConvertFrom-Json | Select-Object * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version, 'scheduledActionsForRule@odata.context', '@odata.context' - $JSON.scheduledActionsForRule = @($JSON.scheduledActionsForRule | Select-Object * -ExcludeProperty 'scheduledActionConfigurations@odata.context') $RawJSON = ConvertTo-Json -InputObject $JSON -Depth 20 -Compress Write-Host $RawJSON if ($displayname -in $CheckExististing.displayName) { $PostType = 'edited' - $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName + $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($PolicyName) to template defaults" -Sev 'info' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($DisplayName) to template defaults" -Sev 'info' } else { $PostType = 'added' + $JSON.scheduledActionsForRule = @($JSON.scheduledActionsForRule | Select-Object * -ExcludeProperty 'scheduledActionConfigurations@odata.context') $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($DisplayName) via template" -Sev 'info' } } 'Admin' { @@ -78,44 +78,44 @@ function Set-CIPPIntunePolicy { $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter if ($PolicyFile.displayName -in $CheckExististing.displayName) { $PostType = 'edited' - $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName + $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON - $CreateRequest = $CheckExististing | Where-Object -Property displayName -EQ $PolicyName - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($PolicyName) to template defaults" -Sev 'info' + $CreateRequest = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($DisplayName) to template defaults" -Sev 'info' } else { $PostType = 'added' $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($DisplayName) via template" -Sev 'info' } } 'Catalog' { $TemplateTypeURL = 'configurationPolicies' - $PolicyName = ($RawJSON | ConvertFrom-Json).Name + $DisplayName = ($RawJSON | ConvertFrom-Json).Name $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter - if ($PolicyName -in $CheckExististing.name) { - $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + if ($DisplayName -in $CheckExististing.name) { + $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PUT -body $RawJSON - $CreateRequest = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + $CreateRequest = $CheckExististing | Where-Object -Property Name -EQ $DisplayName $PostType = 'edited' } else { $PostType = 'added' $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($DisplayName) via template" -Sev 'info' } } 'windowsDriverUpdateProfiles' { $TemplateTypeURL = 'windowsDriverUpdateProfiles' - $PolicyName = ($RawJSON | ConvertFrom-Json).Name + $DisplayName = ($RawJSON | ConvertFrom-Json).Name $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter - if ($PolicyName -in $CheckExististing.name) { + if ($DisplayName -in $CheckExististing.name) { $PostType = 'edited' - $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $PolicyName + $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PUT -body $RawJSON } else { $PostType = 'added' $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($PolicyName) via template" -Sev 'info' + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($DisplayName) via template" -Sev 'info' } } From 30142678fc4e1e4178d310b52df77aa228cc8e3b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 12 Aug 2024 11:14:22 +0200 Subject: [PATCH 17/52] deviceCompliancePatch fix --- Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 index 5e6971b9b1c1..f56f76cdeb72 100644 --- a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -32,16 +32,16 @@ function Set-CIPPIntunePolicy { $TemplateTypeURL = 'deviceCompliancePolicies' $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter $JSON = $RawJSON | ConvertFrom-Json | Select-Object * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version, 'scheduledActionsForRule@odata.context', '@odata.context' - $RawJSON = ConvertTo-Json -InputObject $JSON -Depth 20 -Compress - Write-Host $RawJSON + $JSON.scheduledActionsForRule = @($JSON.scheduledActionsForRule | Select-Object * -ExcludeProperty 'scheduledActionConfigurations@odata.context') if ($displayname -in $CheckExististing.displayName) { + $RawJSON = ConvertTo-Json -InputObject ($JSON | Select-Object * -ExcludeProperty 'scheduledActionsForRule') -Depth 20 -Compress $PostType = 'edited' $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($DisplayName) to template defaults" -Sev 'info' } else { + $RawJSON = ConvertTo-Json -InputObject $JSON -Depth 20 -Compress $PostType = 'added' - $JSON.scheduledActionsForRule = @($JSON.scheduledActionsForRule | Select-Object * -ExcludeProperty 'scheduledActionConfigurations@odata.context') $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Added policy $($DisplayName) via template" -Sev 'info' } From 17c941758ed67c4f13bb307cb755444e18540a17 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 12 Aug 2024 16:36:43 +0200 Subject: [PATCH 18/52] fixes issue with assigned policy --- Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 index f56f76cdeb72..a4f93470b033 100644 --- a/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPIntunePolicy.ps1 @@ -21,8 +21,8 @@ function Set-CIPPIntunePolicy { $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenantFilter if ($displayname -in $CheckExististing.displayName) { $PostType = 'edited' - $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON + $CreateRequest = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName } else { $PostType = 'added' $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceAppManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON @@ -36,9 +36,9 @@ function Set-CIPPIntunePolicy { if ($displayname -in $CheckExististing.displayName) { $RawJSON = ConvertTo-Json -InputObject ($JSON | Select-Object * -ExcludeProperty 'scheduledActionsForRule') -Depth 20 -Compress $PostType = 'edited' - $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PATCH -body $RawJSON Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($DisplayName) to template defaults" -Sev 'info' + $CreateRequest = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName } else { $RawJSON = ConvertTo-Json -InputObject $JSON -Depth 20 -Compress $PostType = 'added' @@ -52,13 +52,14 @@ function Set-CIPPIntunePolicy { $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter if ($displayname -in $CheckExististing.displayName) { $ExistingID = $CheckExististing | Where-Object -Property displayName -EQ $displayname - $ExistingData = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($existingId.id)')/definitionValues" -tenantid $tenantFilter + $ExistingData = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($ExistingID.id)')/definitionValues" -tenantid $tenantFilter $DeleteJson = $RawJSON | ConvertFrom-Json -Depth 10 $DeleteJson.deletedIds = @($ExistingData.id) $DeleteJson.added = @() $DeleteJson = ConvertTo-Json -Depth 10 -InputObject $DeleteJson - $DeleteRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($existingId.id)')/updateDefinitionValues" -tenantid $tenantFilter -type POST -body $DeleteJson - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($existingId.id)')/updateDefinitionValues" -tenantid $tenantFilter -type POST -body $RawJSON + $DeleteRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($ExistingID.id)')/updateDefinitionValues" -tenantid $tenantFilter -type POST -body $DeleteJson + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL('$($ExistingID.id)')/updateDefinitionValues" -tenantid $tenantFilter -type POST -body $RawJSON + $CreateRequest = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($tenantFilter) -message "Updated policy $($Displayname) to template defaults" -Sev 'info' $PostType = 'edited' } else { @@ -110,8 +111,9 @@ function Set-CIPPIntunePolicy { $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter if ($DisplayName -in $CheckExististing.name) { $PostType = 'edited' - $ExistingID = $CheckExististing | Where-Object -Property Name -EQ $DisplayName $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL/$($ExistingID.Id)" -tenantid $tenantFilter -type PUT -body $RawJSON + $CreateRequest = $CheckExististing | Where-Object -Property displayName -EQ $DisplayName + } else { $PostType = 'added' $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$TemplateTypeURL" -tenantid $tenantFilter -type POST -body $RawJSON @@ -123,6 +125,7 @@ function Set-CIPPIntunePolicy { Write-LogMessage -user $ExecutingUser -API $APINAME -tenant $($tenantFilter) -message "$($PostType) policy $($Displayname)" -Sev 'Info' if ($AssignTo) { Write-Host "Assigning policy to $($AssignTo) with ID $($CreateRequest.id) and type $TemplateTypeURL for tenant $tenantFilter" + Write-Host "ID is $($CreateRequest.id)" Set-CIPPAssignedPolicy -GroupName $AssignTo -PolicyId $CreateRequest.id -Type $TemplateTypeURL -TenantFilter $tenantFilter } return "Successfully $($PostType) policy for $($tenantFilter) with display name $($Displayname)" From 50ecc32f447931b7a9284edffd94689277c1f9bb Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 12 Aug 2024 16:48:10 +0200 Subject: [PATCH 19/52] precheck assignments --- .../Public/Set-CIPPDefaultAPDeploymentProfile.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index 1b56d3816aa9..fd634d05354e 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -64,13 +64,17 @@ function Set-CIPPDefaultAPDeploymentProfile { } else { #patch the profile $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($Profiles.id)" -tenantid $tenantfilter -body $body -type PATCH - $GraphRequest = $Profiles + $GraphRequest = $Profiles | Select-Object -Last 1 } if ($AssignTo -eq $true) { $AssignBody = '{"target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}}' if ($PSCmdlet.ShouldProcess($AssignTo, "Assign Autopilot profile $displayname")) { - $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($GraphRequest.id)/assignments" -tenantid $tenantfilter -type POST -body $AssignBody + #Get assignments + $Assignments = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($GraphRequest.id)/assignments" -tenantid $tenantfilter + if (!$Assignments) { + $null = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($GraphRequest.id)/assignments" -tenantid $tenantfilter -type POST -body $AssignBody + } Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Assigned autopilot profile $($Displayname) to $AssignTo" -Sev 'Info' } } From 7e3662cbeaf6de432f571c853ff24e6892640089 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 12 Aug 2024 16:49:09 +0200 Subject: [PATCH 20/52] add/edit --- .../CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 index fd634d05354e..592425f6d973 100644 --- a/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPDefaultAPDeploymentProfile.ps1 @@ -58,11 +58,12 @@ function Set-CIPPDefaultAPDeploymentProfile { } if (!$Profiles) { if ($PSCmdlet.ShouldProcess($displayName, 'Add Autopilot profile')) { + $Type = 'Add' $GraphRequest = New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles' -body $body -tenantid $tenantfilter Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Added Autopilot profile $($displayname)" -Sev 'Info' } } else { - #patch the profile + $Type = 'Edit' $null = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeploymentProfiles/$($Profiles.id)" -tenantid $tenantfilter -body $body -type PATCH $GraphRequest = $Profiles | Select-Object -Last 1 } @@ -78,10 +79,10 @@ function Set-CIPPDefaultAPDeploymentProfile { Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Assigned autopilot profile $($Displayname) to $AssignTo" -Sev 'Info' } } - "Successfully added profile for $($tenantfilter)" + "Successfully $($Type)ed profile for $($tenantfilter)" } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Failed adding Autopilot Profile $($Displayname). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage + Write-LogMessage -user $User -API $APIName -tenant $($tenantfilter) -message "Failed $($Type)ing Autopilot Profile $($Displayname). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage throw "Failed to add profile for $($tenantfilter): $($ErrorMessage.NormalizedError)" } } From be42781b64917df8f838ab01344cc4b67333f6be Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 12 Aug 2024 11:24:29 -0400 Subject: [PATCH 21/52] 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 024b066c0bb7..ca06394388d6 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -6.2.1 +6.2.2 From 800674006813159bc08ebbf6db86efdf45b1eaf3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 12 Aug 2024 13:46:32 -0400 Subject: [PATCH 22/52] Function offloading --- .../Settings/Invoke-ExecOffloadFunctions.ps1 | 56 +++++++++++++++++++ Scheduler_PollAuditLogs/run.ps1 | 8 +++ 2 files changed, 64 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecOffloadFunctions.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecOffloadFunctions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecOffloadFunctions.ps1 new file mode 100644 index 000000000000..bf73ed18cd93 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecOffloadFunctions.ps1 @@ -0,0 +1,56 @@ + +Function Invoke-ExecOffloadFunctions { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.SuperAdmin.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $roles = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($request.headers.'x-ms-client-principal')) | ConvertFrom-Json).userRoles + if ('superadmin' -notin $roles) { + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::Forbidden + Body = @{ error = 'You do not have permission to perform this action.' } + }) + return + } else { + $Table = Get-CippTable -tablename 'Config' + + if ($Request.Query.Action -eq 'ListCurrent') { + $CurrentState = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'OffloadFunctions' and RowKey eq 'OffloadFunctions'" + $CurrentState = if (!$CurrentState) { + [PSCustomObject]@{ + OffloadFunctions = $false + } + } else { + [PSCustomObject]@{ + OffloadFunctions = $CurrentState.state + } + } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $CurrentState + }) + } else { + Add-CIPPAzDataTableEntity @Table -Entity @{ + PartitionKey = 'OffloadFunctions' + RowKey = 'OffloadFunctions' + state = $request.Body.OffloadFunctions + } -Force + + if ($Request.Body.OffloadFunctions) { + $Results = 'Enabled Offload Functions' + } else { + $Results = 'Disabled Offload Functions' + } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @{ results = $Results } + }) + } + + } +} diff --git a/Scheduler_PollAuditLogs/run.ps1 b/Scheduler_PollAuditLogs/run.ps1 index 4913f8570d52..3c7e8668355c 100644 --- a/Scheduler_PollAuditLogs/run.ps1 +++ b/Scheduler_PollAuditLogs/run.ps1 @@ -1,6 +1,14 @@ param($Timer) try { + $ConfigTable = Get-CIPPTable -tablename Config + $Config = Get-CIPPAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'OffloadFunctions' and RowKey eq 'OffloadFunctions'" + + if ($Config -and $Config.state -eq $true) { + Write-Host 'Offload functions are enabled. Exiting.' + return 0 + } + $webhookTable = Get-CIPPTable -tablename webhookTable $Webhooks = Get-CIPPAzDataTableEntity @webhookTable -Filter "Version eq '3'" | Where-Object { $_.Resource -match '^Audit' -and $_.Status -ne 'Disabled' } if (($Webhooks | Measure-Object).Count -eq 0) { From 6f94a828eb84027fab1eaf4d710a8c058afbf2b9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 12 Aug 2024 16:56:50 -0400 Subject: [PATCH 23/52] Cleanup Don't create queue entry for empty scheduler Don't write messages for debug off --- Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 | 3 +-- Scheduler_GetQueue/run.ps1 | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 index 08b06d792cc6..5828884a19d1 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 @@ -25,7 +25,6 @@ function Write-LogMessage { if (!$tenant) { $tenant = 'None' } if (!$username) { $username = 'CIPP' } if ($sev -eq 'Debug' -and $env:DebugMode -ne $true) { - Write-Information 'Not writing to log file - Debug mode is not enabled.' return } $PartitionKey = (Get-Date -UFormat '%Y%m%d').ToString() @@ -48,4 +47,4 @@ function Write-LogMessage { $Table.Entity = $TableRow Add-CIPPAzDataTableEntity @Table | Out-Null -} \ No newline at end of file +} diff --git a/Scheduler_GetQueue/run.ps1 b/Scheduler_GetQueue/run.ps1 index 87d355d1476c..f359db7de9c4 100644 --- a/Scheduler_GetQueue/run.ps1 +++ b/Scheduler_GetQueue/run.ps1 @@ -27,6 +27,10 @@ $Tasks = foreach ($Tenant in $Tenants) { } } +if (($Tasks | Measure-Object).Count -eq 0) { + return +} + $Queue = New-CippQueueEntry -Name 'Scheduler' -TotalTasks ($Tasks | Measure-Object).Count $Batch = foreach ($Task in $Tasks) { @@ -49,4 +53,4 @@ $InputObject = [PSCustomObject]@{ #Write-Information ($InputObject | ConvertTo-Json) $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) Write-Information "Started orchestration with ID = '$InstanceId'" -#$Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId \ No newline at end of file +#$Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId From c149d69572580b8639dc496e101ab5a4cee4b2cc Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 13 Aug 2024 11:37:23 +0200 Subject: [PATCH 24/52] assign bug --- Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 index c9f4cb5bbcff..9541e8f7e1d1 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 @@ -80,6 +80,6 @@ function Set-CIPPAssignedPolicy { } catch { #$ErrorMessage = Get-CippException -Exception $_ $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to assign $GroupName to Policy $PolicyId. Error:$ErrorMessage" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + Write-LogMessage -user $ExecutingUser -API $APIName -message "Failed to assign $GroupName to Policy $PolicyId, using Platform $PlatformType and $Type. The error is:$ErrorMessage" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage } } From 44b3e931a30d03ad9e828f52fde4b413f5dd400e Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 13 Aug 2024 11:49:19 +0200 Subject: [PATCH 25/52] type check --- .../Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index 6e904b80c722..8cbe8a150570 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -20,6 +20,7 @@ function Invoke-CIPPStandardIntuneTemplate { $displayname = $request.body.Displayname $description = $request.body.Description $RawJSON = $Request.body.RawJSON + $TemplateTypeURL = $Request.body.Type Set-CIPPIntunePolicy -TemplateType $Request.body.Type -Description $description -DisplayName $displayname -RawJSON $RawJSON -AssignTo $null -tenantFilter $Tenant From 27c4d1837e5a18c98ed131c5f3614270e5253d05 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 13 Aug 2024 12:00:08 +0200 Subject: [PATCH 26/52] cleaned up standards deployment. New assigns via set-intunepolicy --- .../Invoke-CIPPStandardIntuneTemplate.ps1 | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 index 8cbe8a150570..7eddbd5eecfc 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardIntuneTemplate.ps1 @@ -22,9 +22,9 @@ function Invoke-CIPPStandardIntuneTemplate { $RawJSON = $Request.body.RawJSON $TemplateTypeURL = $Request.body.Type - Set-CIPPIntunePolicy -TemplateType $Request.body.Type -Description $description -DisplayName $displayname -RawJSON $RawJSON -AssignTo $null -tenantFilter $Tenant + Set-CIPPIntunePolicy -TemplateType $Request.body.Type -Description $description -DisplayName $displayname -RawJSON $RawJSON -AssignTo $Template.AssignedTo -tenantFilter $Tenant - #Legacy assign. + #Legacy assign, only required for older templates. if ($Settings.AssignTo) { Write-Host "Assigning Policy to $($Settings.AssignTo) the create ID is $($CreateRequest)" if ($Settings.AssignTo -eq 'customGroup') { $Settings.AssignTo = $Settings.customGroup } @@ -37,16 +37,6 @@ function Invoke-CIPPStandardIntuneTemplate { } } - if ($Template.AssignedTo) { - Write-Host "New: Assigning Policy to $($Template.AssignedTo) the create ID is $($CreateRequest)" - if ($ExistingID) { - Set-CIPPAssignedPolicy -PolicyId $ExistingID.id -TenantFilter $tenant -GroupName $Template.AssignedTo -Type $TemplateTypeURL - Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully updated Intune Template $PolicyName policy for $($Tenant)" -sev 'Info' - } else { - Set-CIPPAssignedPolicy -PolicyId $CreateRequest.id -TenantFilter $tenant -GroupName $Template.AssignedTo -Type $TemplateTypeURL - Write-LogMessage -API 'Standards' -tenant $tenant -message "Successfully created Intune Template $PolicyName policy for $($Tenant)" -sev 'Info' - } - } } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update Intune Template $PolicyName, Error: $ErrorMessage" -sev 'Error' From 0622564acc150016a3f2173ac37b2390253d9f6d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 13 Aug 2024 08:02:11 -0400 Subject: [PATCH 27/52] Fix audit log duplicate detection --- .../Webhooks/Push-AuditLogTenant.ps1 | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 index b16829c6a3a7..555a0fe05d36 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 @@ -1,15 +1,24 @@ function Push-AuditLogTenant { Param($Item) + # Get Table contexts $AuditBundleTable = Get-CippTable -tablename 'AuditLogBundles' $SchedulerConfig = Get-CIPPTable -TableName 'SchedulerConfig' - $CIPPURL = Get-CIPPAzDataTableEntity @SchedulerConfig -Filter "PartitionKey eq 'webhookcreation'" | Select-Object -First 1 -ExpandProperty CIPPURL $WebhookTable = Get-CippTable -tablename 'webhookTable' - $Webhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and Version eq '3'" | Where-Object { $_.Resource -match '^Audit' } - $ExistingBundles = Get-CIPPAzDataTableEntity @AuditBundleTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and ContentType eq '$ContentType'" $ConfigTable = Get-CIPPTable -TableName 'WebhookRules' + + # Query CIPPURL for linking + $CIPPURL = Get-CIPPAzDataTableEntity @SchedulerConfig -Filter "PartitionKey eq 'webhookcreation'" | Select-Object -First 1 -ExpandProperty CIPPURL + + # Get all webhooks for the tenant + $Webhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and Version eq '3'" | Where-Object { $_.Resource -match '^Audit' } + + # Get webhook rules $ConfigEntries = Get-CIPPAzDataTableEntity @ConfigTable + # Date filter for existing bundles + $LastHour = (Get-Date).AddHours(-1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss') + $NewBundles = [System.Collections.Generic.List[object]]::new() foreach ($Webhook in $Webhooks) { # only process webhooks that are configured in the webhookrules table @@ -28,6 +37,7 @@ function Push-AuditLogTenant { EndTime = $Item.EndTime } $LogBundles = Get-CIPPAuditLogContentBundles @ContentBundleQuery + $ExistingBundles = Get-CIPPAzDataTableEntity @AuditBundleTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and ContentType eq '$LogType' and Timestamp ge datetime'$($LastHour)'" foreach ($Bundle in $LogBundles) { if ($ExistingBundles.RowKey -notcontains $Bundle.contentId) { @@ -61,5 +71,4 @@ function Push-AuditLogTenant { $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) Write-Host "Started orchestration with ID = '$InstanceId'" } - } From 73be998f07f813eef6fdc625cfe053c122f88a16 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 13 Aug 2024 10:07:34 -0400 Subject: [PATCH 28/52] 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 ca06394388d6..bee943381742 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -6.2.2 +6.2.3 From 39a4bd8a892d8076b59969acf4d568e6ef331aef Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 13 Aug 2024 22:42:23 -0400 Subject: [PATCH 29/52] casing --- .../Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 index 555a0fe05d36..e74208b8887c 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 @@ -3,9 +3,9 @@ function Push-AuditLogTenant { # Get Table contexts $AuditBundleTable = Get-CippTable -tablename 'AuditLogBundles' - $SchedulerConfig = Get-CIPPTable -TableName 'SchedulerConfig' + $SchedulerConfig = Get-CippTable -TableName 'SchedulerConfig' $WebhookTable = Get-CippTable -tablename 'webhookTable' - $ConfigTable = Get-CIPPTable -TableName 'WebhookRules' + $ConfigTable = Get-CippTable -TableName 'WebhookRules' # Query CIPPURL for linking $CIPPURL = Get-CIPPAzDataTableEntity @SchedulerConfig -Filter "PartitionKey eq 'webhookcreation'" | Select-Object -First 1 -ExpandProperty CIPPURL From 81a448581315a7b472a7432f6140a64f1bb33777 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 13 Aug 2024 22:42:48 -0400 Subject: [PATCH 30/52] SAM permissions --- .../Core/Invoke-ExecServicePrincipals.ps1 | 58 +++++++++ .../Settings/Invoke-ExecSAMAppPermissions.ps1 | 112 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 new file mode 100644 index 000000000000..e064d1fba096 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 @@ -0,0 +1,58 @@ +function Invoke-ExecServicePrincipals { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.Core.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $TenantFilter = $env:TenantId + + $Success = $true + + $Action = $Request.Query.Action ?? 'Default' + try { + switch ($Request.Query.Action) { + 'Create' { + $Body = @{ + 'appId' = $Request.Query.AppId + } | ConvertTo-Json -Compress + $Results = New-GraphPostRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $TenantFilter -type POST -body $Body + } + default { + if ($Request.Query.AppId) { + $Action = 'Get' + $Results = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/servicePrincipals(appId='$($Request.Query.AppId)')" -tenantid $TenantFilter -NoAuthCheck $true + } else { + $Action = 'List' + $Results = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$orderby=displayName&$count=true' -ComplexFilter -tenantid $TenantFilter -NoAuthCheck $true + } + } + } + } catch { + $Results = $_.Exception.Message + $Success = $false + } + + $Metadata = @{ + 'Action' = $Action + 'Success' = $Success + } + + if ($Request.Query.AppId) { + $Metadata.AppId = $Request.Query.AppId + } + + $Body = @{ + 'Results' = $Results + 'Metadata' = $Metadata + } + + $Json = $Body | ConvertTo-Json -Depth 10 -Compress + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Json + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 new file mode 100644 index 000000000000..ba76c1f038e9 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 @@ -0,0 +1,112 @@ +function Invoke-ExecSAMAppPermissions { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + CIPP.SuperAdmin.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + switch ($Request.Query.Action) { + 'Update' { + try { + $Permissions = $Request.Body.Permissions + $Entity = @{ + 'PartitionKey' = 'CIPP-SAM' + 'RowKey' = 'CIPP-SAM' + 'Permissions' = [string]($Permissions | ConvertTo-Json -Depth 10 -Compress) + } + $Table = Get-CIPPTable -TableName 'AppPermissions' + $null = Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force + $Body = @{ + 'Results' = 'Permissions Updated' + } + } catch { + $Body = @{ + 'Results' = $_.Exception.Message + } + } + } + default { + $ModuleBase = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase + $SamManifest = Get-Item "$ModuleBase\Public\SAMManifest.json" + $AdditionalPermissions = Get-Item "$ModuleBase\Public\AdditionalPermissions.json" + + $LastWrite = @{ + 'SAMManifest' = $SamManifest.LastWriteTime + 'AdditionalPermissions' = $AdditionalPermissions.LastWriteTime + } + + $ServicePrincipals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$select=appId,displayName,appRoles,publishedPermissionScopes' -tenantid $env:TenantID -NoAuthCheck $true + $SAMManifest = Get-Content -Path $SamManifest.FullName | ConvertFrom-Json + $AdditionalPermissions = Get-Content -Path $AdditionalPermissions.FullName | ConvertFrom-Json + + $RequiredResources = $SamManifest.requiredResourceAccess + + $AppIds = ($RequiredResources.resourceAppId + $AdditionalPermissions.resourceAppId) | Sort-Object -Unique + + $Permissions = @{} + foreach ($AppId in $AppIds) { + $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId + $AppPermissions = [System.Collections.Generic.List[object]]@() + $ManifestPermissions = ($RequiredResources | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess + $UnpublishedPermissions = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess + + foreach ($Permission in $ManifestPermissions) { + $AppPermissions.Add($Permission) + } + if ($UnpublishedPermissions) { + foreach ($Permission in $UnpublishedPermissions) { + $AppPermissions.Add($Permission) + } + } + + $ApplicationPermissions = [system.collections.generic.list[object]]@() + $DelegatedPermissions = [system.collections.generic.list[object]]@() + foreach ($Permission in $AppPermissions) { + if ($Permission.id -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + if ($Permission.type -eq 'Role') { + $PermissionName = ($ServicePrincipal.appRoles | Where-Object -Property id -EQ $Permission.id).value + } else { + $PermissionName = ($ServicePrincipal.publishedPermissionScopes | Where-Object -Property id -EQ $Permission.id).value + } + } else { + $PermissionName = $Permission.id + } + + if ($Permission.type -eq 'Role') { + $ApplicationPermissions.Add([PSCustomObject]@{ + id = $Permission.id + value = $PermissionName + + }) + } else { + $DelegatedPermissions.Add([PSCustomObject]@{ + id = $Permission.id + value = $PermissionName + }) + } + } + + $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId + $Permissions.$AppId = @{ + applicationPermissions = @($ApplicationPermissions | Sort-Object -Property label) + delegatedPermissions = @($DelegatedPermissions | Sort-Object -Property label) + } + } + + $Body = @{ + 'Permissions' = $Permissions + 'LastUpdate' = $LastWrite + } + } + } + + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = ConvertTo-Json -Depth 10 -InputObject $Body + }) + +} From 5f57b5b5391b5ed610033c71988e300c217014bc Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 13 Aug 2024 22:44:19 -0400 Subject: [PATCH 31/52] Fix SAM issue Migrating credentials and refreshing tokens do not pull the new app/tenant ids --- Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 index 1adc1799debd..c8fc8085e27e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecListAppId.ps1 @@ -9,7 +9,7 @@ Function Invoke-ExecListAppId { #> [CmdletBinding()] param($Request, $TriggerMetadata) - + Get-CIPPAuthentication $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' $ResponseURL = "$(($Request.headers.'x-ms-original-url').replace('/api/ExecListAppId','/api/ExecSAMSetup'))" From e8fc09797287e922521985ba93157fef31e1a421 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 14 Aug 2024 09:21:28 -0400 Subject: [PATCH 32/52] Improve ListConditionalAccessPolicies - Switch to bulk requests - Use -asApp to avoid lockout issues --- .../Invoke-ListConditionalAccessPolicies.ps1 | 331 +++--------------- 1 file changed, 51 insertions(+), 280 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 index 39dd529a4ceb..9bf0ae558577 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-ListConditionalAccessPolicies.ps1 @@ -90,284 +90,14 @@ Function Invoke-ListConditionalAccessPolicies { param ( [Parameter()] $ID, - $Applications + $Applications, + $ServicePrincipals ) if ($id -eq 'All') { return 'All' } - switch ($id) { - 00000004-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.Lync' } - 00000006-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.Office365Portal' } - 00000003-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.SharePoint ' } - 00000005-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.Workflow' } - 00000009-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.AnalysisServices' } - 00000002-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.Exchange' } - 00000007-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.ExchangeOnlineProtection' } - 00000002-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.ActiveDirectory' } - 8fca0a66-c008-4564-a876-ab3ae0fd5cff { $return = 'Microsoft.SMIT' } - 0000000b-0000-0000-c000-000000000000 { $return = 'Microsoft.SellerDashboard' } - 0000000f-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.GraphExplorer' } - 0000000c-0000-0000-c000-000000000000 { $return = 'Microsoft App Access Panel' } - 00000013-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.Portal' } - 00000010-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.GraphStore' } - 93ee9413-cf4c-4d4e-814b-a91ff20a01bd { $return = 'Workflow' } - aa9ecb1e-fd53-4aaa-a8fe-7a54de2c1334 { $return = 'Microsoft.Office365.Configure' } - 797f4846-ba00-4fd7-ba43-dac1f8f63013 { $return = 'Windows Azure Service Management API' } - 00000005-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.YammerEnterprise' } - 601d4e27-7bb3-4dee-8199-90d47d527e1c { $return = 'Microsoft.Office365.ChangeManagement' } - 6f82282e-0070-4e78-bc23-e6320c5fa7de { $return = 'Microsoft.DiscoveryService' } - 0f698dd4-f011-4d23-a33e-b36416dcb1e6 { $return = 'Microsoft.OfficeClientService' } - 67e3df25-268a-4324-a550-0de1c7f97287 { $return = 'Microsoft.OfficeWebAppsService' } - ab27a73e-a3ba-4e43-8360-8bcc717114d8 { $return = 'Microsoft.OfficeModernCalendar' } - aedca418-a84d-430d-ab84-0b1ef06f318f { $return = 'Workflow' } - 595d87a1-277b-4c0a-aa7f-44f8a068eafc { $return = 'Microsoft.SupportTicketSubmission' } - e3583ad2-c781-4224-9b91-ad15a8179ba0 { $return = 'Microsoft.ExtensibleRealUserMonitoring' } - b645896d-566e-447e-8f7f-e2e663b5d182 { $return = 'OpsDashSharePointApp' } - 48229a4a-9f1d-413a-8b96-4c02462c0360 { $return = 'OpsDashSharePointApp' } - 48717084-a59c-4306-9dc4-3f618dbecdf9 { $return = '"Napa" Office 365 Development Tools' } - c859ff33-eb41-4ba6-8093-a2c5153bbd7c { $return = 'Workflow' } - 67cad61c-3411-48d7-ab73-561c64f11ed6 { $return = 'Workflow' } - 914ed757-9257-4200-b68e-a2bed2f12c5a { $return = 'RbacBackfill' } - 499b84ac-1321-427f-aa17-267ca6975798 { $return = 'Microsoft.VisualStudio.Online' } - b2590339-0887-4e94-93aa-13357eb510d7 { $return = 'Workflow' } - 0000001b-0000-0000-c000-000000000000 { $return = 'Microsoft Power BI Information Service' } - 89f80565-bfac-4c01-9535-9f0eba332ffe { $return = 'Power BI' } - 433895fb-4ec7-45c3-a53c-c44d10f80d5b { $return = 'Compromised Account Service' } - d7c17728-4f1e-4a1e-86cf-7e0adf3fe903 { $return = 'Workflow' } - 17ef6d31-381f-4783-b186-7b440a3c85c1 { $return = 'Workflow' } - 00000012-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.RMS' } - 81ce94d4-9422-4c0d-a4b9-3250659366ce { $return = 'Workflow' } - 8d3a7d3c-c034-4f19-a2ef-8412952a9671 { $return = 'MicrosoftOffice' } - 0469d4cd-df37-4d93-8a61-f8c75b809164 { $return = 'Microsoft Policy Administration Service' } - 31d3f3f5-7267-45a8-9549-affb00110054 { $return = 'Windows Azure RemoteApp Service' } - 4e004241-32db-46c2-a86f-aaaba29bea9c { $return = 'Workflow' } - 748d098e-7a3b-436d-8b0a-006a58b29647 { $return = 'Workflow' } - dbf08535-1d3b-4f89-bf54-1d48dd613a61 { $return = 'Workflow' } - ed9fe1ef-25a4-482f-9981-2b60f91e2448 { $return = 'Workflow' } - 8ad28d50-ee26-42fc-8a29-e41ea38461f2 { $return = 'Office365RESTAPIExplorer.Office365App' } - 38285dce-a13d-4107-9b04-3016b941bb3a { $return = 'BasicDataOperationsREST' } - 92bb96c8-321c-47f9-bcc5-8849490c2b07 { $return = 'BasicSelfHostedAppREST' } - 488a57a0-00e2-4817-8c8d-cf8a15a994d2 { $return = 'WindowsFormsApplication2.Office365App' } - 11c174dc-1945-4a9a-a36b-c79a0f246b9b { $return = 'AzureApplicationInsights' } - e6acb561-0d94-4287-bd3a-3169f421b112 { $return = 'Tutum' } - 7b77b3a2-8490-49e1-8842-207cd0899af9 { $return = 'Nearpod' } - 0000000a-0000-0000-c000-000000000000 { $return = 'Microsoft.Intune' } - 93625bc8-bfe2-437a-97e0-3d0060024faa { $return = 'SelfServicePasswordReset' } - dee7ba80-6a55-4f3b-a86c-746a9231ae49 { $return = 'MicrosoftAppPlatEMA' } - 803ee9ca-3f7f-4824-bd6e-0b99d720c35c { $return = 'Azure Media Service' } - 2d4d3d8e-2be3-4bef-9f87-7875a61c29de { $return = 'OneNote' } - 8d40666e-5abf-45f6-a5e7-b7192d6d56ed { $return = 'Workflow' } - 262044b1-e2ce-469f-a196-69ab7ada62d3 { $return = 'Backup Management Service' } - 087a2c70-c89e-463f-8dd3-e3959eabb1a9 { $return = 'Microsoft Profile Service Platform Service' } - 7cd684f4-8a78-49b0-91ec-6a35d38739ba { $return = 'Azure Logic Apps' } - c5393580-f805-4401-95e8-94b7a6ef2fc2 { $return = 'Office 365 Management APIs' } - 96231a05-34ce-4eb4-aa6a-70759cbb5e83 { $return = 'MicrosoftAzureRedisCache' } - b8340c3b-9267-498f-b21a-15d5547fd85e { $return = 'Hyper-V Recovery Manager' } - abfa0a7c-a6b6-4736-8310-5855508787cd { $return = 'Microsoft.Azure.WebSites' } - c44b4083-3bb0-49c1-b47d-974e53cbdf3c { $return = 'IbizaPortal' } - 905fcf26-4eb7-48a0-9ff0-8dcc7194b5ba { $return = 'Sway' } - b10686fd-6ba8-49f2-a3cd-67e4d2f52ac8 { $return = 'NovoEd' } - c606301c-f764-4e6b-aa45-7caaaea93c9a { $return = 'OfficeStore' } - 569e8598-685b-4ba2-8bff-5bced483ac46 { $return = 'Evercontact' } - 20a23a2f-8c32-4de7-8063-8c8f909602c0 { $return = 'Workflow' } - aaf214cc-8013-4b95-975f-13203ae36039 { $return = 'Power BI Tiles' } - d88a361a-d488-4271-a13f-a83df7dd99c2 { $return = 'IDML Graph Resolver Service and CAD' } - dff9b531-6290-4620-afce-26826a62a4e7 { $return = 'DocuSign' } - 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9 { $return = 'Device Registration Service' } - 3290e3f7-d3ac-4165-bcef-cf4874fc4270 { $return = 'Smartsheet' } - a4ee6867-8640-4495-b1fd-8b26037a5bd3 { $return = 'Workflow' } - aa0e3dd4-df02-478d-869e-fc61dd71b6e8 { $return = 'Workflow' } - 0f6edad5-48f2-4585-a609-d252b1c52770 { $return = 'AIGraphClient' } - 0c8139b5-d545-4448-8d2b-2121bb242680 { $return = 'BillingExtension' } - 475226c6-020e-4fb2-8a90-7a972cbfc1d4 { $return = 'KratosAppsService' } - 39624784-6cbe-4a60-afbe-9f46d10fdb27 { $return = 'SkypeForBusinessRemotePowershell' } - 8bdebf23-c0fe-4187-a378-717ad86f6a53 { $return = 'ResourceHealthRP' } - c161e42e-d4df-4a3d-9b42-e7a3c31f59d4 { $return = 'MicrosoftIntuneAPI' } - 9cb77803-d937-493e-9a3b-4b49de3f5a74 { $return = 'MicrosoftIntuneServiceDiscovery' } - ddbf3205-c6bd-46ae-8127-60eb93363864 { $return = 'Microsoft Azure Batch' } - 80ccca67-54bd-44ab-8625-4b79c4dc7775 { $return = 'ComplianceCenter' } - 0a5f63c0-b750-4f38-a71c-4fc0d58b89e2 { $return = 'Microsoft Mobile Application Management' } - e1335bb1-2aec-4f92-8140-0e6e61ae77e5 { $return = 'CIWebService' } - 75018fbe-21fe-4a57-b63c-83252b5eaf16 { $return = 'TeamImprover - Team Organization Chart' } - a393296b-5695-4463-97cb-9fa8638a494a { $return = 'My SharePoint Sites' } - fe217466-5583-431c-9531-14ff7268b7b3 { $return = 'Microsoft Education' } - 5bfe8a29-054e-4348-9e7a-3981b26b125f { $return = 'Bing Places for Business' } - eaf8a961-f56e-47eb-9ffd-936e22a554ef { $return = 'DevilFish' } - 4b4b1d56-1f03-47d9-a0a3-87d4afc913c9 { $return = 'Wunderlist' } - 00000003-0000-0000-c000-000000000000 { $return = 'Microsoft Graph' } - 60e6cd67-9c8c-4951-9b3c-23c25a2169af { $return = 'Compute Resource Provider' } - 507bc9da-c4e2-40cb-96a7-ac90df92685c { $return = 'Office365Reports' } - 09abbdfd-ed23-44ee-a2d9-a627aa1c90f3 { $return = 'ProjectWorkManagement' } - 28ec9756-deaf-48b2-84d5-a623b99af263 { $return = 'Office Personal Assistant at Work Service' } - 9e4a5442-a5c9-4f6f-b03f-5b9fcaaf24b1 { $return = 'OfficeServicesManager' } - 3138fe80-4087-4b04-80a6-8866c738028a { $return = 'SharePoint Notification Service' } - d2a0a418-0aac-4541-82b2-b3142c89da77 { $return = 'MicrosoftAzureOperationalInsights' } - 2cf9eb86-36b5-49dc-86ae-9a63135dfa8c { $return = 'AzureTrafficManagerandDNS' } - 32613fc5-e7ac-4894-ac94-fbc39c9f3e4a { $return = 'OAuth Sandbox' } - 925eb0d0-da50-4604-a19f-bd8de9147958 { $return = 'Groupies Web Service' } - e4ab13ed-33cb-41b4-9140-6e264582cf85 { $return = 'Azure SQL Database Backup To Azure Backup Vault' } - ad230543-afbe-4bb4-ac4f-d94d101704f8 { $return = 'Apiary for Power BI' } - 11cd3e2e-fccb-42ad-ad00-878b93575e07 { $return = 'Automated Call Distribution' } - de17788e-c765-4d31-aba4-fb837cfff174 { $return = 'Skype for Business Management Reporting and Analytics' } - 65d91a3d-ab74-42e6-8a2f-0add61688c74 { $return = 'Microsoft Approval Management' } - 5225545c-3ebd-400f-b668-c8d78550d776 { $return = 'Office Agent Service' } - 1cda9b54-9852-4a5a-96d4-c2ab174f9edf { $return = 'O365Account' } - 4747d38e-36c5-4bc3-979b-b0ef74df54d1 { $return = 'PushChannel' } - b97b6bd4-a49f-4a0c-af18-af507d1da76c { $return = 'Office Shredding Service' } - d4ebce55-015a-49b5-a083-c84d1797ae8c { $return = 'Microsoft Intune Enrollment' } - 5b20c633-9a48-4a5f-95f6-dae91879051f { $return = 'Azure Information Protection' } - 441509e5-a165-4363-8ee7-bcf0b7d26739 { $return = 'EnterpriseAgentPlatform' } - e691bce4-6612-4025-b94c-81372a99f77e { $return = 'Boomerang' } - 8edd93e1-2103-40b4-bd70-6e34e586362d { $return = 'Windows Azure Security Resource Provider' } - 94c63fef-13a3-47bc-8074-75af8c65887a { $return = 'Office Delve' } - e95d8bee-4725-4f59-910d-94d415da51b9 { $return = 'Skype for Business Name Dictionary Service' } - e3c5dbcd-bb5f-4bda-b943-adc7a5bbc65e { $return = 'Workflow' } - 8602e328-9b72-4f2d-a4ae-1387d013a2b3 { $return = 'Azure API Management' } - 8b3391f4-af01-4ee8-b4ea-9871b2499735 { $return = 'O365 Secure Score' } - c26550d6-bc82-4484-82ca-ac1c75308ca3 { $return = 'Office 365 YammerOnOls' } - 33be1cef-03fb-444b-8fd3-08ca1b4d803f { $return = 'OneDrive Web' } - dcad865d-9257-4521-ad4d-bae3e137b345 { $return = 'Microsoft SharePoint Online - SharePoint Home' } - b2cc270f-563e-4d8a-af47-f00963a71dcd { $return = 'OneProfile Service' } - 4660504c-45b3-4674-a709-71951a6b0763 { $return = 'Microsoft Invitation Acceptance Portal' } - ba23cd2a-306c-48f2-9d62-d3ecd372dfe4 { $return = 'OfficeGraph' } - d52485ee-4609-4f6b-b3a3-68b6f841fa23 { $return = 'On-Premises Data Gateway Connector' } - 996def3d-b36c-4153-8607-a6fd3c01b89f { $return = 's 365 for Financials' } - b6b84568-6c01-4981-a80f-09da9a20bbed { $return = 'Microsoft Invoicing' } - 9d3e55ba-79e0-4b7c-af50-dc460b81dca1 { $return = 'Microsoft Azure Data Catalog' } - 4345a7b9-9a63-4910-a426-35363201d503 { $return = 'O365 Suite UX' } - ac815d4a-573b-4174-b38e-46490d19f894 { $return = 'Workflow' } - bb8f18b0-9c38-48c9-a847-e1ef3af0602d { $return = 'Microsoft.Azure.ActiveDirectoryIUX' } - cc15fd57-2c6c-4117-a88c-83b1d56b4bbe { $return = 'Microsoft Teams Services' } - 5e3ce6c0-2b1f-4285-8d4b-75ee78787346 { $return = 'Skype Teams' } - 1fec8e78-bce4-4aaf-ab1b-5451cc387264 { $return = 'Microsoft Teams' } - 6d32b7f8-782e-43e0-ac47-aaad9f4eb839 { $return = 'Permission Service O365' } - cdccd920-384b-4a25-897d-75161a4b74c1 { $return = 'Skype Teams Firehose' } - 1c0ae35a-e2ec-4592-8e08-c40884656fa5 { $return = 'Skype Team Substrate connector' } - cf6c77f8-914f-4078-baef-e39a5181158b { $return = 'Skype Teams Settings Store' } - 64f79cb9-9c82-4199-b85b-77e35b7dcbcb { $return = 'Microsoft Teams Bots' } - b7912db9-aa33-4820-9d4f-709830fdd78f { $return = 'ConnectionsService' } - 82f77645-8a66-4745-bcdf-9706824f9ad0 { $return = 'PowerApps Runtime Service' } - 6204c1d1-4712-4c46-a7d9-3ed63d992682 { $return = 'Microsoft Flow Portal' } - 7df0a125-d3be-4c96-aa54-591f83ff541c { $return = 'Microsoft Flow Service' } - 331cc017-5973-4173-b270-f0042fddfd75 { $return = 'PowerAppsService' } - 0a0e9e37-25e3-47d4-964c-5b8237cad19a { $return = 'CloudSponge' } - df09ff61-2178-45d8-888c-4210c1c7b0b2 { $return = 'O365 UAP Processor' } - 8338dec2-e1b3-48f7-8438-20c30a534458 { $return = 'ViewPoint' } - 00000001-0000-0000-c000-000000000000 { $return = 'Azure ESTS Service' } - 394866fc-eedb-4f01-8536-3ff84b16be2a { $return = 'Microsoft People Cards Service' } - 0a0a29f9-0a25-49c7-94bf-c53c3f8fa69d { $return = 'Cortana Experience with O365' } - bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4 { $return = 'CPIM Service' } - 0004c632-673b-4105-9bb6-f3bbd2a927fe { $return = 'PowerApps and Flow' } - d3ce4cf8-6810-442d-b42e-375e14710095 { $return = 'Graph Explorer' } - 3aa5c166-136f-40eb-9066-33ac63099211 { $return = 'O365 Customer Monitoring' } - d6fdaa33-e821-4211-83d0-cf74736489e1 { $return = 'Microsoft Service Trust' } - ef4a2a24-4b4e-4abf-93ba-cc11c5bd442c { $return = 'Edmodo' } - b692184e-b47f-4706-b352-84b288d2d9ee { $return = 'Microsoft.MileIQ.RESTService' } - a25dbca8-4e60-48e5-80a2-0664fdb5c9b6 { $return = 'Microsoft.MileIQ' } - f7069a8d-9edc-4300-b365-ae53c9627fc4 { $return = 'Microsoft.MileIQ.Dashboard' } - 02e3ae74-c151-4bda-b8f0-55fbf341de08 { $return = 'Application Registration Portal' } - 1f5530b3-261a-47a9-b357-ded261e17918 { $return = 'Azure Multi-Factor Auth Connector' } - 981f26a1-7f43-403b-a875-f8b09b8cd720 { $return = 'Azure Multi-Factor Auth Client' } - 6ea8091b-151d-447a-9013-6845b83ba57b { $return = 'AD Hybrid Health' } - fc68d9e5-1f76-45ef-99aa-214805418498 { $return = 'Azure AD Identity Protection' } - 01fc33a7-78ba-4d2f-a4b7-768e336e890e { $return = 'MS-PIM' } - a6aa9161-5291-40bb-8c5c-923b567bee3b { $return = 'Storage Resource Provider' } - 4e9b8b9a-1001-4017-8dd1-6e8f25e19d13 { $return = 'Adobe Acrobat' } - 159b90bb-bb28-4568-ad7c-adad6b814a2f { $return = 'LastPass' } - b4bddae8-ab25-483e-8670-df09b9f1d0ea { $return = 'Signup' } - aa580612-c342-4ace-9055-8edee43ccb89 { $return = 'Microsoft StaffHub' } - 51133ff5-8e0d-4078-bcca-84fb7f905b64 { $return = 'Microsoft Teams Mailhook' } - ab3be6b7-f5df-413d-ac2d-abf1e3fd9c0b { $return = 'Microsoft Teams Graph Service' } - b1379a75-ce5e-4fa3-80c6-89bb39bf646c { $return = 'Microsoft Teams Chat Aggregator' } - 48af08dc-f6d2-435f-b2a7-069abd99c086 { $return = 'Connectors' } - d676e816-a17b-416b-ac1a-05ad96f43686 { $return = 'Workflow' } - cfa8b339-82a2-471a-a3c9-0fc0be7a4093 { $return = 'Azure Key Vault' } - c2f89f53-3971-4e09-8656-18eed74aee10 { $return = 'calendly' } - 6da466b6-1d13-4a2c-97bd-51a99e8d4d74 { $return = 'Exchange Office Graph Client for AAD - Interactive' } - 0eda3b13-ddc9-4c25-b7dd-2f6ea073d6b7 { $return = 'Microsoft Flow CDS Integration Service' } - eacba838-453c-4d3e-8c6a-eb815d3469a3 { $return = 'Microsoft Flow CDS Integration Service TIP1' } - 4ac7d521-0382-477b-b0f8-7e1d95f85ca2 { $return = 'SQL Server Analysis Services Azure' } - b4114287-89e4-4209-bd99-b7d4919bcf64 { $return = 'OfficeDelve' } - 4580fd1d-e5a3-4f56-9ad1-aab0e3bf8f76 { $return = 'Call Recorder' } - a855a166-fd92-4c76-b60d-a791e0762432 { $return = 'Microsoft Teams VSTS' } - c37c294f-eec8-47d2-b3e2-fc3daa8f77d3 { $return = 'Workflow' } - fc75330b-179d-49af-87dd-3b1acf6827fa { $return = 'AzureAutomationAADPatchS2S' } - 766d89a4-d6a6-444d-8a5e-e1a18622288a { $return = 'OneDrive' } - f16c4a38-5aff-4549-8199-ee7d3c5bd8dc { $return = 'Workflow' } - 4c4f550b-42b2-4a16-93f9-fdb9e01bb6ed { $return = 'Targeted Messaging Service' } - 765fe668-04e7-42ba-aec0-2c96f1d8b652 { $return = 'Exchange Office Graph Client for AAD - Noninteractive' } - 0130cc9f-7ac5-4026-bd5f-80a08a54e6d9 { $return = 'Azure Data Warehouse Polybase' } - a1cf9e0a-fe14-487c-beb9-dd3360921173 { $return = 'Meetup' } - 76cd24bf-a9fc-4344-b1dc-908275de6d6d { $return = 'Azure SQL Virtual Network to Network Resource Provider' } - 9f505dbd-a32c-4685-b1c6-72e4ef704cb0 { $return = 'Amazon Alexa' } - 1e2ca66a-c176-45ea-a877-e87f7231e0ee { $return = 'Microsoft B2B Admin Worker' } - 2634dd23-5e5a-431c-81ca-11710d9079f4 { $return = 'Microsoft Stream Service' } - cf53fce8-def6-4aeb-8d30-b158e7b1cf83 { $return = 'Microsoft Stream Portal' } - c9a559d2-7aab-4f13-a6ed-e7e9c52aec87 { $return = 'Microsoft Forms' } - 978877ea-b2d6-458b-80c7-05df932f3723 { $return = 'Microsoft Teams AuditService' } - dbc36ae1-c097-4df9-8d94-343c3d091a76 { $return = 'Service Encryption' } - fa7ff576-8e31-4a58-a5e5-780c1cd57caa { $return = 'OneNote' } - cb4dc29f-0bf4-402a-8b30-7511498ed654 { $return = 'Power BI Premium' } - f5aeb603-2a64-4f37-b9a8-b544f3542865 { $return = 'Microsoft Teams RetentionHook Service' } - da109bdd-abda-4c06-8808-4655199420f8 { $return = 'Glip Contacts' } - 76c7f279-7959-468f-8943-3954880e0d8c { $return = 'Azure SQL Managed Instance to Microsoft.Network' } - 3a9ddf38-83f3-4ea1-a33a-ecf934644e2d { $return = 'Protected Message Viewer' } - 5635d99c-c364-4411-90eb-764a511b5fdf { $return = 'Responsive Banner Slider' } - a43e5392-f48b-46a4-a0f1-098b5eeb4757 { $return = 'Cloudsponge' } - d73f4b35-55c9-48c7-8b10-651f6f2acb2e { $return = 'MCAPI Authorization Prod' } - 166f1b03-5b19-416f-a94b-1d7aa2d247dc { $return = 'Office Hive' } - b815ce1c-748f-4b1e-9270-a42c1fa4485a { $return = 'Workflow' } - bd7b778b-4aa8-4cde-8d90-8aeb821c0bd2 { $return = 'Workflow' } - 9d06afd9-66c9-49a6-b385-ea7509332b0b { $return = 'O365SBRM Service' } - 9ea1ad79-fdb6-4f9a-8bc3-2b70f96e34c7 { $return = 'Bing' } - 57fb890c-0dab-4253-a5e0-7188c88b2bb4 { $return = 'SharePoint Online Client' } - 45c10911-200f-4e27-a666-9e9fca147395 { $return = 'drawio' } - b73f62d0-210b-4396-a4c5-ea50c4fab79b { $return = 'Skype Business Voice Fraud Detection and Prevention' } - bc59ab01-8403-45c6-8796-ac3ef710b3e3 { $return = 'Outlook Online Add-in App' } - 035f9e1d-4f00-4419-bf50-bf2d87eb4878 { $return = 'Azure Monitor Restricted' } - 7c33bfcb-8d33-48d6-8e60-dc6404003489 { $return = 'Network Watcher' } - a0be0c72-870e-46f0-9c49-c98333a996f7 { $return = 'AzureDnsFrontendApp' } - 1e3e4475-288f-4018-a376-df66fd7fac5f { $return = 'NetworkTrafficAnalyticsService' } - 7557eb47-c689-4224-abcf-aef9bd7573df { $return = 'Skype for Business' } - c39c9bac-9d1f-4dfb-aa29-27f6365e5cb7 { $return = 'Azure Advisor' } - 2087bd82-7206-4c0a-b305-1321a39e5926 { $return = 'Microsoft To-Do' } - f8d98a96-0999-43f5-8af3-69971c7bb423 { $return = 'iOS Accounts' } - c27373d3-335f-4b45-8af9-fe81c240d377 { $return = 'P2P Server' } - 5c2ffddc-f1d7-4dc3-926e-3c1bd98e32bd { $return = 'RITS Dev' } - 982bda36-4632-4165-a46a-9863b1bbcf7d { $return = 'O365 Demeter' } - 98c8388a-4e86-424f-a176-d1288462816f { $return = 'OfficeFeedProcessors' } - bf9fc203-c1ff-4fd4-878b-323642e462ec { $return = 'Jarvis Transaction Service' } - 257601fd-462f-4a21-b623-7f719f0f90f4 { $return = 'Centralized Deployment' } - 2a486b53-dbd2-49c0-a2bc-278bdfc30833 { $return = 'Cortana at Work Service' } - 22d7579f-06c2-4baa-89d2-e844486adb9d { $return = 'Cortana at Work Bing Services' } - 4c8f074c-e32b-4ba7-b072-0f39d71daf51 { $return = 'IPSubstrate' } - a164aee5-7d0a-46bb-9404-37421d58bdf7 { $return = 'Microsoft Teams AuthSvc' } - 354b5b6d-abd6-4736-9f51-1be80049b91f { $return = 'Microsoft Mobile Application Management Backend' } - 82b293b2-d54d-4d59-9a95-39c1c97954a7 { $return = 'Tasks in a Box' } - fdc83783-b652-4258-a622-66bc85f1a871 { $return = 'FedExPackageTracking' } - d0597157-f0ae-4e23-b06c-9e65de434c4f { $return = 'Microsoft Teams Task Service' } - f5c26e74-f226-4ae8-85f0-b4af0080ac9e { $return = 'Application Insights API' } - 57c0fc58-a83a-41d0-8ae9-08952659bdfd { $return = 'Azure Cosmos DB Virtual Network To Network Resource Provider' } - 744e50be-c4ff-4e90-8061-cd7f1fabac0b { $return = 'LinkedIn Microsoft Graph Connector' } - 823dfde0-1b9a-415a-a35a-1ad34e16dd44 { $return = 'Microsoft Teams Wiki Images Migration' } - 3ab9b3bc-762f-4d62-82f7-7e1d653ce29f { $return = 'Microsoft Volume Licensing' } - 44eb7794-0e11-42b6-800b-dc31874f9f60 { $return = 'Alignable' } - c58637bb-e2e1-4312-8a00-04b5ffcd3403 { $return = 'SharePoint Online Client Extensibility' } - 62b732f7-fc71-40bc-b27d-35efcb0509de { $return = 'Microsoft Teams AadSync' } - 07978fee-621a-42df-82bb-3eabc6511c26 { $return = 'SurveyMonkey' } - 47ee738b-3f1a-4fc7-ab11-37e4822b007e { $return = 'Azure AD Application Proxy' } - 00000007-0000-0000-c000-000000000000 { $return = 'Dynamics CRM Online' } - 913c6de4-2a4a-4a61-a9ce-945d2b2ce2e0 { $return = 'Dynamics Lifecycle services' } - f217ad13-46b8-4c5b-b661-876ccdf37302 { $return = 'Attach OneDrive files to Asana' } - 00000008-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.DataMarket' } - 9b06ebd4-9068-486b-bdd2-dac26b8a5a7a { $return = 'Microsoft.DynamicsMarketing' } - e8ab36af-d4be-4833-a38b-4d6cf1cfd525 { $return = 'Microsoft Social Engagement' } - 8909aac3-be91-470c-8a0b-ff09d669af91 { $return = 'Microsoft Parature Dynamics CRM' } - 71234da4-b92f-429d-b8ec-6e62652e50d7 { $return = 'Microsoft Customer Engagement Portal' } - b861dbcc-a7ef-4219-a005-0e4de4ea7dcf { $return = 'Data Export Service for Microsoft Dynamics 365' } - 2db8cb1d-fb6c-450b-ab09-49b6ae35186b { $return = 'Microsoft Dynamics CRM Learning Path' } - 2e49aa60-1bd3-43b6-8ab6-03ada3d9f08b { $return = 'Dynamics Data Integration' } - } + + $return = $ServicePrincipals | Where-Object { $_.appId -eq $ID } | Select-Object -ExpandProperty DisplayName if ([string]::IsNullOrEmpty($return)) { $return = $Applications | Where-Object { $_.Appid -eq $ID } | Select-Object -ExpandProperty DisplayName @@ -390,12 +120,53 @@ Function Invoke-ListConditionalAccessPolicies { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter try { - $ConditionalAccessPolicyOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -tenantid $tenantfilter - $AllNamedLocations = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations' -tenantid $tenantfilter - $AllApplications = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/applications' -tenantid $tenantfilter - $AllRoleDefinitions = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions' -tenantid $tenantfilter - $GroupListOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenantfilter - $UserListOutput = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/users?$top=999' -tenantid $tenantfilter | Select-Object * -ExcludeProperty *extensionAttribute* + $Requests = @( + @{ + id = 'policies' + url = 'identity/conditionalAccess/policies' + method = 'GET' + } + @{ + id = 'namedLocations' + url = 'identity/conditionalAccess/namedLocations' + method = 'GET' + } + @{ + id = 'applications' + url = 'applications?$top=999&$select=appId,displayName' + method = 'GET' + } + @{ + id = 'roleDefinitions' + url = 'roleManagement/directory/roleDefinitions?$select=id,displayName' + method = 'GET' + } + @{ + id = 'groups' + url = 'groups?$top=999&$select=id,displayName' + method = 'GET' + } + @{ + id = 'users' + url = 'users?$top=999&$select=id,displayName,userPrincipalName' + method = 'GET' + } + @{ + id = 'servicePrincipals' + url = 'servicePrincipals?$top=999&$select=appId,displayName' + method = 'GET' + } + ) + + $GraphRequest = New-GraphBulkRequest -Requests $Requests -tenantid $tenantfilter -asapp $true + + $ConditionalAccessPolicyOutput = ($GraphRequest | Where-Object { $_.id -eq 'policies' }).body.value + $AllNamedLocations = ($GraphRequest | Where-Object { $_.id -eq 'namedLocations' }).body.value + $AllApplications = ($GraphRequest | Where-Object { $_.id -eq 'applications' } ).body.value + $AllRoleDefinitions = ($GraphRequest | Where-Object { $_.id -eq 'roleDefinitions' }).body.value + $GroupListOutput = ($GraphRequest | Where-Object { $_.id -eq 'groups' }).body.value + $UserListOutput = ($GraphRequest | Where-Object { $_.id -eq 'users' }).body.value + $GraphRequest = foreach ($cap in $ConditionalAccessPolicyOutput) { $temp = [PSCustomObject]@{ From bfc996b6ffd76c8be2aa35fa755b595dedbc17d6 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 14 Aug 2024 09:25:07 -0400 Subject: [PATCH 33/52] Update EditCAPolicy - Use -asapp to avoid lockout issues --- .../Tenant/Conditional/Invoke-EditCAPolicy.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-EditCAPolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-EditCAPolicy.ps1 index e3baf6e4a607..cca477e4a3ce 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-EditCAPolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Conditional/Invoke-EditCAPolicy.ps1 @@ -17,11 +17,10 @@ Function Invoke-EditCAPolicy { $ID = $request.query.guid $results = try { $EditBody = "{`"state`": `"$($request.query.state)`"}" - $Request = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta//identity/conditionalAccess/policies/$($id)" -tenantid $tenant -type PATCH -body $EditBody + $Request = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta//identity/conditionalAccess/policies/$($id)" -tenantid $tenant -type PATCH -body $EditBody -asapp $true Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Edited CA policy: $($ID)" -Sev 'Info' "Successfully edited CA policy: $($ID)" - } - catch { + } catch { "Failed to add CA policy: $($_.Exception.Message)" Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Failed editing CA policy $($ID). Error: $($_.Exception.Message)" -Sev 'Error' continue From 30a2cd535c8e0979fcbc77e030cf92b46cdc093a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 14 Aug 2024 09:57:08 -0400 Subject: [PATCH 34/52] Cleanup duplicate permissions --- Modules/CIPPCore/Public/SAMManifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/SAMManifest.json b/Modules/CIPPCore/Public/SAMManifest.json index 5c24954b1d7e..45df45352603 100644 --- a/Modules/CIPPCore/Public/SAMManifest.json +++ b/Modules/CIPPCore/Public/SAMManifest.json @@ -113,7 +113,6 @@ { "id": "1138cb37-bd11-4084-a2b7-9f71582aeddb", "type": "Role" }, { "id": "78145de6-330d-4800-a6ce-494ff2d33d07", "type": "Role" }, { "id": "9241abd9-d0e6-425a-bd4f-47ba86e767a4", "type": "Role" }, - { "id": "5b07b0dd-2377-4e44-a38d-703f09a0dc3c", "type": "Role" }, { "id": "243333ab-4d21-40cb-a475-36241daa0842", "type": "Role" }, { "id": "e330c4f0-4170-414e-a55a-2f022ec2b57b", "type": "Role" }, { "id": "5ac13192-7ace-4fcf-b828-1a26f28068ee", "type": "Role" }, @@ -154,7 +153,8 @@ { "id": "e0a7cdbb-08b0-4697-8264-0069786e9674", "type": "Scope" }, { "id": "19da66cb-0fb0-4390-b071-ebc76a349482", "type": "Role" }, { "id": "818c620a-27a9-40bd-a6a5-d96f7d610b4b", "type": "Scope" }, - { "id": "6931bccd-447a-43d1-b442-00a195474933", "type": "Role" } + { "id": "6931bccd-447a-43d1-b442-00a195474933", "type": "Role" }, + { "id": "c5366453-9fb0-48a5-a156-24f0c49a4b84", "type": "Scope" } ] }, { From d62a47b1ebe119469138aa8d4283dc6062d0734b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 14 Aug 2024 18:49:23 -0400 Subject: [PATCH 35/52] CIPP-SAM permissions - Update CPV functions, set delegated to no translate - Add function to parse SAMManifest.json/AdditionalPermissions.json and table - Add GUID check to ExecServicePrincipals --- .../Public/Add-CIPPApplicationPermission.ps1 | 21 ++- .../Public/Add-CIPPDelegatedPermission.ps1 | 43 +++-- .../Core/Invoke-ExecServicePrincipals.ps1 | 19 ++- .../Settings/Invoke-ExecSAMAppPermissions.ps1 | 74 +-------- .../GraphHelper/Get-CippSamPermissions.ps1 | 154 ++++++++++++++++++ 5 files changed, 223 insertions(+), 88 deletions(-) create mode 100644 Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 diff --git a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 index b63db8aeb3f8..b4b6c7d177ca 100644 --- a/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1 @@ -11,7 +11,26 @@ function Add-CIPPApplicationPermission { } Set-Location (Get-Item $PSScriptRoot).FullName if ($RequiredResourceAccess -eq 'CIPPDefaults') { - $RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess + #$RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess + + $Permissions = Get-CippSamPermissions -NoDiff + $RequiredResourceAccess = [System.Collections.Generic.List[object]]::new() + + foreach ($AppId in $Permissions.Permissions.PSObject.Properties.Name) { + $AppPermissions = @($Permissions.Permissions.$AppId.applicationPermissions) + $Resource = @{ + resourceAppId = $AppId + resourceAccess = [System.Collections.Generic.List[object]]::new() + } + foreach ($Permission in $AppPermissions) { + $Resource.ResourceAccess.Add(@{ + id = $Permission.id + type = 'Role' + }) + } + + $RequiredResourceAccess.Add($Resource) + } } $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -skipTokenCache $true -tenantid $Tenantfilter -NoAuthCheck $true $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property AppId -EQ $ApplicationId diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 03be9e2a44d6..37932d98b9e6 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -15,8 +15,24 @@ function Add-CIPPDelegatedPermission { } if ($RequiredResourceAccess -eq 'CIPPDefaults') { - $RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess - $AdditionalPermissions = Get-Content '.\AdditionalPermissions.json' | ConvertFrom-Json + $Permissions = Get-CippSamPermissions -NoDiff + $NoTranslateRequired = $Permissions.Type -eq 'Table' + $RequiredResourceAccess = [System.Collections.Generic.List[object]]::new() + foreach ($AppId in $Permissions.Permissions.PSObject.Properties.Name) { + $DelegatedPermissions = @($Permissions.Permissions.$AppId.delegatedPermissions) + $ResourceAccess = [System.Collections.Generic.List[object]]::new() + foreach ($Permission in $DelegatedPermissions) { + $ResourceAccess.Add(@{ + id = $Permission.value + type = 'Scope' + }) + } + $Resource = @{ + resourceAppId = $AppId + resourceAccess = @($ResourceAccess) + } + $RequiredResourceAccess.Add($Resource) + } if ($Tenantfilter -eq $env:TenantID) { $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId }) @@ -24,7 +40,6 @@ function Add-CIPPDelegatedPermission { # remove the partner center permission if not pushing to partner tenant $RequiredResourceAccess = $RequiredResourceAccess | Where-Object { $_.resourceAppId -ne 'fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd' } } - $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId }) } $Translator = Get-Content '.\PermissionsTranslator.json' | ConvertFrom-Json $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true -NoAuthCheck $true @@ -46,16 +61,22 @@ function Add-CIPPDelegatedPermission { continue } } - $AdditionalScopes = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $App.resourceAppId).resourceAccess | Where-Object -Property type -EQ 'Scope' + $DelegatedScopes = $App.resourceAccess | Where-Object -Property type -EQ 'Scope' - if ($AdditionalScopes) { - $NewScope = (@(($Translator | Where-Object { $_.id -in $DelegatedScopes.id }).value) + @($AdditionalScopes.id | Select-Object -Unique)) -join ' ' + if ($NoTranslateRequired) { + $NewScope = @($DelegatedScopes | ForEach-Object { $_.id } | Sort-Object -Unique) -join ' ' } else { - if ($NoTranslateRequired) { - $NewScope = @($DelegatedScopes | ForEach-Object { $_.id } | Sort-Object -Unique) -join ' ' - } else { - $NewScope = @(($Translator | Where-Object { $_.id -in $DelegatedScopes.id }).value | Sort-Object -Unique) -join ' ' + $NewScope = foreach ($Scope in $DelegatedScopes.id) { + if ($Scope -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + $TranslatedScope = ($Translator | Where-Object -Property id -EQ $Scope).value + if ($TranslatedScope) { + $TranslatedScope + } + } else { + $Scope + } } + $NewScope = (@($NewScope) | Sort-Object -Unique) -join ' ' } $OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id) @@ -83,7 +104,7 @@ function Add-CIPPDelegatedPermission { # Added permissions $Added = ($Compare | Where-Object { $_.SideIndicator -eq '=>' }).InputObject -join ' ' $Removed = ($Compare | Where-Object { $_.SideIndicator -eq '<=' }).InputObject -join ' ' - $Results.add("Successfully updated permissions for $($svcPrincipalId.displayName). $(if ($Added) { "Added: $Added"}) $(if ($Removed) { "Removed: $Removed"})") + $Results.add("Successfully updated permissions for $($svcPrincipalId.displayName). $(if ($Added) { "Added: $Added"}) $(if ($Removed) { "Removed: $Removed"})") } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 index e064d1fba096..c2fe14c59131 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecServicePrincipals.ps1 @@ -16,10 +16,21 @@ function Invoke-ExecServicePrincipals { try { switch ($Request.Query.Action) { 'Create' { - $Body = @{ - 'appId' = $Request.Query.AppId - } | ConvertTo-Json -Compress - $Results = New-GraphPostRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $TenantFilter -type POST -body $Body + $Action = 'Create' + if ($Request.Query.AppId -match '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$') { + $Body = @{ + 'appId' = $Request.Query.AppId + } | ConvertTo-Json -Compress + try { + $Results = New-GraphPostRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals' -tenantid $TenantFilter -type POST -body $Body + } catch { + $Results = "Unable to create service principal: $($_.Exception.Message)" + $Success = $false + } + } else { + $Results = 'Invalid AppId' + $Success = $false + } } default { if ($Request.Query.AppId) { diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 index ba76c1f038e9..a3c442c85656 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecSAMAppPermissions.ps1 @@ -15,7 +15,7 @@ function Invoke-ExecSAMAppPermissions { $Entity = @{ 'PartitionKey' = 'CIPP-SAM' 'RowKey' = 'CIPP-SAM' - 'Permissions' = [string]($Permissions | ConvertTo-Json -Depth 10 -Compress) + 'Permissions' = [string]($Permissions.Permissions | ConvertTo-Json -Depth 10 -Compress) } $Table = Get-CIPPTable -TableName 'AppPermissions' $null = Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force @@ -29,77 +29,7 @@ function Invoke-ExecSAMAppPermissions { } } default { - $ModuleBase = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase - $SamManifest = Get-Item "$ModuleBase\Public\SAMManifest.json" - $AdditionalPermissions = Get-Item "$ModuleBase\Public\AdditionalPermissions.json" - - $LastWrite = @{ - 'SAMManifest' = $SamManifest.LastWriteTime - 'AdditionalPermissions' = $AdditionalPermissions.LastWriteTime - } - - $ServicePrincipals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$select=appId,displayName,appRoles,publishedPermissionScopes' -tenantid $env:TenantID -NoAuthCheck $true - $SAMManifest = Get-Content -Path $SamManifest.FullName | ConvertFrom-Json - $AdditionalPermissions = Get-Content -Path $AdditionalPermissions.FullName | ConvertFrom-Json - - $RequiredResources = $SamManifest.requiredResourceAccess - - $AppIds = ($RequiredResources.resourceAppId + $AdditionalPermissions.resourceAppId) | Sort-Object -Unique - - $Permissions = @{} - foreach ($AppId in $AppIds) { - $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId - $AppPermissions = [System.Collections.Generic.List[object]]@() - $ManifestPermissions = ($RequiredResources | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess - $UnpublishedPermissions = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess - - foreach ($Permission in $ManifestPermissions) { - $AppPermissions.Add($Permission) - } - if ($UnpublishedPermissions) { - foreach ($Permission in $UnpublishedPermissions) { - $AppPermissions.Add($Permission) - } - } - - $ApplicationPermissions = [system.collections.generic.list[object]]@() - $DelegatedPermissions = [system.collections.generic.list[object]]@() - foreach ($Permission in $AppPermissions) { - if ($Permission.id -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { - if ($Permission.type -eq 'Role') { - $PermissionName = ($ServicePrincipal.appRoles | Where-Object -Property id -EQ $Permission.id).value - } else { - $PermissionName = ($ServicePrincipal.publishedPermissionScopes | Where-Object -Property id -EQ $Permission.id).value - } - } else { - $PermissionName = $Permission.id - } - - if ($Permission.type -eq 'Role') { - $ApplicationPermissions.Add([PSCustomObject]@{ - id = $Permission.id - value = $PermissionName - - }) - } else { - $DelegatedPermissions.Add([PSCustomObject]@{ - id = $Permission.id - value = $PermissionName - }) - } - } - - $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId - $Permissions.$AppId = @{ - applicationPermissions = @($ApplicationPermissions | Sort-Object -Property label) - delegatedPermissions = @($DelegatedPermissions | Sort-Object -Property label) - } - } - - $Body = @{ - 'Permissions' = $Permissions - 'LastUpdate' = $LastWrite - } + $Body = Get-CippSamPermissions } } diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 new file mode 100644 index 000000000000..cd5a5e7997c8 --- /dev/null +++ b/Modules/CIPPCore/Public/GraphHelper/Get-CippSamPermissions.ps1 @@ -0,0 +1,154 @@ +function Get-CippSamPermissions { + <# + .SYNOPSIS + This script retrieves the CIPP-SAM permissions. + + .DESCRIPTION + The Get-CippSamManifest function is used to retrieve the CIPP-SAM permissions either from the manifest files or table. + + .EXAMPLE + Get-CippSamManifest + Retrieves the CIPP SAM manifest located in the module root + + .FUNCTIONALITY + Internal + #> + [CmdletBinding(DefaultParameterSetName = 'Default')] + Param( + [Parameter(ParameterSetName = 'ManifestOnly')] + [switch]$ManifestOnly, + [Parameter(ParameterSetName = 'Default')] + [switch]$SavedOnly, + [Parameter(ParameterSetName = 'Diff')] + [switch]$NoDiff + ) + + if (!$SavedOnly.IsPresent) { + $ModuleBase = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase + $SamManifest = Get-Item "$ModuleBase\Public\SAMManifest.json" + $AdditionalPermissions = Get-Item "$ModuleBase\Public\AdditionalPermissions.json" + + $ServicePrincipals = New-GraphGetRequest -Uri 'https://graph.microsoft.com/beta/servicePrincipals?$top=999&$select=appId,displayName,appRoles,publishedPermissionScopes' -tenantid $env:TenantID -NoAuthCheck $true + $SAMManifest = Get-Content -Path $SamManifest.FullName | ConvertFrom-Json + $AdditionalPermissions = Get-Content -Path $AdditionalPermissions.FullName | ConvertFrom-Json + + $RequiredResources = $SamManifest.requiredResourceAccess + + $AppIds = ($RequiredResources.resourceAppId + $AdditionalPermissions.resourceAppId) | Sort-Object -Unique + + $Permissions = @{} + foreach ($AppId in $AppIds) { + $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId + $AppPermissions = [System.Collections.Generic.List[object]]@() + $ManifestPermissions = ($RequiredResources | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess + $UnpublishedPermissions = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $AppId).resourceAccess + + foreach ($Permission in $ManifestPermissions) { + $AppPermissions.Add($Permission) + } + if ($UnpublishedPermissions) { + foreach ($Permission in $UnpublishedPermissions) { + $AppPermissions.Add($Permission) + } + } + + $ApplicationPermissions = [system.collections.generic.list[object]]@() + $DelegatedPermissions = [system.collections.generic.list[object]]@() + foreach ($Permission in $AppPermissions) { + if ($Permission.id -match '^[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}$') { + if ($Permission.type -eq 'Role') { + $PermissionName = ($ServicePrincipal.appRoles | Where-Object -Property id -EQ $Permission.id).value + } else { + $PermissionName = ($ServicePrincipal.publishedPermissionScopes | Where-Object -Property id -EQ $Permission.id).value + } + } else { + $PermissionName = $Permission.id + } + + if ($Permission.type -eq 'Role') { + $ApplicationPermissions.Add([PSCustomObject]@{ + id = $Permission.id + value = $PermissionName + + }) + } else { + $DelegatedPermissions.Add([PSCustomObject]@{ + id = $Permission.id + value = $PermissionName + }) + } + } + + $ServicePrincipal = $ServicePrincipals | Where-Object -Property appId -EQ $AppId + $Permissions.$AppId = @{ + applicationPermissions = @($ApplicationPermissions | Sort-Object -Property label) + delegatedPermissions = @($DelegatedPermissions | Sort-Object -Property label) + } + } + } + if ($ManifestOnly) { + return [PSCustomObject]@{ + Permissions = $Permissions + Type = 'Manifest' + } + } + + $Table = Get-CippTable -tablename 'AppPermissions' + $SavedPermissions = Get-CippAzDataTableEntity @Table -Filter "PartitionKey eq 'CIPP-SAM' and RowKey eq 'CIPP-SAM'" + if ($SavedPermissions.Permissions) { + $SavedPermissions.Permissions = $SavedPermissions.Permissions | ConvertFrom-Json + } else { + $SavedPermissions = @{ + Permissions = [PSCustomObject]@{} + } + } + + if ($SavedOnly.IsPresent) { + $SavedPermissions | Add-Member -MemberType NoteProperty -Name Type -Value 'Table' + return $SavedPermissions + } + + if (!$NoDiff -and $SavedPermissions.Permissions) { + $DiffPermissions = @{} + foreach ($AppId in $AppIds) { + $ManifestSpPermissions = $Permissions.$AppId + $SavedSpPermission = $SavedPermissions.Permissions.$AppId + $MissingApp = [System.Collections.Generic.List[object]]::new() + $MissingDelegated = [System.Collections.Generic.List[object]]::new() + foreach ($Permission in $ManifestSpPermissions.applicationPermissions) { + if ($SavedSpPermission.applicationPermissions.id -notcontains $Permission.id) { + $MissingApp.Add($Permission) + } + } + foreach ($Permission in $ManifestSpPermissions.delegatedPermissions) { + if ($SavedSpPermission.delegatedPermissions.id -notcontains $Permission.id) { + $MissingDelegated.Add($Permission) + } + } + if ($MissingApp -or $MissingDelegated) { + $DiffPermissions.$AppId = @{ + applicationPermissions = $MissingApp + delegatedPermissions = $MissingDelegated + } + } + } + } + + $SamAppPermissions = @{} + if (($SavedPermissions.Permissions.PSObject.Properties.Name | Measure-Object).Count -gt 0) { + $SamAppPermissions.Permissions = $SavedPermissions.Permissions + $SamAppPermissions.Type = 'Table' + } else { + $SamAppPermissions.Permissions = $Permissions + $SamAppPermissions.Type = 'Manifest' + } + + if (!$NoDiff.IsPresent) { + $SamAppPermissions.MissingPermissions = $DiffPermissions + } + + $SamAppPermissions = $SamAppPermissions | ConvertTo-Json -Depth 10 -Compress | ConvertFrom-Json + + return $SamAppPermissions +} + From 59e9b86be8d97ce9d9f260af092479213a7ba424 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 15 Aug 2024 21:32:47 -0400 Subject: [PATCH 36/52] Hudu sync - Fix BEC link --- Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index aa86714cdef0..e74e230cc7ef 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -605,7 +605,7 @@ function Invoke-HuduExtensionSync { if ($EnableCIPP) { $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/view?customerId=$($Tenant.customerid)&userId=$($User.id)&tenantDomain=$($Tenant.defaultDomainName)&userEmail=$($User.userPrincipalName)" -Icon 'far fa-eye' -Title 'CIPP - View User')) $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/users/edit?customerId=$($Tenant.customerid)&userId=$($User.id)&tenantDomain=$($Tenant.defaultDomainName)&userEmail=$($User.userPrincipalName)" -Icon 'fas fa-user-cog' -Title 'CIPP - Edit User')) - $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/ViewBec?customerId=$($Tenant.customerid)&userId=$($User.id)&tenantDomain=$($Tenant.defaultDomainName)&userEmail=$($User.userPrincipalName)" -Icon 'fas fa-user-secret' -Title 'CIPP - BEC Tool')) + $CIPPLinksFormatted.add((Get-HuduLinkBlock -URL "$($CIPPURL)/identity/administration/ViewBec?customerId=$($Tenant.customerid)&userId=$($User.id)&tenantDomain=$($Tenant.defaultDomainName)&ID=$($User.userPrincipalName)" -Icon 'fas fa-user-secret' -Title 'CIPP - BEC Tool')) } [System.Collections.Generic.List[PSCustomObject]]$UserLinksFormatted = @() From d967069c405615e240a931e9d9009b9f41dff1f2 Mon Sep 17 00:00:00 2001 From: Chris Brannon Date: Fri, 16 Aug 2024 10:54:26 -0400 Subject: [PATCH 37/52] Enable Limited number of snoozes --- .../CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 index 86f215bfa54f..35eb01b0cbf5 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardNudgeMFA.ps1 @@ -59,6 +59,7 @@ function Invoke-CIPPStandardNudgeMFA { $Body = $CurrentInfo $body.registrationEnforcement.authenticationMethodsRegistrationCampaign.state = $Settings.state $body.registrationEnforcement.authenticationMethodsRegistrationCampaign.snoozeDurationInDays = $Settings.snoozeDurationInDays + $body.registrationEnforcement.authenticationMethodsRegistrationCampaign.enforceRegistrationAfterAllowedSnoozes = $true $body = ConvertTo-Json -Depth 10 -InputObject ($body | Select-Object registrationEnforcement) New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy' -Type patch -Body $body -ContentType 'application/json' From 7bf63bc6751446bf6d7a1593bdaaa1cf47830c68 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 16 Aug 2024 16:18:25 -0400 Subject: [PATCH 38/52] Improve hudu sync --- .../Public/Hudu/Invoke-HuduExtensionSync.ps1 | 156 +++++++++++------- 1 file changed, 93 insertions(+), 63 deletions(-) diff --git a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 index e74e230cc7ef..5723734a14d2 100644 --- a/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 +++ b/Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1 @@ -7,87 +7,117 @@ function Invoke-HuduExtensionSync { $Configuration, $TenantFilter ) - Connect-HuduAPI -configuration $Configuration - - # Get mapping configuration - $MappingTable = Get-CIPPTable -TableName 'CippMapping' - $Mappings = Get-CIPPAzDataTableEntity @MappingTable -Filter "PartitionKey eq 'HuduMapping' or PartitionKey eq 'HuduFieldMapping'" - #Write-Host ($Mappings | ConvertTo-Json) - $defaultdomain = $TenantFilter - $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $TenantFilter } - $TenantMap = $Mappings | Where-Object { $_.RowKey -eq $Tenant.customerId } + try { + Connect-HuduAPI -configuration $Configuration + + $CompanyResult = [PSCustomObject]@{ + Name = $Tenant.displayName + Users = 0 + Devices = 0 + Errors = [System.Collections.Generic.List[string]]@() + Logs = [System.Collections.Generic.List[string]]@() + } - $HuduAssetCache = Get-CippTable -tablename 'CacheHuduAssets' + # Get mapping configuration + $MappingTable = Get-CIPPTable -TableName 'CippMapping' + $Mappings = Get-CIPPAzDataTableEntity @MappingTable -Filter "PartitionKey eq 'HuduMapping' or PartitionKey eq 'HuduFieldMapping'" - if (!$TenantMap) { - return 'Tenant not found in mapping table' - } + $defaultdomain = $TenantFilter + $Tenant = Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $TenantFilter } + $TenantMap = $Mappings | Where-Object { $_.RowKey -eq $Tenant.customerId } - $CompanyResult = [PSCustomObject]@{ - Name = $Tenant.displayName - Users = 0 - Devices = 0 - Errors = [System.Collections.Generic.List[string]]@() - Logs = [System.Collections.Generic.List[string]]@() - } + # Get Asset cache + $HuduAssetCache = Get-CippTable -tablename 'CacheHuduAssets' - $PeopleLayoutId = $Mappings | Where-Object { $_.RowKey -eq 'Users' } | Select-Object -ExpandProperty IntegrationId - $CreateUsers = $Configuration.CreateMissingUsers - $DeviceLayoutId = $Mappings | Where-Object { $_.RowKey -eq 'Devices' } | Select-Object -ExpandProperty IntegrationId - $CreateDevices = $Configuration.CreateMissingDevices + # Import license mapping + Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName + $LicTable = Import-Csv Conversiontable.csv - if ($PeopleLayoutId) { - $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $PeopleLayoutId - } - if ($DeviceLayoutId) { - $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $DeviceLayoutId - } + $CompanyResult.Logs.Add('Starting Hudu Extension Sync') - $importDomains = $Configuration.ImportDomains - $monitordomains = $Configuration.MonitorDomains - $IntuneDesktopDeviceTypes = 'windowsRT,macMDM' -split ',' + # Get CIPP URL + $ConfigTable = Get-Cipptable -tablename 'Config' + $Config = Get-CippAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" + $CIPPURL = 'https://{0}' -f $Config.Value + $EnableCIPP = $true - $DefaultSerials = [System.Collections.Generic.List[string]]@('SystemSerialNumber', 'To Be Filled By O.E.M.', 'System Serial Number', '0123456789', '123456789', 'TobefilledbyO.E.M.') + # Get Hudu Extension Cache + $ExtensionCache = Get-ExtensionCacheData -TenantFilter $Tenant.defaultDomainName + $company_id = $TenantMap.IntegrationId - if ($Configuration.ExcludeSerials) { - $ExcludeSerials = $DefaultSerials.AddRange($Configuration.ExcludeSerials -split ',') - } else { - $ExcludeSerials = $DefaultSerials - } + # If tenant not found in mapping table, return error + if (!$TenantMap) { + return 'Tenant not found in mapping table' + } - Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.Parent.FullName - $LicTable = Import-Csv Conversiontable.csv + # Get Hudu Layout mappings + $PeopleLayoutId = $Mappings | Where-Object { $_.RowKey -eq 'Users' } | Select-Object -ExpandProperty IntegrationId + $DeviceLayoutId = $Mappings | Where-Object { $_.RowKey -eq 'Devices' } | Select-Object -ExpandProperty IntegrationId - $CompanyResult.Logs.Add('Starting Hudu Extension Sync') + try { + if (![string]::IsNullOrEmpty($PeopleLayoutId)) { + $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $PeopleLayoutId + $CreateUsers = $Configuration.CreateMissingUsers + $PeopleLayout = Get-HuduAssetLayouts -Id $PeopleLayoutId + if ($PeopleLayout.id) { + $People = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $PeopleLayout.id + } else { + $CreateUsers = $false + $People = @() + } + } else { + $CreateUsers = $false + $People = @() + } + } catch { + $CreateUsers = $false + $People = @() + $CompanyResult.Errors.add("Company: Unable to fetch People $_") + Write-Host "Hudu People - Error: $_" + } - #$AssignedMap = Get-AssignedMap - #$AssignedNameMap = Get-AssignedNameMap + Write-Host "CreateUsers: $CreateUsers" - $EnableCIPP = $true - $ConfigTable = Get-Cipptable -tablename 'Config' - $Config = Get-CippAzDataTableEntity @ConfigTable -Filter "PartitionKey eq 'InstanceProperties' and RowKey eq 'CIPPURL'" - $CIPPURL = 'https://{0}' -f $Config.Value + try { + if (![string]::IsNullOrEmpty($DeviceLayoutId)) { + $null = Add-HuduAssetLayoutM365Field -AssetLayoutId $DeviceLayoutId + $CreateDevices = $Configuration.CreateMissingDevices + $DesktopsLayout = Get-HuduAssetLayouts -Id $DeviceLayoutId + if ($DesktopsLayout.id) { + $HuduDesktopDevices = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $DesktopsLayout.id + $HuduDevices = $HuduDesktopDevices + } else { + $CreateDevices = $false + $HuduDevices = @() + } + } else { + $CreateDevices = $false + $HuduDevices = @() + } + } catch { + $CreateDevices = $false + $HuduDevices = @() + $CompanyResult.Errors.add("Company: Unable to fetch Devices $_") + Write-Host "Hudu Devices - Error: $_" + } - $ExtensionCache = Get-ExtensionCacheData -TenantFilter $Tenant.defaultDomainName + Write-Host "CreateDevices: $CreateDevices" - try { - $company_id = $TenantMap.IntegrationId + $importDomains = $Configuration.ImportDomains + $monitordomains = $Configuration.MonitorDomains - if ($PeopleLayoutId) { - $PeopleLayout = Get-HuduAssetLayouts -Id $PeopleLayoutId - $People = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $PeopleLayout.id - } + # Defaults + $IntuneDesktopDeviceTypes = 'windowsRT,macMDM' -split ',' + $DefaultSerials = [System.Collections.Generic.List[string]]@('SystemSerialNumber', 'To Be Filled By O.E.M.', 'System Serial Number', '0123456789', '123456789', 'TobefilledbyO.E.M.') - if ($DeviceLayoutId) { - $DesktopsLayout = Get-HuduAssetLayouts -Id $DeviceLayoutId - $HuduDesktopDevices = Get-HuduAssets -CompanyId $company_id -AssetLayoutId $DesktopsLayout.id + if ($Configuration.ExcludeSerials) { + $ExcludeSerials = $DefaultSerials.AddRange($Configuration.ExcludeSerials -split ',') + } else { + $ExcludeSerials = $DefaultSerials } $HuduRelations = Get-HuduRelations - - $HuduDevices = $HuduDesktopDevices - $Links = @( @{ Title = 'M365 Admin Portal' @@ -626,7 +656,7 @@ function Invoke-HuduExtensionSync { $UserBody = "
$AssignedPlansBlock
$UserLinksBlock
$($UserOverviewBlock)$($UserMailDetailsBlock)$($OneDriveBlock)$($UserMailSettingsBlock)$($UserPoliciesBlock)
$($UserDevicesDetailsBlock)
$($UserGroupsBlock)
" - if ($PeopleLayoutId) { + if (![string]::IsNullOrEmpty($PeopleLayoutId)) { $UserAssetFields = @{ microsoft_365 = $UserBody email_address = $user.userPrincipalName @@ -802,7 +832,7 @@ function Invoke-HuduExtensionSync { } $NewHash = Get-StringHash -String $DeviceIntuneDetailshtml - if ($DeviceLayoutId) { + if (![string]::IsNullOrEmpty($DeviceLayoutId)) { if ($HuduDevice) { if (($HuduDevice | Measure-Object).count -eq 1) { $ExistingAsset = Get-CIPPAzDataTableEntity @HuduAssetCache -Filter "PartitionKey eq 'HuduDevice' and CompanyId eq '$company_id' and RowKey eq '$($HuduDevice.id)'" From 7b7b2c53ee7e0f271c3a1a011f60a4e8e39ebe81 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 16 Aug 2024 17:28:31 -0400 Subject: [PATCH 39/52] Create Get-CIPPAlertHuntressRogueApps.ps1 --- .../Alerts/Get-CIPPAlertHuntressRogueApps.ps1 | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 new file mode 100644 index 000000000000..ae6a08cdeeda --- /dev/null +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 @@ -0,0 +1,34 @@ +function Get-CIPPAlertHuntressRogueApps { + <# + .SYNOPSIS + Check for rogue apps in a Tenant + .DESCRIPTION + This function checks for rogue apps in the tenant by comparing the service principals in the tenant with a list of known rogue apps provided by Huntress. + .FUNCTIONALITY + Entrypoint + .LINK + https://huntresslabs.github.io/rogueapps/ + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $false)] + [Alias('input')] + $InputValue, + $TenantFilter + ) + + try { + $RogueApps = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/huntresslabs/rogueapps/main/public/rogueapps.json' + $RogueAppFilter = $RogueApps.appId -join "','" + $ServicePrincipals = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$filter=appId in ('$RogueAppFilter')" -tenantid $TenantFilter + + if (($ServicePrincipals | Measure-Object).Count -gt 0) { + $AlertData = foreach ($ServicePrincipal in $ServicePrincipals) { + $RogueApps | Where-Object { $_.appId -eq $ServicePrincipal.appId } + } + Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData + } + } catch { + #Write-AlertMessage -tenant $($TenantFilter) -message "Failed to check for rogue apps for $($TenantFilter): $(Get-NormalizedError -message $_.Exception.message)" + } +} From 814ed89333678a9b2f496f040347ecb358c76ac6 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 17 Aug 2024 09:19:03 -0400 Subject: [PATCH 40/52] Update Get-CIPPAlertHuntressRogueApps.ps1 --- .../Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 index ae6a08cdeeda..5dedfa8adfde 100644 --- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 @@ -24,7 +24,17 @@ function Get-CIPPAlertHuntressRogueApps { if (($ServicePrincipals | Measure-Object).Count -gt 0) { $AlertData = foreach ($ServicePrincipal in $ServicePrincipals) { - $RogueApps | Where-Object { $_.appId -eq $ServicePrincipal.appId } + $RogueApp = $RogueApps | Where-Object { $_.appId -eq $ServicePrincipal.appId } + [pscustomobject]@{ + appDisplayName = $RogueApp.appDisplayName + appId = $RogueApp.appId + description = $RogueApp.description + accountEnabled = $ServicePrincipal.accountEnabled + createdDateTime = $ServicePrincipal.createdDateTime + tags = $RogueApp.tags -join ', ' + references = $RogueApp.references -join ', ' + huntressAdded = $RogueApp.dateAdded + } } Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData } From 0e3dfb16a68fc15db38ff0c71730edbc0cb20276 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 17 Aug 2024 09:30:17 -0400 Subject: [PATCH 41/52] Update Get-CIPPAlertHuntressRogueApps.ps1 --- .../Alerts/Get-CIPPAlertHuntressRogueApps.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 index 5dedfa8adfde..efd1326fbcc5 100644 --- a/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 +++ b/Modules/CIPPCore/Public/Alerts/Get-CIPPAlertHuntressRogueApps.ps1 @@ -26,14 +26,14 @@ function Get-CIPPAlertHuntressRogueApps { $AlertData = foreach ($ServicePrincipal in $ServicePrincipals) { $RogueApp = $RogueApps | Where-Object { $_.appId -eq $ServicePrincipal.appId } [pscustomobject]@{ - appDisplayName = $RogueApp.appDisplayName - appId = $RogueApp.appId - description = $RogueApp.description - accountEnabled = $ServicePrincipal.accountEnabled - createdDateTime = $ServicePrincipal.createdDateTime - tags = $RogueApp.tags -join ', ' - references = $RogueApp.references -join ', ' - huntressAdded = $RogueApp.dateAdded + 'App Name' = $RogueApp.appDisplayName + 'App Id' = $RogueApp.appId + 'Description' = $RogueApp.description + 'Enabled' = $ServicePrincipal.accountEnabled + 'Created' = $ServicePrincipal.createdDateTime + 'Tags' = $RogueApp.tags -join ', ' + 'References' = $RogueApp.references -join ', ' + 'Huntress Added' = $RogueApp.dateAdded } } Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData From e7c9f3881c857cdc34205bbefda5a4014079e873 Mon Sep 17 00:00:00 2001 From: Chris Brannon Date: Sat, 17 Aug 2024 17:04:07 -0400 Subject: [PATCH 42/52] Assign & Unassign Teams Voice User Numbers --- ...cRemoveTeamsVoicePhoneNumberAssignment.ps1 | 31 ++++++++++++++++ ...ke-ExecTeamsVoicePhoneNumberAssignment.ps1 | 36 +++++++++++++++++++ .../Invoke-ListTeamsLisLocation.ps1 | 31 ++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecTeamsVoicePhoneNumberAssignment.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsLisLocation.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 new file mode 100644 index 000000000000..55619f07deb5 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment.ps1 @@ -0,0 +1,31 @@ +using namespace System.Net + +Function Invoke-ExecRemoveTeamsVoicePhoneNumberAssignment { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Teams.Voice.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $tenantFilter = $Request.Body.TenantFilter + try { + $null = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Remove-CsPhoneNumberAssignment' -CmdParams @{Identity = $Request.Body.AssignedTo; PhoneNumber = $Request.Body.PhoneNumber; PhoneNumberType = $Request.Body.PhoneNumberType; ErrorAction = 'stop'} + $Results = [pscustomobject]@{'Results' = "Successfully unassigned $($Request.Body.PhoneNumber) from $($Request.Body.AssignedTo)"} + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message $($Results.Results) -Sev 'Info' + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $Results = [pscustomobject]@{'Results' = $ErrorMessage} + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message $($Results.Results) -Sev 'Error' + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Results + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecTeamsVoicePhoneNumberAssignment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecTeamsVoicePhoneNumberAssignment.ps1 new file mode 100644 index 000000000000..fa04ac2c41d8 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ExecTeamsVoicePhoneNumberAssignment.ps1 @@ -0,0 +1,36 @@ +using namespace System.Net + +Function Invoke-ExecTeamsVoicePhoneNumberAssignment { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Teams.Voice.ReadWrite + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $tenantFilter = $Request.Body.TenantFilter + try { + if ($Request.Body.locationOnly) { + $null = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Set-CsPhoneNumberAssignment' -CmdParams @{LocationId = $Request.Body.input; PhoneNumber = $Request.Body.PhoneNumber; ErrorAction = 'stop'} + $Results = [pscustomobject]@{'Results' = "Successfully assigned emergency location to $($Request.Body.PhoneNumber)"} + } else { + $null = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Set-CsPhoneNumberAssignment' -CmdParams @{Identity = $Request.Body.input; PhoneNumber = $Request.Body.PhoneNumber; PhoneNumberType = $Request.Body.PhoneNumberType; ErrorAction = 'stop'} + $Results = [pscustomobject]@{'Results' = "Successfully assigned $($Request.Body.PhoneNumber) to $($Request.Body.input)"} + } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message $($Results.Results) -Sev 'Info' + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $Results = [pscustomobject]@{'Results' = $ErrorMessage} + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($TenantFilter) -message $($Results.Results) -Sev 'Error' + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Results + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsLisLocation.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsLisLocation.ps1 new file mode 100644 index 000000000000..198f83c4680a --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-ListTeamsLisLocation.ps1 @@ -0,0 +1,31 @@ +using namespace System.Net + +Function Invoke-ListTeamsLisLocation { + <# + .FUNCTIONALITY + Entrypoint + .ROLE + Teams.Voice.Read + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + $TenantFilter = $Request.Query.TenantFilter + try { + $EmergencyLocations = New-TeamsRequest -TenantFilter $TenantFilter -Cmdlet 'Get-CsOnlineLisLocation' + $StatusCode = [HttpStatusCode]::OK + } catch { + $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message + $StatusCode = [HttpStatusCode]::Forbidden + $EmergencyLocations = $ErrorMessage + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = @($EmergencyLocations) + }) + +} From c86f83f20826b30008230d76d4577d651abb5f13 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sun, 18 Aug 2024 18:30:06 +0200 Subject: [PATCH 43/52] Fixes jitadmin issues --- .../Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 | 4 ++-- .../CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 | 8 ++++---- Modules/CippEntrypoints/CippEntrypoints.psm1 | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 index 51e7e429beb7..680460302a7f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecJITAdmin.ps1 @@ -136,7 +136,7 @@ Function Invoke-ExecJITAdmin { } } - $Parameters = [pscustomobject]@{ + $Parameters = @{ TenantFilter = $Request.Body.TenantFilter User = @{ 'UserPrincipalName' = $Username @@ -153,7 +153,7 @@ Function Invoke-ExecJITAdmin { value = 'Set-CIPPUserJITAdmin' label = 'Set-CIPPUserJITAdmin' } - Parameters = $Parameters + Parameters = [pscustomobject]$Parameters ScheduledTime = $Request.Body.StartDate PostExecution = @{ Webhook = [bool]$Request.Body.PostExecution.Webhook diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 index 46f092f6aff3..1755a9f58378 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-NormalizedError.ps1 @@ -21,16 +21,16 @@ function Get-NormalizedError { #We need to check if the message is in one of these fields, and if so, return it. if ($JSONMsg.error.innererror.message) { - Write-Host 'innererror.message found' + Write-Host "innererror.message found: $($JSONMsg.error.innererror.message)" $message = $JSONMsg.error.innererror.message } elseif ($JSONMsg.error.message) { - Write-Host 'error.message found' + Write-Host "error.message found: $($JSONMsg.error.message)" $message = $JSONMsg.error.message } elseif ($JSONMsg.error.details.message) { - Write-Host 'error.details.message found' + Write-Host "error.details.message found: $($JSONMsg.error.details.message)" $message = $JSONMsg.error.details.message } elseif ($JSONMsg.error.innererror.internalException.message) { - Write-Host 'error.innererror.internalException.message found' + Write-Host "error.innererror.internalException.message found: $($JSONMsg.error.innererror.internalException.message)" $message = $JSONMsg.error.innererror.internalException.message } diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index 11bc4be9bb15..aae4096c53a1 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -9,7 +9,6 @@ function Receive-CippHttpTrigger { $Request, $TriggerMetadata ) - Write-Host "Request: $($Request | ConvertTo-Json -Depth 100 -Compress)" # Convert the request to a PSCustomObject because the httpContext is case sensitive since 7.3 $Request = $Request | ConvertTo-Json -Depth 100 | ConvertFrom-Json Set-Location (Get-Item $PSScriptRoot).Parent.Parent.FullName From baddbf52f5e7c4b8e4a63d0e843b7c9a4c73f895 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 19 Aug 2024 12:49:56 +0200 Subject: [PATCH 44/52] fixes assigning of EDR policy --- .../Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 index bc94e7cf5851..72136b94b9d2 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 @@ -210,11 +210,11 @@ Function Invoke-AddDefenderDeployment { settings = @($EDRSettings) } Write-Host ( $EDRbody) - $CheckExististingEDR = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant + $CheckExististingEDR = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant | Where-Object -Property Name -eq 'EDR Configuration' if ('EDR Configuration' -in $CheckExististingEDR.Name) { "$($Tenant): EDR Policy already exists. Skipping" } else { - #$EDRRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant -type POST -body $EDRbody + $EDRRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant -type POST -body $EDRbody if ($ASR.AssignTo -ne 'none') { $AssignBody = if ($ASR.AssignTo -ne 'AllDevicesAndUsers') { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($asr.AssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' } $assign = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$($EDRRequest.id)')/assign" -tenantid $tenant -type POST -body $AssignBody From a308f034213d7019f5ff552402f6ee42235e2e66 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 19 Aug 2024 12:53:57 +0200 Subject: [PATCH 45/52] intune deployment for Defender fixes --- .../Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 index 72136b94b9d2..59527aa0dc92 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/MEM/Invoke-AddDefenderDeployment.ps1 @@ -35,8 +35,14 @@ Function Invoke-AddDefenderDeployment { iosMobileApplicationManagementEnabled = [bool]$Compliance.appSync microsoftDefenderForEndpointAttachEnabled = [bool]$true } | ConvertTo-Json -Compress - $SettingsRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/' -tenantid $tenant -type POST -body $SettingsObj -AsApp $true - "$($Tenant): Successfully set Defender Compliance and Reporting settings" + $ExistingSettings = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/fc780465-2017-40d4-a0c5-307022471b92' -tenantid $tenant + if ($ExistingSettings) { + "Defender Intune Configuration already active for $($Tenant). Skipping" + } else { + $SettingsRequest = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/mobileThreatDefenseConnectors/' -tenantid $tenant -type POST -body $SettingsObj -AsApp $true + "$($Tenant): Successfully set Defender Compliance and Reporting settings" + } + $Settings = switch ($PolicySettings) { { $_.ScanArchives } { @@ -210,7 +216,7 @@ Function Invoke-AddDefenderDeployment { settings = @($EDRSettings) } Write-Host ( $EDRbody) - $CheckExististingEDR = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant | Where-Object -Property Name -eq 'EDR Configuration' + $CheckExististingEDR = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -tenantid $tenant | Where-Object -Property Name -EQ 'EDR Configuration' if ('EDR Configuration' -in $CheckExististingEDR.Name) { "$($Tenant): EDR Policy already exists. Skipping" } else { From 2f74278072523c77d0deb790ce53fce2798f29d4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 19 Aug 2024 17:47:28 +0200 Subject: [PATCH 46/52] test --- .../Activity Triggers/Push-ExecScheduledCommand.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index f157dd0884b9..da0cae51d4f9 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -4,6 +4,7 @@ function Push-ExecScheduledCommand { Entrypoint #> param($Item) + $item = $Item | ConvertTo-Json -Depth 100 | ConvertFrom-Json Write-Host "We are going to be running a scheduled task: $($Item.TaskInfo | ConvertTo-Json -Depth 10)" $Table = Get-CippTable -tablename 'ScheduledTasks' @@ -100,7 +101,7 @@ function Push-ExecScheduledCommand { default { throw "Unsupported recurrence format: $($task.Recurrence)" } } $nextRunUnixTime = [int64]$task.ScheduledTime + [int64]$secondsToAdd - Write-Host "The job is recurring and should occur again at: $nextRunUnixTime" + Write-Host "The job is recurring. It was scheduled for $($task.ScheduledTime). The next runtime should be $nextRunUnixTime" Update-AzDataTableEntity @Table -Entity @{ PartitionKey = $task.PartitionKey RowKey = $task.RowKey From b52d0095b84f252775c724aea44211c110800081 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 19 Aug 2024 12:06:36 -0400 Subject: [PATCH 47/52] Fix for scheduled time in the past --- Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 | 4 ++++ .../Activity Triggers/Push-ExecScheduledCommand.ps1 | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 index 4b08eda522bb..30aa79cffaa2 100644 --- a/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1 @@ -51,6 +51,10 @@ function Add-CIPPScheduledTask { $task.Recurrence.value } + if ([int64]$task.ScheduledTime -eq 0 -or [string]::IsNullOrEmpty($task.ScheduledTime)) { + $task.ScheduledTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds + } + $entity = @{ PartitionKey = [string]'ScheduledTask' TaskState = [string]'Planned' diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 index da0cae51d4f9..65ce3ff5d36a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecScheduledCommand.ps1 @@ -100,6 +100,14 @@ function Push-ExecScheduledCommand { '(\d+)d$' { [int64]$matches[1] * 86400 } default { throw "Unsupported recurrence format: $($task.Recurrence)" } } + + if ($secondsToAdd -gt 0) { + $unixtimeNow = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds + if ([int64]$task.ScheduledTime -lt ($unixtimeNow - $secondsToAdd)) { + $task.ScheduledTime = $unixtimeNow + } + } + $nextRunUnixTime = [int64]$task.ScheduledTime + [int64]$secondsToAdd Write-Host "The job is recurring. It was scheduled for $($task.ScheduledTime). The next runtime should be $nextRunUnixTime" Update-AzDataTableEntity @Table -Entity @{ From c836ef6fe8f5e803d4ec1840c20f5e4adee0843d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 19 Aug 2024 18:18:09 +0200 Subject: [PATCH 48/52] Add Set-CIPPAlwaysShowFrom function --- .../Public/Set-CIPPAlwaysShowFrom.ps1 | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 diff --git a/Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 b/Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 new file mode 100644 index 000000000000..d30d9676cc7a --- /dev/null +++ b/Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 @@ -0,0 +1,88 @@ + +Function Set-CIPPAlwaysShowFrom { + <# + .SYNOPSIS + Sets the "Always Show From" property for a user or all users in a tenant. + + .DESCRIPTION + The Set-CIPPAlwaysShowFrom function is used to set the "Always Show From" property for a specified user or all users in a specified tenant. The "Always Show From" property determines whether the from field is always shown in Outlook. + + .PARAMETER UserID + Specifies the user ID for which to set the "Always Show From" property. This parameter is mandatory unless the RunOnAllUsersInTenant switch is used. + + .PARAMETER TenantFilter + Specifies the tenant for which to set the "Always Show From" property. + + .PARAMETER APIName + Specifies the name of the API. The default value is "Always Show From". + + .PARAMETER ExecutingUser + Specifies the user who is executing the function. + + .PARAMETER AlwaysShowFrom + Specifies whether to set the "Always Show From" property to true or false. + + .PARAMETER RunOnAllUsersInTenant + If this switch is present, the function will set the "Always Show From" property for all users in the specified tenant. + + .EXAMPLE + Set-CIPPAlwaysShowFrom -UserID "john.doe@example.com" -TenantFilter "example.com" -AlwaysShowFrom $true + Sets the "Always Show From" property to true for the user "john.doe@example.com" in the "example.com" tenant. + + .EXAMPLE + Set-CIPPAlwaysShowFrom -TenantFilter "example.com" -AlwaysShowFrom $true -RunOnAllUsersInTenant + Sets the "Always Show From" property to true for all users in the "example.com" tenant. + #> + [CmdletBinding(DefaultParameterSetName = 'User')] + param ( + [Parameter(Mandatory = $true, ParameterSetName = 'User')] + [Parameter(Mandatory = $false, ParameterSetName = 'AllUsers')] + [Alias('Username')][string]$UserID, + + [Parameter(Mandatory = $true, ParameterSetName = 'User')] + [Parameter(Mandatory = $true, ParameterSetName = 'AllUsers')] + $TenantFilter, + + [Parameter(ParameterSetName = 'User')] + [Parameter(ParameterSetName = 'AllUsers')] + $APIName = 'Always Show From', + + [Parameter(ParameterSetName = 'User')] + [Parameter(ParameterSetName = 'AllUsers')] + $ExecutingUser, + + [Parameter(Mandatory = $true, ParameterSetName = 'User')] + [Parameter(Mandatory = $true, ParameterSetName = 'AllUsers')] + [bool]$AlwaysShowFrom, + + [Parameter(ParameterSetName = 'AllUsers')] + [switch]$RunOnAllUsersInTenant + ) + + + if ($RunOnAllUsersInTenant.IsPresent -eq $true) { + $AllUsers = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-Mailbox' -cmdParams @{ ResultSize = 'Unlimited' } + Write-LogMessage -user $ExecutingUser -API $APIName -message "Setting Always Show From to $AlwaysShowFrom for all $($AllUsers.Count) users in $TenantFilter" -Sev 'Info' -tenant $TenantFilter + $ErrorCount = 0 + foreach ($User in $AllUsers) { + try { + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxMessageConfiguration' -anchor $User.UserPrincipalName -cmdParams @{AlwaysShowFrom = $AlwaysShowFrom; Identity = $User.UserPrincipalName } + Write-Host "Set Always Show From to $AlwaysShowFrom for $($User.UserPrincipalName)" + } catch { + $ErrorCount++ + } + } + Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Always Show From to $AlwaysShowFrom for $($AllUsers.Count - $ErrorCount) users in $TenantFilter" -Sev 'Info' -tenant $TenantFilter + return "Set Always Show From to $AlwaysShowFrom for $($AllUsers.Count - $ErrorCount) users in $TenantFilter" + } else { + try { + $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxMessageConfiguration' -anchor $UserID -cmdParams @{AlwaysShowFrom = $AlwaysShowFrom; Identity = $UserID } + Write-LogMessage -user $ExecutingUser -API $APIName -message "Set Always Show From to $AlwaysShowFrom for $UserID" -Sev 'Info' -tenant $TenantFilter + } catch { + $ErrorMessage = Get-CippException -Exception $_ + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not set Always Show From to $AlwaysShowFrom for $UserID. Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not set Always Show From to $AlwaysShowFrom for $UserID. Error: $($ErrorMessage.NormalizedError)" + } + return "Set Always Show From to $AlwaysShowFrom for $UserID" + } +} From 0fcf297123f6543601d266235a4d38a35e5b2ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 19 Aug 2024 18:22:47 +0200 Subject: [PATCH 49/52] chore: Update Set-CIPPAlwaysShowFrom function documentation --- Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 b/Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 index d30d9676cc7a..56a8b2575678 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAlwaysShowFrom.ps1 @@ -8,10 +8,11 @@ Function Set-CIPPAlwaysShowFrom { The Set-CIPPAlwaysShowFrom function is used to set the "Always Show From" property for a specified user or all users in a specified tenant. The "Always Show From" property determines whether the from field is always shown in Outlook. .PARAMETER UserID - Specifies the user ID for which to set the "Always Show From" property. This parameter is mandatory unless the RunOnAllUsersInTenant switch is used. + Specifies the user ID for which to set the "Always Show From" property. This can be UserPrincipalName, SamAccountName, GUID or Email address. + This parameter is mandatory unless the RunOnAllUsersInTenant switch is used. .PARAMETER TenantFilter - Specifies the tenant for which to set the "Always Show From" property. + Specifies the tenant for which to set the "Always Show From" property. This parameter is mandatory. .PARAMETER APIName Specifies the name of the API. The default value is "Always Show From". @@ -20,7 +21,7 @@ Function Set-CIPPAlwaysShowFrom { Specifies the user who is executing the function. .PARAMETER AlwaysShowFrom - Specifies whether to set the "Always Show From" property to true or false. + Specifies whether to set the "Always Show From" property to true or false. This parameter is mandatory. .PARAMETER RunOnAllUsersInTenant If this switch is present, the function will set the "Always Show From" property for all users in the specified tenant. @@ -67,7 +68,7 @@ Function Set-CIPPAlwaysShowFrom { foreach ($User in $AllUsers) { try { $null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-MailboxMessageConfiguration' -anchor $User.UserPrincipalName -cmdParams @{AlwaysShowFrom = $AlwaysShowFrom; Identity = $User.UserPrincipalName } - Write-Host "Set Always Show From to $AlwaysShowFrom for $($User.UserPrincipalName)" + # Write-Information "Set Always Show From to $AlwaysShowFrom for $($User.UserPrincipalName)" } catch { $ErrorCount++ } From 9d8efca4c769807f6597741365a09f51e4368b2b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 19 Aug 2024 18:57:43 +0200 Subject: [PATCH 50/52] version up --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index bee943381742..798e38995c4d 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -6.2.3 +6.3.0 From a9e5f14478992c8f438aa653b850dbd36047a003 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 19 Aug 2024 13:16:29 -0400 Subject: [PATCH 51/52] Fix reset mfa --- .../Users/Invoke-ExecResetMFA.ps1 | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 index 1667958aa7a1..881f35afbf93 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecResetMFA.ps1 @@ -13,31 +13,44 @@ Function Invoke-ExecResetMFA { $APIName = $TriggerMetadata.FunctionName Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' - - # Write to the Azure Functions log stream. - Write-Host 'PowerShell HTTP trigger function processed a request.' - Write-Host "$($Request.query.ID)" # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter + $UserID = $Request.Query.ID try { - $AADGraphtoken = (Get-GraphToken -scope 'https://graph.windows.net/.default') - $tenantid = (Get-Tenants | Where-Object -Property defaultDomainName -EQ $TenantFilter).customerId - $TrackingGuid = (New-Guid).GUID - $LogonPost = @" -http://provisioning.microsoftonline.com/IProvisioningWebService/MsolConnecturn:uuid:$TrackingGuidhttp://www.w3.org/2005/08/addressing/anonymous$($AADGraphtoken['Authorization'])50afce61-c917-435b-8c6d-60aa5a8b8aa71.2.183.57Version47$($TrackingGuid)https://provisioningapi.microsoftonline.com/provisioningwebservice.svcVersion4 -"@ - $DataBlob = (Invoke-RestMethod -Method POST -Uri 'https://provisioningapi.microsoftonline.com/provisioningwebservice.svc' -ContentType 'application/soap+xml; charset=utf-8' -Body $LogonPost).envelope.header.BecContext.DataBlob.'#text' - $MSOLXML = @" -http://provisioning.microsoftonline.com/IProvisioningWebService/SetUserurn:uuid:$TrackingGuidhttp://www.w3.org/2005/08/addressing/anonymous$($AADGraphtoken['Authorization'])$($DataBlob)9450afce61-c917-435b-8c6d-60aa5a8b8aa71.2.183.57Version47$TrackingGuidhttps://provisioningapi.microsoftonline.com/provisioningwebservice.svcVersion16$($tenantid)$($Request.query.id)*0001-01-01T00:00:00Enabled -"@ - $SetMFA = (Invoke-RestMethod -Uri 'https://provisioningapi.microsoftonline.com/provisioningwebservice.svc' -Method post -Body $MSOLXML -ContentType 'application/soap+xml; charset=utf-8') - - $Results = [pscustomobject]@{'Results' = 'Successfully completed request. User must supply MFA at next logon' } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Reset Multi factor authentication settings for $($Request.query.id)" -Sev 'Info' + Write-Host "Getting auth methods for $UserID" + $AuthMethods = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$UserID/authentication/methods" -tenantid $TenantFilter -AsApp $true + $Requests = [System.Collections.Generic.List[object]]::new() + foreach ($Method in $AuthMethods) { + if ($Method.'@odata.type' -and $Method.'@odata.type' -ne '#microsoft.graph.passwordAuthenticationMethod') { + $MethodType = ($Method.'@odata.type' -split '\.')[-1] -replace 'Authentication', '' + $Requests.Add(@{ + id = "$MethodType-$($Method.id)" + method = 'DELETE' + url = ('users/{0}/authentication/{1}s/{2}' -f $UserID, $MethodType, $Method.id) + }) + } + } + if (($Requests | Measure-Object).Count -eq 0) { + $Results = [pscustomobject]@{'Results' = "No MFA methods found for user $($Request.Query.ID)" } + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Results + }) + return + } + + $Results = New-GraphBulkRequest -Requests $Requests -tenantid $TenantFilter -asapp $true -erroraction stop + + + if ($Results.status -eq 204) { + $Results = [pscustomobject]@{'Results' = "Successfully completed request. User $($Request.Query.ID) must supply MFA at next logon" } + } else { + $FailedAuthMethods = (($Results | Where-Object { $_.status -ne 204 }).id -split '-')[0] -join ', ' + $Results = [pscustomobject]@{'Results' = "Failed to reset MFA methods for $FailedAuthMethods" } + } } catch { - $Results = [pscustomobject]@{'Results' = "Failed to reset MFA methods for $($Request.query.id): $($_.Exception.Message)" } - Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to reset MFA: $($_.Exception.Message)" -Sev 'Error' - + $Results = [pscustomobject]@{'Results' = "Failed to reset MFA methods for $($Request.Query.ID): $(Get-NormalizedError -message $_.Exception.Message)" } + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to reset MFA for user $($Request.Query.ID): $($_.Exception.Message)" -Sev 'Error' -LogData (Get-CippException -Exception $_) } # Associate values to output bindings by calling 'Push-OutputBinding'. From 82661699e4cf525296f5bdd4f63b30bbd8762cd0 Mon Sep 17 00:00:00 2001 From: Chris Brannon Date: Mon, 19 Aug 2024 13:20:18 -0400 Subject: [PATCH 52/52] Fix SP BPA Report Display --- Config/SharePoint.BPATemplate.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Config/SharePoint.BPATemplate.json b/Config/SharePoint.BPATemplate.json index da945a5d1f4f..fd279d368fda 100644 --- a/Config/SharePoint.BPATemplate.json +++ b/Config/SharePoint.BPATemplate.json @@ -31,18 +31,18 @@ }, { "name": "Resharing by external users", - "value": "isResharingByExternalUsersEnabled", - "formatter": "reverseBool" + "value": "SharepointSettings.isResharingByExternalUsersEnabled", + "formatter": "bool" }, { "name": "Allow users to sync from unmanaged devices", "value": "SharepointSettings.isUnmanagedSyncAppForTenantRestricted", - "formatter": "bool" + "formatter": "reverseBool" }, { - "name": "Site creation by standards users enabled", + "name": "Site creation by standard users enabled", "value": "SharepointSettings.isSiteCreationEnabled", - "formatter": "reverseBool" + "formatter": "bool" }, { "name": "Deleted user data rention(days)",