From 275b6f10cdeea7945ea659afa0e7608ab60a50e9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Oct 2024 10:57:30 -0400 Subject: [PATCH 01/31] CPV partner tenant support --- .../CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 | 9 ++++++--- .../CIPP/Settings/Invoke-ExecCPVPermissions.ps1 | 10 ++++++++-- .../Public/GraphHelper/Get-AuthorisedRequest.ps1 | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 index 1bdbb1daac6e..86affa77ad29 100644 --- a/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1 @@ -34,7 +34,7 @@ function Add-CIPPDelegatedPermission { $RequiredResourceAccess.Add($Resource) } - if ($Tenantfilter -eq $env:TenantID) { + if ($Tenantfilter -eq $env:TenantID -or $Tenantfilter -eq 'PartnerTenant') { $RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId }) } else { # remove the partner center permission if not pushing to partner tenant @@ -42,20 +42,23 @@ function Add-CIPPDelegatedPermission { } } $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 + $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=appId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true -NoAuthCheck $true $ourSVCPrincipal = $ServicePrincipalList | Where-Object -Property appId -EQ $ApplicationId $Results = [System.Collections.Generic.List[string]]::new() $CurrentDelegatedScopes = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/oauth2PermissionGrants" -skipTokenCache $true -tenantid $Tenantfilter -NoAuthCheck $true foreach ($App in $RequiredResourceAccess) { + if (!$App) { + continue + } $svcPrincipalId = $ServicePrincipalList | Where-Object -Property appId -EQ $App.resourceAppId if (!$svcPrincipalId) { try { $Body = @{ appId = $App.resourceAppId } | ConvertTo-Json -Compress - $svcPrincipalId = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/servicePrincipals' -tenantid $Tenantfilter -body $Body -type POST + $svcPrincipalId = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/v1.0/servicePrincipals' -tenantid $Tenantfilter -body $Body -type POST -NoAuthCheck $true } catch { $Results.add("Failed to create service principal for $($App.resourceAppId): $(Get-NormalizedError -message $_.Exception.Message)") continue diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 index 22f7f02c0307..8fbf7872e3c9 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Settings/Invoke-ExecCPVPermissions.ps1 @@ -28,14 +28,20 @@ Function Invoke-ExecCPVPermissions { } $GraphRequest = try { - if ($TenantFilter -ne 'PartnerTenant') { + if ($TenantFilter -notin @('PartnerTenant', $env:TenantId)) { Set-CIPPCPVConsent @CPVConsentParams } else { $TenantFilter = $env:TenantID + $Tenant = [PSCustomObject]@{ + displayName = '*Partner Tenant' + defaultDomainName = $env:TenantID + } } Add-CIPPApplicationPermission -RequiredResourceAccess 'CIPPDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $TenantFilter Add-CIPPDelegatedPermission -RequiredResourceAccess 'CIPPDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $TenantFilter - Set-CIPPSAMAdminRoles -TenantFilter $TenantFilter + if ($TenantFilter -notin @('PartnerTenant', $env:TenantId)) { + Set-CIPPSAMAdminRoles -TenantFilter $TenantFilter + } $Success = $true } catch { "Failed to update permissions for $($Tenant.displayName): $($_.Exception.Message)" diff --git a/Modules/CIPPCore/Public/GraphHelper/Get-AuthorisedRequest.ps1 b/Modules/CIPPCore/Public/GraphHelper/Get-AuthorisedRequest.ps1 index f68050d83d12..f8147728a15c 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Get-AuthorisedRequest.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Get-AuthorisedRequest.ps1 @@ -12,7 +12,7 @@ function Get-AuthorisedRequest { if (!$TenantID) { $TenantID = $env:TenantID } - if ($Uri -like 'https://graph.microsoft.com/beta/contracts*' -or $Uri -like '*/customers/*' -or $Uri -eq 'https://graph.microsoft.com/v1.0/me/sendMail' -or $Uri -like '*/tenantRelationships/*') { + if ($Uri -like 'https://graph.microsoft.com/beta/contracts*' -or $Uri -like '*/customers/*' -or $Uri -eq 'https://graph.microsoft.com/v1.0/me/sendMail' -or $Uri -like '*/tenantRelationships/*' -or $Uri -like '*/security/partner/*') { return $true } $Tenants = Get-Tenants -IncludeErrors From 15978c771597829ea88a737edbd3280f5de4e39a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Oct 2024 11:00:57 -0400 Subject: [PATCH 02/31] Remove return on exo exception --- .../Domain Analyser/Push-DomainAnalyserDomain.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 index a787676e9de2..ea06aa8c29c3 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 @@ -303,7 +303,6 @@ function Push-DomainAnalyserDomain { } catch { $ErrorMessage = Get-CippException -Exception $_ Write-LogMessage -API 'DomainAnalyser' -tenant $DomainObject.TenantId -message "MS CNAME DKIM error: $($ErrorMessage.NormalizedError)" -LogData $ErrorMessage -sev Error - return $ErrorMessage.NormalizedError } } From fbd6afc74bc1d569336acbe83654265e3e7c00b4 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 7 Oct 2024 11:01:22 -0400 Subject: [PATCH 03/31] Fix listmailboxes param issue --- .../CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 index dc2227ee2a29..260241fa972f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxes.ps1 @@ -38,7 +38,7 @@ Function Invoke-ListMailboxes { @{Parameter = 'SoftDeletedMailbox'; Type = 'Bool' } ) - foreach ($Param in $Request.Query.Keys) { + foreach ($Param in $Request.Query.PSObject.Properties.Name) { $CmdParam = $AllowedParameters | Where-Object { $_.Parameter -eq $Param } if ($CmdParam) { switch ($CmdParam.Type) { @@ -48,7 +48,9 @@ Function Invoke-ListMailboxes { } } 'Bool' { - if ([bool]$Request.Query.$Param -eq $true) { + $ParamIsTrue = $false + [bool]::TryParse($Request.Query.$Param, [ref]$ParamIsTrue) | Out-Null + if ($ParamIsTrue -eq $true) { $ExoRequest.cmdParams.$Param = $true } } From 5a93672258420b93a82f6c980632f2a203aebc04 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 8 Oct 2024 15:43:22 -0400 Subject: [PATCH 04/31] Fix CA vacation mode exclusion Switch to AsApp so not impacted by CA changes --- Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 index d9b4658a7405..9f66cf3e74c5 100644 --- a/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPCAExclusion.ps1 @@ -9,7 +9,7 @@ function Set-CIPPCAExclusion { $executingUser ) try { - $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)" -tenantid $TenantFilter + $CheckExististing = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($PolicyId)" -tenantid $TenantFilter -AsApp $true if ($ExclusionType -eq 'add') { $NewExclusions = [pscustomobject]@{ conditions = [pscustomobject]@{ users = [pscustomobject]@{ @@ -19,7 +19,7 @@ function Set-CIPPCAExclusion { } $RawJson = ConvertTo-Json -Depth 10 -InputObject $NewExclusions if ($PSCmdlet.ShouldProcess($PolicyId, "Add exclusion for $UserID")) { - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true } } @@ -32,7 +32,7 @@ function Set-CIPPCAExclusion { } $RawJson = ConvertTo-Json -Depth 10 -InputObject $NewExclusions if ($PSCmdlet.ShouldProcess($PolicyId, "Remove exclusion for $UserID")) { - New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON + New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($CheckExististing.id)" -tenantid $tenantfilter -type PATCH -body $RawJSON -AsApp $true } } "Successfully performed $($ExclusionType) exclusion for $username from policy $($PolicyId)" @@ -41,4 +41,4 @@ function Set-CIPPCAExclusion { "Failed to $($ExclusionType) user exclusion for $username from policy $($PolicyId): $($_.Exception.Message)" Write-LogMessage -user $executingUser -API 'Set-CIPPConditionalAccessExclusion' -message "Failed to $($ExclusionType) user exclusion for $username from policy $($PolicyId): $_" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) } -} \ No newline at end of file +} From 0949be669fab63f24734d01c635700d32550aa4f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 8 Oct 2024 15:44:33 -0400 Subject: [PATCH 05/31] Update domain analyser Remove returns on exceptions for each test Add new signature domain to domain list exclusion --- .../Domain Analyser/Push-DomainAnalyserDomain.ps1 | 7 +++---- .../Domain Analyser/Push-DomainAnalyserTenant.ps1 | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 index ea06aa8c29c3..3c682fb8854d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserDomain.ps1 @@ -124,7 +124,6 @@ function Push-DomainAnalyserDomain { } catch { $Message = 'SPF Error' Write-LogMessage -API 'DomainAnalyser' -tenant $DomainObject.TenantId -message $Message -LogData (Get-CippException -Exception $_) -sev Error - return $Message } # Check SPF Record @@ -187,7 +186,7 @@ function Push-DomainAnalyserDomain { } catch { $Message = 'DMARC Error' Write-LogMessage -API 'DomainAnalyser' -tenant $DomainObject.TenantId -message $Message -LogData (Get-CippException -Exception $_) -sev Error - return $Message + #return $Message } # DNS Sec Check @@ -205,7 +204,7 @@ function Push-DomainAnalyserDomain { } catch { $Message = 'DNSSEC Error' Write-LogMessage -API 'DomainAnalyser' -tenant $DomainObject.TenantId -message $Message -LogData (Get-CippException -Exception $_) -sev Error - return $Message + #return $Message } # DKIM Check @@ -240,7 +239,7 @@ function Push-DomainAnalyserDomain { } catch { $Message = 'DKIM Exception' Write-LogMessage -API 'DomainAnalyser' -tenant $DomainObject.TenantId -message $Message -LogData (Get-CippException -Exception $_) -sev Error - return $Message + #return $Message } # Get Microsoft DKIM CNAME selector Records diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 index dd702c8e464e..203428ec580e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Domain Analyser/Push-DomainAnalyserTenant.ps1 @@ -20,7 +20,7 @@ function Push-DomainAnalyserTenant { return } else { try { - $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $Tenant.customerId | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -Notlike '*.excl.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.isVerified) } + $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/domains' -tenantid $Tenant.customerId | Where-Object { ($_.id -notlike '*.microsoftonline.com' -and $_.id -NotLike '*.exclaimer.cloud' -and $_.id -Notlike '*.excl.cloud' -and $_.id -NotLike '*.codetwo.online' -and $_.id -NotLike '*.call2teams.com' -and $_.id -notlike '*signature365.net' -and $_.isVerified) } $TenantDomains = foreach ($d in $Domains) { [PSCustomObject]@{ From cae37e3b221594ba8fa70c4f29e48faf29e69cb7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 8 Oct 2024 15:44:48 -0400 Subject: [PATCH 06/31] Correct issue with standard --- .../Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 index c58a9a3dd7cf..65396b677490 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardAntiPhishPolicy.ps1 @@ -51,10 +51,10 @@ function Invoke-CIPPStandardAntiPhishPolicy { param($Tenant, $Settings) ##$Rerun -Type Standard -Tenant $Tenant -Settings $Settings 'AntiPhishPolicy' - $PolicyName = @('Default Anti-Phishing Policy', 'Office365 AntiPhish Default (Default)') - - $CurrentState = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishPolicy' | - Where-Object -Property Name -In $PolicyName | + $PolicyList = @('Default Anti-Phishing Policy', 'Office365 AntiPhish Default (Default)') + $ExistingPolicy = New-ExoRequest -tenantid $Tenant -cmdlet 'Get-AntiPhishPolicy' | Where-Object -Property Name -In $PolicyList + $PolicyName = $ExistingPolicy.Name + $CurrentState = $ExistingPolicy | Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligence, EnableMailboxIntelligenceProtection, EnableSpoofIntelligence, EnableFirstContactSafetyTips, EnableSimilarUsersSafetyTips, EnableSimilarDomainsSafetyTips, EnableUnusualCharactersSafetyTips, EnableUnauthenticatedSender, EnableViaTag, AuthenticationFailAction, SpoofQuarantineTag, MailboxIntelligenceProtectionAction, MailboxIntelligenceQuarantineTag, TargetedUserProtectionAction, TargetedUserQuarantineTag, TargetedDomainProtectionAction, TargetedDomainQuarantineTag, EnableOrganizationDomainsProtection $StateIsCorrect = ($CurrentState.Name -eq $PolicyName) -and From 256d9eaa3575911ec7e086611c7c2904371c3bcc Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 8 Oct 2024 16:16:26 -0400 Subject: [PATCH 07/31] Optimize tenant refresh in onboarding --- .../Activity Triggers/Push-ExecOnboardTenantQueue.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 index bf9b17bb9dc9..5c18cbe54d21 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecOnboardTenantQueue.ps1 @@ -275,7 +275,7 @@ Function Push-ExecOnboardTenantQueue { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Clearing tenant cache' }) $y = 0 do { - $Tenant = Get-Tenants -TriggerRefresh -IncludeAll | Where-Object { $_.customerId -eq $Relationship.customer.tenantId } | Select-Object -First 1 + $Tenant = Get-Tenants -TriggerRefresh -TenantFilter $Relationship.customer.tenantId | Select-Object -First 1 $y++ Start-Sleep -Seconds 20 } while (!$Tenant -and $y -le 10) From 62fc28b8e757b61999bcaad1c8211b676a2c1819 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 14:10:21 -0400 Subject: [PATCH 08/31] Ninja webhook error handling --- .../NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 index 9213a7015b1a..0404a44aa618 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 @@ -24,23 +24,36 @@ function Invoke-NinjaOneDeviceWebhook { $Device = Get-CIPPAzDataTableEntity @DeviceMapTable -Filter $DeviceFilter if (($Device | Measure-Object).count -eq 1) { - $Token = Get-NinjaOneToken -configuration $Configuration + try { + $Token = Get-NinjaOneToken -configuration $Configuration - if ($DeviceM365.isCompliant -eq $True) { - $Compliant = 'Compliant' - } else { - $Compliant = 'Non-Compliant' - } - - $ComplianceBody = @{ - "$($MappedFields.DeviceCompliance)" = $Compliant - } | ConvertTo-Json + if (!$Token.access_token) { + Write-LogMessage -API 'NinjaOneSync' -tenant $tenantfilter -user 'CIPP' -message 'Failed to get NinjaOne Token for Device Compliance Update' -Sev 'Error' + return + } - $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device/$($Device.NinjaOneID)/custom-fields" -Method PATCH -Body $ComplianceBody -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' + if ($DeviceM365.isCompliant -eq $True) { + $Compliant = 'Compliant' + } else { + $Compliant = 'Non-Compliant' + } - Write-Host 'Updated NinjaOne Device Compliance' + $ComplianceBody = @{ + "$($MappedFields.DeviceCompliance)" = $Compliant + } | ConvertTo-Json + $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/device/$($Device.NinjaOneID)/custom-fields" -Method PATCH -Body $ComplianceBody -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' + Write-Host 'Updated NinjaOne Device Compliance' + } catch { + $Message = if ($_.ErrorDetails.Message) { + Get-NormalizedError -Message $_.ErrorDetails.Message + } else { + $_.Exception.message + } + Write-Error "Failed NinjaOne Device Webhook for: $($Data | ConvertTo-Json -Depth 100) Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" + Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "Failed NinjaOne Device Webhook Linenumber: $($_.InvocationInfo.ScriptLineNumber) Error: $Message" -Sev 'Error' + } } else { Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "$($DeviceM365.displayName) ($($M365DeviceID)) was not matched in Ninja for $($tenantfilter)" -Sev 'Info' } @@ -59,4 +72,4 @@ function Invoke-NinjaOneDeviceWebhook { -} \ No newline at end of file +} From 3f6e628649779cf0f51c1c4d66054ab2a1dbc76b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 14:48:07 -0400 Subject: [PATCH 09/31] Adjust logging webhooks for ninja --- .../Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 index 0404a44aa618..67e54991324e 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneDeviceWebhook.ps1 @@ -5,7 +5,6 @@ function Invoke-NinjaOneDeviceWebhook { $Configuration ) try { - Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook Recieved - Updating NinjaOne Device compliance for $($Data.resourceData.id) in $($Data.tenantId)" -Sev 'Info' -tenant $TenantFilter $MappedFields = [pscustomobject]@{} $CIPPMapping = Get-CIPPTable -TableName CippMapping $Filter = "PartitionKey eq 'NinjaOneFieldMapping'" @@ -14,6 +13,7 @@ function Invoke-NinjaOneDeviceWebhook { } if ($MappedFields.DeviceCompliance) { + Write-LogMessage -user $ExecutingUser -API $APIName -message "Webhook Recieved - Updating NinjaOne Device compliance for $($Data.resourceData.id) in $($Data.tenantId)" -Sev 'Info' -tenant $TenantFilter $tenantfilter = $Data.tenantId $M365DeviceID = $Data.resourceData.id From 16b087cc1b07891144144e2a5d7c63b3f0f42ac9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 14:48:20 -0400 Subject: [PATCH 10/31] Add function node to log messages --- Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 index 5828884a19d1..c73b97f205a5 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 @@ -37,6 +37,7 @@ function Write-LogMessage { 'SentAsAlert' = $false 'PartitionKey' = $PartitionKey 'RowKey' = ([guid]::NewGuid()).ToString() + 'FunctionNode' = $env:WEBSITE_SITE_NAME 'LogData' = [string]$LogData } From f0e19e04f212847c8442f69275de0adc6eb1352c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 14:48:30 -0400 Subject: [PATCH 11/31] Fix issue with ProcessorQueue functions --- .../Timer Functions/Start-CIPPProcessorQueue.ps1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 index ed6e09bebf62..ab29f115a55a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 @@ -12,7 +12,16 @@ function Start-CIPPProcessorQueue { foreach ($QueueItem in $QueueItems) { if ($PSCmdlet.ShouldProcess("Processing function $($QueueItem.ProcessorFunction)")) { Remove-AzDataTableEntity @QueueTable -Entity $QueueItem - $Parameters = $QueueItem.Parameters | ConvertFrom-Json -AsHashtable + + if ($QueueItem.Parameters) { + try { + $Parameters = $QueueItem.Parameters | ConvertFrom-Json -AsHashtable + } catch { + $Parameters = @{} + } + } else { + $Parameters = @{} + } if (Get-Command -Name $QueueItem.FunctionName -Module CIPPCore -ErrorAction SilentlyContinue) { & $QueueItem.FunctionName @Parameters } else { From eb174ac78ffe7d309b4350e2abc6b593f0250cdc Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 16:50:08 -0400 Subject: [PATCH 12/31] Add missing timer --- CIPPTimers.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CIPPTimers.json b/CIPPTimers.json index e8763e7cf3d9..e527285dd78f 100644 --- a/CIPPTimers.json +++ b/CIPPTimers.json @@ -23,6 +23,13 @@ "PreferredProcessor": "auditlog", "IsSystem": true }, + { + "Command": "Start-ApplicationOrchestrator", + "Description": "Orchestrator to process application uploads", + "Cron": "0 0 */12 * * *", + "Priority": 2, + "RunOnProcessor": true + }, { "Command": "Start-WebhookOrchestrator", "Description": "Orchestrator to process webhooks", From 66d1a690d54b1d1e284df2470f31f794905fa730 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 17:09:52 -0400 Subject: [PATCH 13/31] app upload tweaks --- .../Endpoint/Applications/Invoke-ExecAppUpload.ps1 | 1 - .../Start-ApplicationOrchestrator.ps1 | 2 +- .../Timer Functions/Start-CIPPProcessorQueue.ps1 | 4 ++-- Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-ExecAppUpload.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-ExecAppUpload.ps1 index 2202e7f58ccd..824722a5e6de 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-ExecAppUpload.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Endpoint/Applications/Invoke-ExecAppUpload.ps1 @@ -31,7 +31,6 @@ function Invoke-ExecAppUpload { } } - $Results = [pscustomobject]@{'Results' = 'Started application queue' } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $Results diff --git a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-ApplicationOrchestrator.ps1 b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-ApplicationOrchestrator.ps1 index c564d6a70d60..eb4d4386aeac 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-ApplicationOrchestrator.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Orchestrator Functions/Start-ApplicationOrchestrator.ps1 @@ -7,7 +7,7 @@ function Start-ApplicationOrchestrator { Param() Write-LogMessage -API 'IntuneApps' -message 'Started uploading applications to tenants' -sev Info - + Write-Information 'Started uploading applications to tenants' $InputObject = [PSCustomObject]@{ OrchestratorName = 'ApplicationOrchestrator' SkipLog = $true diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 index ab29f115a55a..c32f1ef8cf1d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 @@ -11,8 +11,7 @@ function Start-CIPPProcessorQueue { foreach ($QueueItem in $QueueItems) { if ($PSCmdlet.ShouldProcess("Processing function $($QueueItem.ProcessorFunction)")) { - Remove-AzDataTableEntity @QueueTable -Entity $QueueItem - + Write-Information "Running queued function $($QueueItem.ProcessorFunction)" if ($QueueItem.Parameters) { try { $Parameters = $QueueItem.Parameters | ConvertFrom-Json -AsHashtable @@ -27,6 +26,7 @@ function Start-CIPPProcessorQueue { } else { Write-Warning "Function $($QueueItem.FunctionName) not found" } + Remove-AzDataTableEntity @QueueTable -Entity $QueueItem } } } diff --git a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 index c73b97f205a5..27560afaa984 100644 --- a/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 +++ b/Modules/CIPPCore/Public/GraphHelper/Write-LogMessage.ps1 @@ -35,9 +35,9 @@ function Write-LogMessage { 'Username' = [string]$username 'Severity' = [string]$sev 'SentAsAlert' = $false - 'PartitionKey' = $PartitionKey - 'RowKey' = ([guid]::NewGuid()).ToString() - 'FunctionNode' = $env:WEBSITE_SITE_NAME + 'PartitionKey' = [string]$PartitionKey + 'RowKey' = [string]([guid]::NewGuid()).ToString() + 'FunctionNode' = [string]$env:WEBSITE_SITE_NAME 'LogData' = [string]$LogData } From 0b255894dbc6deaa9358bc243ec41ea57bb371e7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 17:18:22 -0400 Subject: [PATCH 14/31] Update Start-CIPPProcessorQueue.ps1 --- .../Timer Functions/Start-CIPPProcessorQueue.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 index c32f1ef8cf1d..5ba435f87a2b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 @@ -22,7 +22,11 @@ function Start-CIPPProcessorQueue { $Parameters = @{} } if (Get-Command -Name $QueueItem.FunctionName -Module CIPPCore -ErrorAction SilentlyContinue) { - & $QueueItem.FunctionName @Parameters + try { + Invoke-Command -ScriptBlock { & $QueueItem.FunctionName @Parameters } + } catch { + Write-Warning "Failed to run function $($QueueItem.FunctionName). Error: $($_.Exception.Message)" + } } else { Write-Warning "Function $($QueueItem.FunctionName) not found" } From 2aa948bd3b0049163e0295fc734623104ce5bf68 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 9 Oct 2024 17:33:28 -0400 Subject: [PATCH 15/31] Update Start-CIPPProcessorQueue.ps1 --- .../Timer Functions/Start-CIPPProcessorQueue.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 index 5ba435f87a2b..a0d340bb6c8a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Timer Functions/Start-CIPPProcessorQueue.ps1 @@ -21,14 +21,14 @@ function Start-CIPPProcessorQueue { } else { $Parameters = @{} } - if (Get-Command -Name $QueueItem.FunctionName -Module CIPPCore -ErrorAction SilentlyContinue) { + if (Get-Command -Name $QueueItem.ProcessorFunction -Module CIPPCore -ErrorAction SilentlyContinue) { try { - Invoke-Command -ScriptBlock { & $QueueItem.FunctionName @Parameters } + Invoke-Command -ScriptBlock { & $QueueItem.ProcessorFunction @Parameters } } catch { - Write-Warning "Failed to run function $($QueueItem.FunctionName). Error: $($_.Exception.Message)" + Write-Warning "Failed to run function $($QueueItem.ProcessorFunction). Error: $($_.Exception.Message)" } } else { - Write-Warning "Function $($QueueItem.FunctionName) not found" + Write-Warning "Function $($QueueItem.ProcessorFunction) not found" } Remove-AzDataTableEntity @QueueTable -Entity $QueueItem } From 67c266a149698d0b570b90e5caa302144cbf294e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Oct 2024 10:59:26 -0400 Subject: [PATCH 16/31] cleanup generic graph request --- .../Public/Entrypoints/Invoke-ListGraphRequest.ps1 | 8 +++++--- .../Public/GraphRequests/Get-GraphRequestList.ps1 | 5 ----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 index dbb63b088425..f656bd541400 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListGraphRequest.ps1 @@ -116,13 +116,15 @@ function Invoke-ListGraphRequest { $GraphRequestParams.AsApp = $true } - Write-Host ($GraphRequestParams | ConvertTo-Json) - $Metadata = $GraphRequestParams try { $Results = Get-GraphRequestList @GraphRequestParams - + if ($Results.nextLink -and $Request.Query.NoPagination) { + $Metadata['nextLink'] = $Results.nextLink | Select-Object -Last 1 + #Results is an array of objects, so we need to remove the last object before returning + $Results = $Results | Select-Object -First ($Results.Count - 1) + } if ($Request.Query.ListProperties) { $Columns = ($Results | Select-Object -First 1).PSObject.Properties.Name $Results = $Columns | Where-Object { @('Tenant', 'CippStatus') -notcontains $_ } diff --git a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 index 769796f02504..e5687be6900d 100644 --- a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 +++ b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 @@ -295,11 +295,6 @@ function Get-GraphRequestList { if ($nextLink) { $GraphRequest.uri = $nextLink } $GraphRequestResults = New-GraphGetRequest @GraphRequest -Caller 'Get-GraphRequestList' -ErrorAction Stop - if ($GraphRequestResults.nextLink) { - #$Metadata['nextLink'] = $GraphRequestResults.nextLink | Select-Object -Last 1 - #GraphRequestResults is an array of objects, so we need to remove the last object before returning - $GraphRequestResults = $GraphRequestResults | Select-Object -First ($GraphRequestResults.Count - 1) - } $GraphRequestResults = $GraphRequestResults | Select-Object *, @{n = 'Tenant'; e = { $TenantFilter } }, @{n = 'CippStatus'; e = { 'Good' } } if ($ReverseTenantLookup -and $GraphRequestResults) { From f5d532f9135f50b4b67843b817fcf3434084ff72 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 10 Oct 2024 14:41:31 -0400 Subject: [PATCH 17/31] Fix LAPS & error msgs --- Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 | 4 ++-- Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 index a72d598639a2..b7aed6e35fc0 100644 --- a/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPBitlockerKey.ps1 @@ -15,7 +15,7 @@ function Get-CIPPBitlockerKey { return $GraphRequest } catch { $ErrorMessage = Get-NormalizedError -Message $_.Exception.Message - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) - return "Could not add out of office message for $($userid). Error: $ErrorMessage" + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not retrieve bitlocker recovery key for $($device)" -Sev 'Error' -tenant $TenantFilter -LogData (Get-CippException -Exception $_) + return "Could not retrieve bitlocker recovery key for $($device). Error: $ErrorMessage" } } diff --git a/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 b/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 index 011ab9f4552a..eeac2a740ac2 100644 --- a/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPLAPSPassword.ps1 @@ -9,7 +9,7 @@ function Get-CIPPLapsPassword { ) try { - $GraphRequest = (New-GraphGetRequest -noauthcheck $true -uri "https://graph.microsoft.com/beta/deviceLocalCredentials/$($device)?`$select=credentials" -tenantid $TenantFilter).credentials | Select-Object -First 1 | ForEach-Object { + $GraphRequest = (New-GraphGetRequest -noauthcheck $true -uri "https://graph.microsoft.com/beta/directory/deviceLocalCredentials/$($device)?`$select=credentials" -tenantid $TenantFilter).credentials | Select-Object -First 1 | ForEach-Object { $PlainText = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($_.passwordBase64)) $date = $_.BackupDateTime "The password for $($_.AccountName) is $($PlainText) generated at $($date)" @@ -17,8 +17,8 @@ function Get-CIPPLapsPassword { if ($GraphRequest) { return $GraphRequest } else { return "No LAPS password found for $device" } } catch { $ErrorMessage = Get-CippException -Exception $_ - Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not add OOO for $($userid). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage - return "Could not add out of office message for $($userid). Error: $($ErrorMessage.NormalizedError)" + Write-LogMessage -user $ExecutingUser -API $APIName -message "Could not retrieve LAPS password for $($device). Error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -tenant $TenantFilter -LogData $ErrorMessage + return "Could not retrieve LAPS password for $($device). Error: $($ErrorMessage.NormalizedError)" } } From 38a305098b49c384d506508f49d233e313c92699 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 09:45:21 -0400 Subject: [PATCH 18/31] handle json conversion issues in BPA --- .../Tenant/Standards/Invoke-ListBPA.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 index d597a8d6bb87..e583908da1e1 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListBPA.ps1 @@ -40,8 +40,11 @@ Function Invoke-ListBPA { $row = $_ $JSONFields | ForEach-Object { $jsonContent = $row.$_ - if ($jsonContent -ne $null -and $jsonContent -ne 'FAILED') { - $row.$_ = $jsonContent | ConvertFrom-Json -Depth 15 + if (![string]::IsNullOrEmpty($jsonContent) -and $jsonContent -ne 'FAILED') { + try { + $row.$_ = $jsonContent | ConvertFrom-Json -Depth 15 + } catch { + } } } $row.PSObject.Properties | ForEach-Object { @@ -61,8 +64,11 @@ Function Invoke-ListBPA { $row = $_ $JSONFields | ForEach-Object { $jsonContent = $row.$_ - if ($jsonContent -ne $null -and $jsonContent -ne 'FAILED') { - $row.$_ = $jsonContent | ConvertFrom-Json -Depth 15 + if (![string]::IsNullOrEmpty($jsonContent) -and $jsonContent -ne 'FAILED') { + try { + $row.$_ = $jsonContent | ConvertFrom-Json -Depth 15 + } catch { + } } } $row | Where-Object -Property PartitionKey -In $Tenants.customerId From 44fb679b1124589ee1a16dff64fef3cfb1f43cf4 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 11:00:17 -0400 Subject: [PATCH 19/31] NinjaOne API validation & fixes Update standards.json file Add hostname check to ExecExtensionsConfig Add hostname check to NinjaOneTenantSync --- Config/standards.json | 350 ++++++++++++++++-- .../Invoke-ExecExtensionsConfig.ps1 | 14 +- .../NinjaOne/Invoke-NinjaOneTenantSync.ps1 | 149 ++++---- 3 files changed, 412 insertions(+), 101 deletions(-) diff --git a/Config/standards.json b/Config/standards.json index da8c98b3a014..4b12a7181b10 100644 --- a/Config/standards.json +++ b/Config/standards.json @@ -93,7 +93,7 @@ "value": "default" }, { - "label": "Parial-screen background", + "label": "Partial-screen background", "value": "verticalSplit" } ] @@ -349,7 +349,7 @@ "name": "standards.TAP", "cat": "Entra (AAD) Standards", "tag": ["lowimpact"], - "helpText": "Enables TAP and sets the default TAP lifetime to 1 hour. This configuration also allows you to select is a TAP is single use or multi-logon.", + "helpText": "Enables TAP and sets the default TAP lifetime to 1 hour. This configuration also allows you to select if a TAP is single use or multi-logon.", "docsDescription": "Enables Temporary Password generation for the tenant.", "addedComponent": [ { @@ -584,7 +584,7 @@ { "name": "standards.OauthConsentLowSec", "cat": "Entra (AAD) Standards", - "tag": ["mediumimpact"], + "tag": ["mediumimpact", "IntegratedApps"], "helpText": "Sets the default oauth consent level so users can consent to applications that have low risks.", "docsDescription": "Allows users to consent to applications with low assigned risk.", "label": "Allow users to consent to applications with low security risk (Prevent OAuth phishing. Lower impact, less secure)", @@ -648,7 +648,7 @@ "name": "standards.DisableEmail", "cat": "Entra (AAD) Standards", "tag": ["highimpact"], - "helpText": "This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead promts them to create a Microsoft account.", + "helpText": "This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead prompts them to create a Microsoft account.", "addedComponent": [], "label": "Disables Email as an MFA method", "impact": "High Impact", @@ -1278,6 +1278,19 @@ "powershellEquivalent": "Get-Mailbox & Update-MgUser", "recommendedBy": ["CIS"] }, + { + "name": "standards.EXODisableAutoForwarding", + "cat": "Exchange Standards", + "tag": ["highimpact", "CIS", "mdo_autoforwardingmode", "mdo_blockmailforward"], + "helpText": "Disables the ability for users to automatically forward e-mails to external recipients.", + "docsDescription": "Disables the ability for users to automatically forward e-mails to external recipients. This is to prevent data exfiltration. Please check if there are any legitimate use cases for this feature before implementing, like forwarding invoices and such.", + "addedComponent": [], + "label": "Disable automatic forwarding to external recipients", + "impact": "High Impact", + "impactColour": "danger", + "powershellEquivalent": "Set-HostedOutboundSpamFilterPolicy -AutoForwardingMode 'Off'", + "recommendedBy": ["CIS"] + }, { "name": "standards.QuarantineRequestAlert", "cat": "Defender Standards", @@ -1300,12 +1313,7 @@ { "name": "standards.SafeLinksPolicy", "cat": "Defender Standards", - "tag": [ - "lowimpact", - "CIS", - "mdo_safelinksforemail", - "mdo_safelinksforOfficeApps" - ], + "tag": ["lowimpact", "CIS", "mdo_safelinksforemail", "mdo_safelinksforOfficeApps"], "helpText": "This creates a safelink policy that automatically scans, tracks, and and enables safe links for Email, Office, and Teams for both external and internal senders", "addedComponent": [ { @@ -1341,7 +1349,8 @@ "mdo_highconfidencephishaction", "mdo_phisspamacation", "mdo_spam_notifications_only_for_admins", - "mdo_antiphishingpolicies" + "mdo_antiphishingpolicies", + "mdo_phishthresholdlevel" ], "helpText": "This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips.", "addedComponent": [ @@ -1619,13 +1628,7 @@ { "name": "standards.MalwareFilterPolicy", "cat": "Defender Standards", - "tag": [ - "lowimpact", - "CIS", - "mdo_zapspam", - "mdo_zapphish", - "mdo_zapmalware" - ], + "tag": ["lowimpact", "CIS", "mdo_zapspam", "mdo_zapphish", "mdo_zapmalware"], "helpText": "This creates a Malware filter policy that enables the default File filter and Zero-hour auto purge for malware.", "addedComponent": [ { @@ -1643,6 +1646,11 @@ } ] }, + { + "type": "input", + "name": "standards.MalwareFilterPolicy.OptionalFileTypes", + "label": "Optional File Types, Comma separated" + }, { "type": "Select", "label": "QuarantineTag", @@ -1695,18 +1703,24 @@ "tag": ["mediumimpact"], "helpText": "This standard creates a Spam filter policy similar to the default strict policy.", "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" + }, + { + "label": "Move message to Junk Email folder", + "value": "MoveToJmf" } ] }, @@ -1729,6 +1743,21 @@ } ] }, + { + "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", @@ -1748,6 +1777,21 @@ } ] }, + { + "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", @@ -1767,6 +1811,21 @@ } ] }, + { + "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", @@ -1926,14 +1985,22 @@ "name": "standards.DeletedUserRentention", "cat": "SharePoint Standards", "tag": ["lowimpact"], - "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.", "addedComponent": [ { "type": "Select", "name": "standards.DeletedUserRentention.Days", - "label": "Retention in years (Default 1)", + "label": "Retention time (Default 30 days)", "values": [ + { + "label": "30 days", + "value": "30" + }, + { + "label": "90 days", + "value": "90" + }, { "label": "1 year", "value": "365" @@ -2089,23 +2156,62 @@ "name": "standards.DisableAddShortcutsToOneDrive", "cat": "SharePoint Standards", "tag": ["mediumimpact"], - "helpText": "When the feature is disabled the option Add shortcut to OneDrive will be removed. Any folders that have already been added will remain on the user's computer.", - "disabledFeatures": { - "report": true, - "warn": true, - "remediate": false - }, - "addedComponent": [], - "label": "Disable Add Shortcuts To OneDrive", + "helpText": "If disabled, the button Add shortcut to OneDrive will be removed and users in the tenant will no longer be able to add new shortcuts to their OneDrive. Existing shortcuts will remain functional", + "addedComponent": [ + { + "type": "Select", + "label": "Add Shortcuts To OneDrive button state", + "name": "standards.DisableAddShortcutsToOneDrive.state", + "values": [ + { + "label": "Disabled", + "value": "true" + }, + { + "label": "Enabled", + "value": "false" + } + ] + } + ], + "label": "Set Add Shortcuts To OneDrive button state", "impact": "Medium Impact", "impactColour": "warning", - "powershellEquivalent": "Graph API or Portal", + "powershellEquivalent": "Set-SPOTenant -DisableAddShortcutsToOneDrive $true or $false", + "recommendedBy": [] + }, + { + "name": "standards.SPSyncButtonState", + "cat": "SharePoint Standards", + "tag": ["mediumimpact"], + "helpText": "If disabled, users in the tenant will no longer be able to use the Sync button to sync SharePoint content on all sites. However, existing synced content will remain functional on the user's computer.", + "addedComponent": [ + { + "type": "Select", + "label": "SharePoint Sync Button state", + "name": "standards.SPSyncButtonState.state", + "values": [ + { + "label": "Disabled", + "value": "true" + }, + { + "label": "Enabled", + "value": "false" + } + ] + } + ], + "label": "Set SharePoint sync button state", + "impact": "Medium Impact", + "impactColour": "warning", + "powershellEquivalent": "Set-SPOTenant -HideSyncButtonOnTeamSite $true or $false", "recommendedBy": [] }, { "name": "standards.DisableSharePointLegacyAuth", "cat": "SharePoint Standards", - "tag": ["mediumimpact", "CIS"], + "tag": ["mediumimpact", "CIS", "spo_legacy_auth"], "helpText": "Disables the ability to authenticate with SharePoint using legacy authentication methods. Any applications that use legacy authentication will need to be updated to use modern authentication.", "docsDescription": "Disables the ability for users and applications to access SharePoint via legacy basic authentication. This will likely not have any user impact, but will block systems/applications depending on basic auth or the SharePointOnlineCredentials class.", "addedComponent": [], @@ -2255,5 +2361,179 @@ "impactColour": "danger", "powershellEquivalent": "Update-MgAdminSharepointSetting", "recommendedBy": [] + }, + { + "name": "standards.TeamsGlobalMeetingPolicy", + "cat": "Teams Standards", + "tag": ["lowimpact"], + "helpText": "Defines the CIS recommended global meeting policy for Teams. This includes AllowAnonymousUsersToJoinMeeting, AllowAnonymousUsersToStartMeeting, AutoAdmittedUsers, AllowPSTNUsersToBypassLobby, MeetingChatEnabledType, DesignatedPresenterRoleMode, AllowExternalParticipantGiveRequestControl", + "addedComponent": [ + { + "type": "Select", + "name": "standards.TeamsGlobalMeetingPolicy.DesignatedPresenterRoleMode", + "label": "Default value of the `Who can present?`", + "values": [ + { + "label": "EveryoneUserOverride", + "value": "EveryoneUserOverride" + }, + { + "label": "EveryoneInCompanyUserOverride", + "value": "EveryoneInCompanyUserOverride" + }, + { + "label": "EveryoneInSameAndFederatedCompanyUserOverride", + "value": "EveryoneInSameAndFederatedCompanyUserOverride" + }, + { + "label": "OrganizerOnlyUserOverride", + "value": "OrganizerOnlyUserOverride" + } + ] + } + ], + "label": "Define Global Meeting Policy for Teams", + "impact": "Low Impact", + "impactColour": "info", + "powershellEquivalent": "Set-CsTeamsMeetingPolicy -AllowAnonymousUsersToJoinMeeting $false -AllowAnonymousUsersToStartMeeting $false -AutoAdmittedUsers EveryoneInCompanyExcludingGuests -AllowPSTNUsersToBypassLobby $false -MeetingChatEnabledType EnabledExceptAnonymous -DesignatedPresenterRoleMode $DesignatedPresenterRoleMode -AllowExternalParticipantGiveRequestControl $false", + "recommendedBy": ["CIS 3.0"] + }, + { + "name": "standards.TeamsEmailIntegration", + "cat": "Teams Standards", + "tag": ["lowimpact"], + "helpText": "Should users be allowed to send emails directly to a channel email addresses?", + "docsDescription": "Teams channel email addresses are an optional feature that allows users to email the Teams channel directly.", + "addedComponent": [ + { + "type": "boolean", + "name": "standards.TeamsEmailIntegration.AllowEmailIntoChannel", + "label": "Allow channel emails" + } + ], + "label": "Disallow emails to be sent to channel email addresses", + "impact": "Low Impact", + "impactColour": "info", + "powershellEquivalent": "Set-CsTeamsClientConfiguration -AllowEmailIntoChannel $false", + "recommendedBy": ["CIS 3.0"] + }, + { + "name": "standards.TeamsExternalFileSharing", + "cat": "Teams Standards", + "tag": ["lowimpact"], + "helpText": "Ensure external file sharing in Teams is enabled for only approved cloud storage services.", + "addedComponent": [ + { + "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" + } + ], + "label": "Define approved cloud storage services for external file sharing in Teams", + "impact": "Low Impact", + "impactColour": "info", + "powershellEquivalent": "Set-CsTeamsClientConfiguration -AllowGoogleDrive $false -AllowShareFile $false -AllowBox $false -AllowDropBox $false -AllowEgnyte $false", + "recommendedBy": ["CIS 3.0"] + }, + { + "name": "standards.TeamsExternalAccessPolicy", + "cat": "Teams Standards", + "tag": ["mediumimpact"], + "helpText": "Sets the properties of the Global external access policy.", + "docsDescription": "Sets the properties of the Global external access policy. External access policies determine whether or not your users can: 1) communicate with users who have Session Initiation Protocol (SIP) accounts with a federated organization; 2) communicate with users who are using custom applications built with Azure Communication Services; 3) access Skype for Business Server over the Internet, without having to log on to your internal network; 4) communicate with users who have SIP accounts with a public instant messaging (IM) provider such as Skype; and, 5) communicate with people who are using Teams with an account that's not managed by an organization.", + "addedComponent": [ + { + "type": "boolean", + "name": "standards.TeamsExternalAccessPolicy.EnableFederationAccess", + "label": "Allow communication from trusted organizations" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalAccessPolicy.EnablePublicCloudAccess", + "label": "Allow user to communicate with Skype users" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalAccessPolicy.EnableTeamsConsumerAccess", + "label": "Allow communication with unmanaged Teams accounts" + } + ], + "label": "External Access Settings for Microsoft Teams", + "impact": "Medium Impact", + "impactColour": "warning", + "powershellEquivalent": "Set-CsExternalAccessPolicy", + "recommendedBy": [] + }, + { + "name": "standards.TeamsFederationConfiguration", + "cat": "Teams Standards", + "tag": ["mediumimpact"], + "helpText": "Sets the properties of the Global federation configuration.", + "docsDescription": "Sets the properties of the Global federation configuration. Federation configuration settings determine whether or not your users can communicate with users who have SIP accounts with a federated organization.", + "addedComponent": [ + { + "type": "boolean", + "name": "standards.TeamsFederationConfiguration.AllowTeamsConsumer", + "label": "Allow users to communicate with other organizations" + }, + { + "type": "boolean", + "name": "standards.TeamsFederationConfiguration.AllowPublicUsers", + "label": "Allow users to communicate with Skype Users" + }, + { + "type": "Select", + "name": "standards.TeamsFederationConfiguration.DomainControl", + "label": "Communication Mode", + "values": [ + { + "label": "Allow all external domains", + "value": "AllowAllExternal" + }, + { + "label": "Block all external domains", + "value": "BlockAllExternal" + }, + { + "label": "Allow specific external domains", + "value": "AllowSpecificExternal" + }, + { + "label": "Block specific external domains", + "value": "BlockSpecificExternal" + } + ] + }, + { + "type": "input", + "name": "standards.TeamsFederationConfiguration.DomainList", + "label": "Domains, Comma separated" + } + ], + "label": "Federation Configuration for Microsoft Teams", + "impact": "Medium Impact", + "impactColour": "warning", + "powershellEquivalent": "Set-CsTenantFederationConfiguration", + "recommendedBy": [] } ] diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 index ea6f4f16205d..9291fc5ea880 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/CIPP/Extensions/Invoke-ExecExtensionsConfig.ps1 @@ -31,8 +31,20 @@ Function Invoke-ExecExtensionsConfig { # Check if NinjaOne URL is set correctly and the instance has at least version 5.6 if ($Body.NinjaOne) { + $AllowedNinjaHostnames = @( + 'app.ninjarmmm.com', + 'eu.ninjarmmm.com', + 'oc.ninjarmmm.com', + 'ca.ninjarmmm.com', + 'us2.ninjarmm.com' + ) + $SetNinjaHostname = $Body.NinjaOne.Instance -replace '/ws', '' -replace 'https://', '' + if ($AllowedNinjaHostnames -notcontains $SetNinjaHostname) { + throw "NinjaOne URL is not allowed. Allowed hostnames are: $($AllowedNinjaHostnames -join ', ')" + } + try { - [version]$Version = (Invoke-WebRequest -Method GET -Uri "https://$(($Body.NinjaOne.Instance -replace '/ws','') -replace 'https://','')/app-version.txt" -ea stop).content + [version]$Version = (Invoke-WebRequest -Method GET -Uri "$SetNinjaHostname/app-version.txt" -ea stop).content } catch { throw "Failed to connect to NinjaOne check your Instance is set correctly eg 'app.ninjarmmm.com'" } diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index 03d1bed97445..7ba7ee9f6b40 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 @@ -5,7 +5,7 @@ function Invoke-NinjaOneTenantSync { ) try { $StartQueueTime = Get-Date - Write-Host "$(Get-Date) - Starting NinjaOne Sync" + Write-Information "$(Get-Date) - Starting NinjaOne Sync" # Stagger start # Check Global Rate Limiting @@ -22,7 +22,7 @@ function Invoke-NinjaOneTenantSync { $StartDate = try { Get-Date($CurrentItem.lastStartTime) } catch { $Null } $EndDate = try { Get-Date($CurrentItem.lastEndTime) } catch { $Null } - if (($null -ne $CurrentItem.lastStartTime) -and ($StartDate -gt (Get-Date).AddMinutes(-10)) -and ( $Null -eq $CurrentItem.lastEndTime -or ($StartDate -gt $EndDate))) { + if (($null -ne $CurrentItem.lastStartTime) -and ($StartDate -gt (Get-Date).ToUniversalTime().AddMinutes(-10)) -and ( $Null -eq $CurrentItem.lastEndTime -or ($StartDate -gt $EndDate))) { Throw "NinjaOne Sync for Tenant $($MappedTenant.RowKey) is still running, please wait 10 minutes and try again." } @@ -43,7 +43,7 @@ function Invoke-NinjaOneTenantSync { $Customer = Get-Tenants -IncludeErrors | Where-Object { $_.customerId -eq $MappedTenant.RowKey } - Write-Host "Processing: $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)" + Write-Information "Processing: $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)" Write-LogMessage -API 'NinjaOneSync' -user 'NinjaOneSync' -message "Processing NinjaOne Synchronization for $($Customer.displayName) - Queued for $((New-TimeSpan -Start $StartQueueTime -End $StartTime).TotalSeconds)" -Sev 'Info' @@ -59,6 +59,18 @@ function Invoke-NinjaOneTenantSync { $Table = Get-CIPPTable -TableName Extensionsconfig $Configuration = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json).NinjaOne + $AllowedNinjaHostnames = @( + 'app.ninjarmmm.com', + 'eu.ninjarmmm.com', + 'oc.ninjarmmm.com', + 'ca.ninjarmmm.com', + 'us2.ninjarmm.com' + ) + + if ($AllowedNinjaHostnames -notcontains $Configuration.Instance) { + throw "NinjaOne URL is invalid. Allowed hostnames are: $($AllowedNinjaHostnames -join ', ')" + } + # Pull the list of field Mappings so we know which fields to render. $MappedFields = [pscustomobject]@{} $CIPPMapping = Get-CIPPTable -TableName CippMapping @@ -79,7 +91,7 @@ function Invoke-NinjaOneTenantSync { } while ($ResultCount.count -eq $PageSize) - Write-Host 'Fetched NinjaOne Devices' + Write-Information 'Fetched NinjaOne Devices' [System.Collections.Generic.List[PSCustomObject]]$NinjaOneUserDocs = @() @@ -183,7 +195,7 @@ function Invoke-NinjaOneTenantSync { $NinjaDoc | Add-Member -NotePropertyName 'ParsedFields' -NotePropertyValue $ParsedFields -Force } - Write-Host 'Fetched NinjaOne User Docs' + Write-Information 'Fetched NinjaOne User Docs' } [System.Collections.Generic.List[PSCustomObject]]$NinjaOneLicenseDocs = @() @@ -253,7 +265,7 @@ function Invoke-NinjaOneTenantSync { $NinjaLic | Add-Member -NotePropertyName 'ParsedFields' -NotePropertyValue $ParsedFields -Force } - Write-Host 'Fetched NinjaOne License Docs' + Write-Information 'Fetched NinjaOne License Docs' } @@ -339,7 +351,7 @@ function Invoke-NinjaOneTenantSync { Throw "Failed to fetch bulk company data: $_" } - Write-Host 'Fetched Bulk M365 Data' + Write-Information 'Fetched Bulk M365 Data' $Users = Get-GraphBulkResultByID -value -Results $TenantResults -ID 'Users' @@ -399,7 +411,7 @@ function Invoke-NinjaOneTenantSync { $MemberReturn = $null } - Write-Host 'Fetched M365 Roles' + Write-Information 'Fetched M365 Roles' $Roles = foreach ($Result in $MemberReturn) { [PSCustomObject]@{ @@ -457,7 +469,7 @@ function Invoke-NinjaOneTenantSync { $PolicyReturn = $null } - Write-Host 'Fetched M365 Device Compliance' + Write-Information 'Fetched M365 Device Compliance' $DeviceComplianceDetails = foreach ($Result in $PolicyReturn) { [pscustomobject]@{ @@ -487,7 +499,7 @@ function Invoke-NinjaOneTenantSync { $GroupMembersReturn = $null } - Write-Host 'Fetched M365 Group Membership' + Write-Information 'Fetched M365 Group Membership' $Groups = foreach ($Result in $GroupMembersReturn) { [pscustomobject]@{ @@ -596,7 +608,7 @@ function Invoke-NinjaOneTenantSync { $MailboxStatsFull = $null } - Write-Host 'Fetched M365 Additional Data' + Write-Information 'Fetched M365 Additional Data' $FetchEnd = Get-Date @@ -877,7 +889,7 @@ function Invoke-NinjaOneTenantSync { New-CIPPGraphSubscription -TenantFilter $TenantFilter -TypeofSubscription 'updated' -BaseURL $CIPPUrl -Resource 'devices' -EventType 'DeviceUpdate' -ExecutingUser 'NinjaOneSync' } - Write-Host 'Processed Devices' + Write-Information 'Processed Devices' ########## Create / Update User Objects @@ -1353,25 +1365,25 @@ function Invoke-NinjaOneTenantSync { try { # Create New Users if (($NinjaUserCreation | Measure-Object).count -ge 100) { - Write-Host 'Creating NinjaOne Users' + Write-Information 'Creating NinjaOne Users' [System.Collections.Generic.List[PSCustomObject]]$CreatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserCreation.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserCreation [System.Collections.Generic.List[PSCustomObject]]$NinjaUserCreation = @() } } Catch { - Write-Host "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_" + Write-Information "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_" } try { # Update Users if (($NinjaUserUpdates | Measure-Object).count -ge 100) { - Write-Host 'Updating NinjaOne Users' + Write-Information 'Updating NinjaOne Users' [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserUpdates [System.Collections.Generic.List[PSCustomObject]]$NinjaUserUpdates = @() } } Catch { - Write-Host "Bulk Update Errored, but may have been successful as only 1 record with an issue could have been the cause: $_" + Write-Information "Bulk Update Errored, but may have been successful as only 1 record with an issue could have been the cause: $_" } @@ -1428,24 +1440,24 @@ function Invoke-NinjaOneTenantSync { try { # Create New Users if (($NinjaUserCreation | Measure-Object).count -ge 1) { - Write-Host 'Creating NinjaOne Users' + Write-Information 'Creating NinjaOne Users' [System.Collections.Generic.List[PSCustomObject]]$CreatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserCreation.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserCreation } } Catch { - Write-Host "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_" + Write-Information "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_" } try { # Update Users if (($NinjaUserUpdates | Measure-Object).count -ge 1) { - Write-Host 'Updating NinjaOne Users' + Write-Information 'Updating NinjaOne Users' [System.Collections.Generic.List[PSCustomObject]]$UpdatedUsers = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ("[$($NinjaUserUpdates.body -join ',')]") -EA Stop).content | ConvertFrom-Json -Depth 100 Remove-AzDataTableEntity @UsersUpdateTable -Entity $NinjaUserUpdates } } Catch { - Write-Host "Bulk Update Errored, but may have been successful as only 1 record with an issue could have been the cause: $_" + Write-Information "Bulk Update Errored, but may have been successful as only 1 record with an issue could have been the cause: $_" } ### Relationship Mapping @@ -1512,12 +1524,12 @@ function Invoke-NinjaOneTenantSync { try { # Update Relations if (($Relations | Measure-Object).count -ge 1) { - Write-Host 'Updating Relations' + Write-Information 'Updating Relations' $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/entity/NODE/$($LinkDevice.NinjaDevice.id)/relations" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' -Body ($Relations | ConvertTo-Json -Depth 100 -AsArray) -EA Stop - Write-Host 'Completed Update' + Write-Information 'Completed Update' } } Catch { - Write-Host "Creating Relations Failed: $_" + Write-Information "Creating Relations Failed: $_" } } } @@ -1624,22 +1636,22 @@ function Invoke-NinjaOneTenantSync { try { # Create New Subscriptions if (($NinjaLicenseCreation | Measure-Object).count -ge 1) { - Write-Host 'Creating NinjaOne Licenses' + Write-Information 'Creating NinjaOne Licenses' [System.Collections.Generic.List[PSCustomObject]]$CreatedLicenses = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaLicenseCreation | ConvertTo-Json -Depth 100 -AsArray) -EA Stop).content | ConvertFrom-Json -Depth 100 } } Catch { - Write-Host "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_" + Write-Information "Bulk Creation Error, but may have been successful as only 1 record with an issue could have been the cause: $_" } try { # Update Subscriptions if (($NinjaLicenseUpdates | Measure-Object).count -ge 1) { - Write-Host 'Updating NinjaOne Licenses' + Write-Information 'Updating NinjaOne Licenses' [System.Collections.Generic.List[PSCustomObject]]$UpdatedLicenses = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/documents" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaLicenseUpdates | ConvertTo-Json -Depth 100 -AsArray) -EA Stop).content | ConvertFrom-Json -Depth 100 - Write-Host 'Completed Update' + Write-Information 'Completed Update' } } Catch { - Write-Host "Bulk Update Errored, but may have been successful as only 1 record with an issue could have been the cause: $_" + Write-Information "Bulk Update Errored, but may have been successful as only 1 record with an issue could have been the cause: $_" } [System.Collections.Generic.List[PSCustomObject]]$LicenseDocs = $CreatedLicenses + $UpdatedLicenses @@ -1668,12 +1680,12 @@ function Invoke-NinjaOneTenantSync { try { # Update Relations if (($Relations | Measure-Object).count -ge 1) { - Write-Host 'Updating Relations' + Write-Information 'Updating Relations' $Null = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/entity/DOCUMENT/$($($MatchedLicDoc.documentId))/relations" -Method POST -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json' -Body ($Relations | ConvertTo-Json -Depth 100 -AsArray) -EA Stop - Write-Host 'Completed Update' + Write-Information 'Completed Update' } } Catch { - Write-Host "Creating Relations Failed: $_" + Write-Information "Creating Relations Failed: $_" } #Remove relations @@ -1681,7 +1693,7 @@ function Invoke-NinjaOneTenantSync { try { $RelatedItems = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/related-items/$($DelUser.id)" -Method Delete -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 } catch { - Write-Host "Failed to remove relation $($DelUser.id) from $($LinkLic.name)" + Write-Information "Failed to remove relation $($DelUser.id) from $($LinkLic.name)" } } } @@ -1696,7 +1708,7 @@ function Invoke-NinjaOneTenantSync { ### M365 Links Section if ($MappedFields.TenantLinks) { - Write-Host 'Tenant Links' + Write-Information 'Tenant Links' $ManagementLinksData = @( @{ @@ -1798,7 +1810,7 @@ function Invoke-NinjaOneTenantSync { if ($MappedFields.TenantSummary) { - Write-Host 'Tenant Summary' + Write-Information 'Tenant Summary' ### Tenant Overview Card $ParsedAdmins = [PSCustomObject]@{} @@ -1820,7 +1832,7 @@ function Invoke-NinjaOneTenantSync { $TenantSummaryCard = Get-NinjaOneInfoCard -Title 'Tenant Details' -Data $TenantDetailsItems -Icon 'fas fa-building' ### Users details card - Write-Host 'User Details' + Write-Information 'User Details' $TotalUsersCount = ($Users | Measure-Object).count $GuestUsersCount = ($Users | Where-Object { $_.UserType -eq 'Guest' } | Measure-Object).count $LicensedUsersCount = ($licensedUsers | Measure-Object).count @@ -1878,7 +1890,7 @@ function Invoke-NinjaOneTenantSync { ### Device Details Card - Write-Host 'Device Details' + Write-Information 'Device Details' $TotalDeviceswCount = ($Devices | Measure-Object).count $ComplianceDevicesCount = ($Devices | Where-Object { $_.complianceState -eq 'compliant' } | Measure-Object).count $WindowsCount = ($Devices | Where-Object { $_.operatingSystem -eq 'Windows' } | Measure-Object).count @@ -1958,7 +1970,7 @@ function Invoke-NinjaOneTenantSync { $DeviceSummaryCardHTML = Get-NinjaOneCard -Title 'Device Details' -Body $DeviceCardBodyHTML -Icon 'fas fa-network-wired' -TitleLink $TitleLink #### Secure Score Card - Write-Host 'Secure Score Details' + Write-Information 'Secure Score Details' $Top5Actions = ($SecureScoreParsed | Where-Object { $_.scoreInPercentage -ne 100 } | Sort-Object 'Score Impact', adjustedRank -Descending) | Select-Object -First 5 # Score Chart @@ -1978,7 +1990,7 @@ function Invoke-NinjaOneTenantSync { try { $SecureScoreHTML = Get-NinjaInLineBarGraph -Title "Secure Score - $([System.Math]::Round((($CurrentSecureScore.currentScore / $MaxSecureScore) * 100),2))%" -Data $Data -KeyInLine -NoCount -NoSort } catch { - $SecureScoreHTML = "No Secure Score Data Available" + $SecureScoreHTML = 'No Secure Score Data Available' } # Recommended Actions HTML @@ -1993,30 +2005,37 @@ function Invoke-NinjaOneTenantSync { ### CIPP Applied Standards Cards - Write-Host 'Applied Standards' - Set-Location (Get-Item $PSScriptRoot).Parent.Parent.Parent.FullName - $StandardsDefinitions = Get-Content 'config/standards.json' | ConvertFrom-Json -Depth 100 + Write-Information 'Applied Standards' + $ModuleBase = Get-Module CIPPExtensions | Select-Object -ExpandProperty ModuleBase + $CIPPRoot = (Get-Item $ModuleBase).Parent.Parent.FullName + Set-Location $CIPPRoot + + try { + $StandardsDefinitions = Get-Content 'config/standards.json' | ConvertFrom-Json -Depth 100 - $Table = Get-CippTable -tablename 'standards' + $Table = Get-CippTable -tablename 'standards' - $Filter = "PartitionKey eq 'standards'" + $Filter = "PartitionKey eq 'standards'" - $AllStandards = (Get-CIPPAzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json -Depth 100 + $AllStandards = (Get-CIPPAzDataTableEntity @Table -Filter $Filter).JSON | ConvertFrom-Json -Depth 100 - $AppliedStandards = ($AllStandards | Where-Object { $_.Tenant -eq $Customer.defaultDomainName -or $_.Tenant -eq 'AllTenants' }) + $AppliedStandards = ($AllStandards | Where-Object { $_.Tenant -eq $Customer.defaultDomainName -or $_.Tenant -eq 'AllTenants' }) - $ParsedStandards = foreach ($Standard in $AppliedStandards) { - [PSCustomObject]$Standards = $Standard.Standards - $Standards.PSObject.Properties | ForEach-Object { - $CheckValue = $_ - if ($CheckValue.value) { - $MatchedStandard = $StandardsDefinitions | Where-Object { ($_.name -split 'standards.')[1] -eq $CheckValue.name } - if (($MatchedStandard | Measure-Object).count -eq 1) { - '
  • ' + $($MatchedStandard.label) + ' (' + ($($Standard.Tenant)) + ')
  • ' + $ParsedStandards = foreach ($Standard in $AppliedStandards) { + [PSCustomObject]$Standards = $Standard.Standards + $Standards.PSObject.Properties | ForEach-Object { + $CheckValue = $_ + if ($CheckValue.value) { + $MatchedStandard = $StandardsDefinitions | Where-Object { ($_.name -split 'standards.')[1] -eq $CheckValue.name } + if (($MatchedStandard | Measure-Object).count -eq 1) { + '
  • ' + $($MatchedStandard.label) + ' (' + ($($Standard.Tenant)) + ')
  • ' + } } } - } + } + } catch { + $ParsedStandards = 'No standards applied or error retrieving standards' } $TitleLink = "https://$CIPPUrl/tenant/standards/list-applied-standards?customerId=$($Customer.customerId)" @@ -2026,7 +2045,7 @@ function Invoke-NinjaOneTenantSync { $CIPPStandardsSummaryCardHTML = Get-NinjaOneCard -Title 'CIPP Applied Standards' -Body $CIPPStandardsBodyHTML -Icon 'fas fa-shield-halved' -TitleLink $TitleLink ### License Card - Write-Host 'License Details' + Write-Information 'License Details' $LicenseTableHTML = $LicensesParsed | Sort-Object 'License Name' | ConvertTo-Html -As Table -Fragment $LicenseTableHTML = '
    ' + (([System.Web.HttpUtility]::HtmlDecode($LicenseTableHTML) -replace '', '') -replace '', '') + '
    ' @@ -2035,7 +2054,7 @@ function Invoke-NinjaOneTenantSync { ### Summary Stats - Write-Host 'Widget Details' + Write-Information 'Widget Details' [System.Collections.Generic.List[PSCustomObject]]$WidgetData = @() @@ -2220,12 +2239,12 @@ function Invoke-NinjaOneTenantSync { - Write-Host 'Summary Details' + Write-Information 'Summary Details' $SummaryDetailsCardHTML = Get-NinjaOneWidgetCard -Data $WidgetData -Icon 'fas fa-building' -SmallCols 2 -MedCols 3 -LargeCols 4 -XLCols 6 -NoCard # Create the Tenant Summary Field - Write-Host 'Complete Tenant Summary' + Write-Information 'Complete Tenant Summary' $TenantSummaryHTML = '
    ' + $SummaryDetailsCardHTML + '
    ' + '
    ' + '
    ' + $TenantSummaryCard + @@ -2243,7 +2262,7 @@ function Invoke-NinjaOneTenantSync { } if ($MappedFields.UsersSummary) { - Write-Host 'User Details Section' + Write-Information 'User Details Section' $UsersTableFornatted = $ParsedUsers | Sort-Object name | Select-Object -First 100 Name, @{n = 'User Principal Name'; e = { $_.UPN } }, @@ -2281,26 +2300,26 @@ function Invoke-NinjaOneTenantSync { - Write-Host 'Posting Details' + Write-Information 'Posting Details' $Token = Get-NinjaOneToken -configuration $Configuration - Write-Host "Ninja Body: $($NinjaOrgUpdate | ConvertTo-Json -Depth 100)" + Write-Information "Ninja Body: $($NinjaOrgUpdate | ConvertTo-Json -Depth 100)" $Result = Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organization/$($MappedTenant.IntegrationId)/custom-fields" -Method PATCH -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json; charset=utf-8' -Body ($NinjaOrgUpdate | ConvertTo-Json -Depth 100) - Write-Host 'Cleaning Users Cache' + Write-Information 'Cleaning Users Cache' if (($ParsedUsers | Measure-Object).count -gt 0) { Remove-AzDataTableEntity @UsersTable -Entity ($ParsedUsers | Select-Object PartitionKey, RowKey) } - Write-Host 'Cleaning Device Cache' + Write-Information 'Cleaning Device Cache' if (($ParsedDevices | Measure-Object).count -gt 0) { Remove-AzDataTableEntity @DeviceTable -Entity ($ParsedDevices | Select-Object PartitionKey, RowKey) } - Write-Host "Total Fetch Time: $((New-TimeSpan -Start $StartTime -End $FetchEnd).TotalSeconds)" - Write-Host "Completed Total Time: $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds)" + Write-Information "Total Fetch Time: $((New-TimeSpan -Start $StartTime -End $FetchEnd).TotalSeconds)" + Write-Information "Completed Total Time: $((New-TimeSpan -Start $StartTime -End (Get-Date)).TotalSeconds)" # Set Last End Time $CurrentItem | Add-Member -NotePropertyName lastEndTime -NotePropertyValue ([string]$((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ'))) -Force From 28076f57e03770f620d102e11d67a745a63639db Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 11:21:21 -0400 Subject: [PATCH 20/31] Fix complex queries --- .../Public/GraphRequests/Get-GraphRequestList.ps1 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 index e5687be6900d..d32c1ff87f34 100644 --- a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 +++ b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 @@ -96,11 +96,9 @@ function Get-GraphRequestList { $Count = 0 if ($TenantFilter -ne 'AllTenants') { $GraphRequest = @{ - uri = $GraphQuery.ToString() - tenantid = $TenantFilter - } - if ($Parameters.'$filter') { - $GraphRequest.ComplexFilter = $true + uri = $GraphQuery.ToString() + tenantid = $TenantFilter + ComplexFilter = $true } if ($NoPagination.IsPresent) { $GraphRequest.noPagination = $NoPagination.IsPresent From 792895f0a18999ec456ce242499dfdfb53d13e4a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 11:26:27 -0400 Subject: [PATCH 21/31] Update Set-CIPPAssignedPolicy.ps1 --- Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 index 9541e8f7e1d1..08f88bd167c6 100644 --- a/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPAssignedPolicy.ps1 @@ -48,10 +48,10 @@ function Set-CIPPAssignedPolicy { } default { $GroupNames = $GroupName.Split(',') - $GroupIds = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $TenantFilter | ForEach-Object { + $GroupIds = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$select=id,displayName&$top=999' -tenantid $TenantFilter | ForEach-Object { $Group = $_ foreach ($SingleName in $GroupNames) { - if ($_.displayname -like $SingleName) { + if ($_.displayName -like $SingleName) { $group.id } } From c91f25c8ea7689119b7da4325b88e8b75aac6d52 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 11 Oct 2024 17:45:57 +0200 Subject: [PATCH 22/31] fix list intune policy urlname --- .../CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 index ffd921297a06..40ca08937cae 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 @@ -24,7 +24,7 @@ Function Invoke-ListIntunePolicy { if ($ID) { $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/deviceManagement/$($urlname)('$ID')" -tenantid $tenantfilter } else { - $Groups = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups' -tenantid $tenantfilter | Select-Object -Property id, displayName + $Groups = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/groups?$top=999' -tenantid $tenantfilter | Select-Object -Property id, displayName $BulkRequests = [PSCustomObject]@( @{ @@ -57,6 +57,8 @@ Function Invoke-ListIntunePolicy { $BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantFilter $GraphRequest = $BulkResults.body.value | ForEach-Object { + $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' + $policyTypeName = switch -Wildcard ($_.'assignments@odata.context') { '*microsoft.graph.windowsIdentityProtectionConfiguration*' { 'Identity Protection' } '*microsoft.graph.windows10EndpointProtectionConfiguration*' { 'Endpoint Protection' } From 3c2022d25110be71a3e740a574cb2d4798d629fd Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 11:54:52 -0400 Subject: [PATCH 23/31] filter queries --- .../AuditLogs/Get-CippAuditLogSearches.ps1 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 b/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 index 8c2aa9e9a7ff..c55c13186f22 100644 --- a/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 +++ b/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 @@ -13,11 +13,26 @@ function Get-CippAuditLogSearches { [Parameter()] [switch]$ReadyToProcess ) - $Queries = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/security/auditLog/queries' -AsApp $true -tenantid $TenantFilter + if ($ReadyToProcess.IsPresent) { $AuditLogSearchesTable = Get-CippTable -TableName 'AuditLogSearches' $PendingQueries = Get-CIPPAzDataTableEntity @AuditLogSearchesTable -Filter "Tenant eq '$TenantFilter' and CippStatus eq 'Pending'" + + $BulkRequests = foreach ($PendingQuery in $PendingQueries) { + @{ + id = $PendingQuery.RowKey + url = 'security/auditLog/queries/' + $PendingQuery.RowKey + method = 'GET' + } + } + if ($BulkRequests.Count -eq 0) { + return @() + } + $Queries = New-GraphBulkRequest -Requests @($BulkRequests) -AsApp $true -TenantId $TenantFilter | Select-Object -ExpandProperty body + $Queries = $Queries | Where-Object { $PendingQueries.RowKey -contains $_.id -and $_.status -eq 'succeeded' } + } else { + $Queries = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/security/auditLog/queries' -AsApp $true -tenantid $TenantFilter } return $Queries } From abf80f46fcab06511134074433fa44e3806d8bd9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 11 Oct 2024 18:08:09 +0200 Subject: [PATCH 24/31] Intune --- .../Public/Entrypoints/Invoke-ListIntunePolicy.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 index 40ca08937cae..8e65b6f27001 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListIntunePolicy.ps1 @@ -56,9 +56,9 @@ Function Invoke-ListIntunePolicy { $BulkResults = New-GraphBulkRequest -Requests $BulkRequests -tenantid $TenantFilter - $GraphRequest = $BulkResults.body.value | ForEach-Object { - $URLName = (($_).split('?') | Select-Object -First 1) -replace 'https://graph.microsoft.com/beta/deviceManagement/', '' - + $GraphRequest = $BulkResults | ForEach-Object { + $URLName = $_.Id + $_.body.Value | ForEach-Object { $policyTypeName = switch -Wildcard ($_.'assignments@odata.context') { '*microsoft.graph.windowsIdentityProtectionConfiguration*' { 'Identity Protection' } '*microsoft.graph.windows10EndpointProtectionConfiguration*' { 'Endpoint Protection' } @@ -97,7 +97,7 @@ Function Invoke-ListIntunePolicy { $_ | Add-Member -NotePropertyName PolicyExclude -NotePropertyValue ($PolicyExclude -join ', ') $_ } | Where-Object { $_.DisplayName -ne $null } - + } } $StatusCode = [HttpStatusCode]::OK } catch { From 7db8767c6f3cdb314b439882d6023d22efbada75 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 12:17:38 -0400 Subject: [PATCH 25/31] Update Invoke-CIPPStandardPerUserMFA.ps1 --- .../Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 index ecd00d8db6fb..e0aa9df16f0c 100644 --- a/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 +++ b/Modules/CIPPCore/Public/Standards/Invoke-CIPPStandardPerUserMFA.ps1 @@ -39,10 +39,14 @@ function Invoke-CIPPStandardPerUserMFA { url = "/users/$id/authentication/requirements" } } - $UsersWithoutMFA = (New-GraphBulkRequest -tenantid $tenant -Requests @($Requests) -asapp $true).body | Where-Object { $_.perUserMfaState -ne 'enforced' } | Select-Object peruserMFAState, @{Name = 'userPrincipalName'; Expression = { [System.Web.HttpUtility]::UrlDecode($_.'@odata.context'.split("'")[1]) } } + if ($Requests) { + $UsersWithoutMFA = (New-GraphBulkRequest -tenantid $tenant -Requests @($Requests) -asapp $true).body | Where-Object { $_.perUserMfaState -ne 'enforced' } | Select-Object peruserMFAState, @{Name = 'userPrincipalName'; Expression = { [System.Web.HttpUtility]::UrlDecode($_.'@odata.context'.split("'")[1]) } } + } else { + $UsersWithoutMFA = @() + } If ($Settings.remediate -eq $true) { - if (($UsersWithoutMFA.userPrincipalName | Measure-Object).Count -gt 0) { + if (($UsersWithoutMFA | Measure-Object).Count -gt 0) { try { $MFAMessage = Set-CIPPPerUserMFA -TenantFilter $Tenant -userId @($UsersWithoutMFA.userPrincipalName) -State 'enforced' Write-LogMessage -API 'Standards' -tenant $tenant -message $MFAMessage -sev Info From 5197e8ad638a1b8aab898471f774c30c3040f3a5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 13:54:11 -0400 Subject: [PATCH 26/31] Audit log limit processing --- .../Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2faa3fac26eb..ef5ea518bcb8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Webhooks/Push-AuditLogTenant.ps1 @@ -36,7 +36,7 @@ function Push-AuditLogTenant { $Configuration = $ConfigEntries | Where-Object { ($_.Tenants -match $TenantFilter -or $_.Tenants -match 'AllTenants') } if ($Configuration) { try { - $LogSearches = Get-CippAuditLogSearches -TenantFilter $TenantFilter -ReadyToProcess + $LogSearches = Get-CippAuditLogSearches -TenantFilter $TenantFilter -ReadyToProcess | Select-Object -First 20 Write-Information ('Audit Logs: Found {0} searches, begin processing' -f $LogSearches.Count) foreach ($Search in $LogSearches) { $SearchEntity = Get-CIPPAzDataTableEntity @LogSearchesTable -Filter "Tenant eq '$($TenantFilter)' and RowKey eq '$($Search.id)'" From 7ee9ba5c1654dae71ceb315771ec40d27944f8b6 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 14:15:15 -0400 Subject: [PATCH 27/31] tweak audit log search query --- Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 b/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 index c55c13186f22..ba21f2dedcb2 100644 --- a/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 +++ b/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1 @@ -16,7 +16,8 @@ function Get-CippAuditLogSearches { if ($ReadyToProcess.IsPresent) { $AuditLogSearchesTable = Get-CippTable -TableName 'AuditLogSearches' - $PendingQueries = Get-CIPPAzDataTableEntity @AuditLogSearchesTable -Filter "Tenant eq '$TenantFilter' and CippStatus eq 'Pending'" + $15MinutesAgo = (Get-Date).AddMinutes(-15).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + $PendingQueries = Get-CIPPAzDataTableEntity @AuditLogSearchesTable -Filter "Tenant eq '$TenantFilter' and (CippStatus eq 'Pending' or (CippStatus eq 'Processing' and Timestamp le datetime'$15MinutesAgo'))" | Sort-Object Timestamp $BulkRequests = foreach ($PendingQuery in $PendingQueries) { @{ From cbddf6bd9db09286420889d2167bbdeccdd38a45 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 15:18:57 -0400 Subject: [PATCH 28/31] Remove queues from application approval --- .../Push-ExecAddMultiTenantApp.ps1 | 24 ++++++++-------- .../Push-ExecApplicationCopy.ps1 | 11 ++++---- .../Invoke-ExecAddMultiTenantApp.ps1 | 28 ++++++++++++------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1 index 3f2009a0a950..09b7c2c48e27 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecAddMultiTenantApp.ps1 @@ -1,21 +1,23 @@ -function Push-ExecAddMultiTenantApp($QueueItem, $TriggerMetadata) { +function Push-ExecAddMultiTenantApp { <# .FUNCTIONALITY Entrypoint #> + [CmdletBinding()] + param($Item) try { - $Queueitem = $QueueItem | ConvertTo-Json -Depth 10 | ConvertFrom-Json - Write-Host "$($Queueitem | ConvertTo-Json -Depth 10)" - $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Queueitem.Tenant - if ($Queueitem.AppId -Notin $ServicePrincipalList.appId) { - $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $queueitem.tenant -body "{ `"appId`": `"$($Queueitem.appId)`" }" - Write-LogMessage -message "Added $($Queueitem.AppId) to tenant $($Queueitem.Tenant)" -tenant $Queueitem.Tenant -API 'Add Multitenant App' -sev Info + $Item = $Item | ConvertTo-Json -Depth 10 | ConvertFrom-Json + Write-Host "$($Item | ConvertTo-Json -Depth 10)" + $ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Item.Tenant + if ($Item.AppId -Notin $ServicePrincipalList.appId) { + $PostResults = New-GraphPostRequest 'https://graph.microsoft.com/beta/servicePrincipals' -type POST -tenantid $Item.tenant -body "{ `"appId`": `"$($Item.appId)`" }" + Write-LogMessage -message "Added $($Item.AppId) to tenant $($Item.Tenant)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info } else { - Write-LogMessage -message "This app already exists in tenant $($Queueitem.Tenant). We're adding the required permissions." -tenant $Queueitem.Tenant -API 'Add Multitenant App' -sev Info + Write-LogMessage -message "This app already exists in tenant $($Item.Tenant). We're adding the required permissions." -tenant $Item.Tenant -API 'Add Multitenant App' -sev Info } - Add-CIPPApplicationPermission -RequiredResourceAccess ($queueitem.applicationResourceAccess) -ApplicationId $queueitem.AppId -Tenantfilter $Queueitem.Tenant - Add-CIPPDelegatedPermission -RequiredResourceAccess ($queueitem.DelegateResourceAccess) -ApplicationId $queueitem.AppId -Tenantfilter $Queueitem.Tenant + Add-CIPPApplicationPermission -RequiredResourceAccess ($Item.applicationResourceAccess) -ApplicationId $Item.AppId -Tenantfilter $Item.Tenant + Add-CIPPDelegatedPermission -RequiredResourceAccess ($Item.DelegateResourceAccess) -ApplicationId $Item.AppId -Tenantfilter $Item.Tenant } catch { - Write-LogMessage -message "Error adding application to tenant $($Queueitem.Tenant) - $($_.Exception.Message)" -tenant $Queueitem.Tenant -API 'Add Multitenant App' -sev Error + Write-LogMessage -message "Error adding application to tenant $($Item.Tenant) - $($_.Exception.Message)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Error } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1 b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1 index 6437940809db..58154aed81e6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Activity Triggers/Push-ExecApplicationCopy.ps1 @@ -1,13 +1,14 @@ -function Push-ExecApplicationCopy($QueueItem, $TriggerMetadata) { +function Push-ExecApplicationCopy { <# .FUNCTIONALITY Entrypoint #> + [CmdletBinding()] + param($Item) try { - $Queueitem = $QueueItem | ConvertTo-Json -Depth 10 | ConvertFrom-Json - Write-Host "$($Queueitem | ConvertTo-Json -Depth 10)" - New-CIPPApplicationCopy -App $queueitem.AppId -Tenant $Queueitem.Tenant + Write-Host "$($Item | ConvertTo-Json -Depth 10)" + New-CIPPApplicationCopy -App $Item.AppId -Tenant $Item.Tenant } catch { - Write-LogMessage -message "Error adding application to tenant $($Queueitem.Tenant) - $($_.Exception.Message)" -tenant $Queueitem.Tenant -API 'Add Multitenant App' -sev Error + Write-LogMessage -message "Error adding application to tenant $($Item.Tenant) - $($_.Exception.Message)" -tenant $Item.Tenant -API 'Add Multitenant App' -sev Error } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1 index 4cb38d9f9dc8..dd03e24f8b3d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Application Approval/Invoke-ExecAddMultiTenantApp.ps1 @@ -17,26 +17,34 @@ function Invoke-ExecAddMultiTenantApp { $ApplicationResourceAccess = @{ ResourceAppId = '00000003-0000-0000-c000-000000000000'; resourceAccess = $ApplicationResources } $Results = try { - if ($request.body.CopyPermissions -eq $true) { + if ($Request.Body.CopyPermissions -eq $true) { $Command = 'ExecApplicationCopy' } else { $Command = 'ExecAddMultiTenantApp' } - if ('allTenants' -in $Request.body.SelectedTenants.defaultDomainName) { + if ('allTenants' -in $Request.Body.SelectedTenants.defaultDomainName) { $TenantFilter = (Get-Tenants).defaultDomainName } else { - $TenantFilter = $Request.body.SelectedTenants.defaultDomainName + $TenantFilter = $Request.Body.SelectedTenants.defaultDomainName } + $TenantCount = ($TenantFilter | Measure-Object).Count + $Queue = New-CippQueueEntry -Name 'Application Approval' -TotalTasks $TenantCount foreach ($Tenant in $TenantFilter) { try { - Push-OutputBinding -Name QueueItem -Value ([pscustomobject]@{ - FunctionName = $Command - Tenant = $tenant - appId = $Request.body.appid - applicationResourceAccess = $ApplicationResourceAccess - delegateResourceAccess = $DelegateResourceAccess - }) + $InputObject = @{ + OrchestratorName = 'ExecMultiTenantAppOrchestrator' + Batch = @([pscustomobject]@{ + FunctionName = $Command + Tenant = $tenant + AppId = $Request.Body.AppId + applicationResourceAccess = $ApplicationResourceAccess + delegateResourceAccess = $DelegateResourceAccess + QueueId = $Queue.RowKey + }) + SkipLog = $true + } + $null = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress) "Queued application to tenant $Tenant. See the logbook for deployment details" } catch { "Error queuing application to tenant $Tenant - $($_.Exception.Message)" From cf405918dde3012a60183aefeb772dfdb83f6c04 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 16:19:53 -0400 Subject: [PATCH 29/31] Update Invoke-NinjaOneTenantSync.ps1 --- .../Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 index 7ba7ee9f6b40..bcfc085b434a 100644 --- a/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 +++ b/Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 @@ -311,11 +311,11 @@ function Invoke-NinjaOneTenantSync { method = 'GET' url = '/deviceManagement/deviceCompliancePolicies/' }, - @{ + <#@{ id = 'DeviceApps' method = 'GET' url = '/deviceAppManagement/mobileApps' - }, + },#> @{ id = 'Groups' method = 'GET' From 848f408ded34604d58b98908de2a583cb6dca8d7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 17:48:58 -0400 Subject: [PATCH 30/31] Audit log error handling --- .../Public/AuditLogs/Get-CippAuditLogSearchResults.ps1 | 2 +- .../Administration/Alerts/Invoke-ListAuditLogSearches.ps1 | 6 +++++- .../CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 | 8 +++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearchResults.ps1 b/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearchResults.ps1 index 4113c17dc8fb..d2e9ab074bb9 100644 --- a/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearchResults.ps1 +++ b/Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearchResults.ps1 @@ -18,6 +18,6 @@ function Get-CippAuditLogSearchResults { ) process { - New-GraphGetRequest -uri ('https://graph.microsoft.com/beta/security/auditLog/queries/{0}/records?$top=999' -f $QueryId) -AsApp $true -tenantid $TenantFilter + New-GraphGetRequest -uri ('https://graph.microsoft.com/beta/security/auditLog/queries/{0}/records?$top=999' -f $QueryId) -AsApp $true -tenantid $TenantFilter -ErrorAction Stop } } diff --git a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogSearches.ps1 b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogSearches.ps1 index b72187063e48..561384504174 100644 --- a/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogSearches.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAuditLogSearches.ps1 @@ -20,7 +20,11 @@ function Invoke-ListAuditLogSearches { } | ConvertTo-Json -Depth 10 -Compress } 'SearchResults' { - $Results = Get-CippAuditLogSearchResults -TenantFilter $Request.Query.TenantFilter -QueryId $Request.Query.SearchId + try { + $Results = Get-CippAuditLogSearchResults -TenantFilter $Request.Query.TenantFilter -QueryId $Request.Query.SearchId + } catch { + $Results = @{ Error = $_.Exception.Message } + } $Body = @{ Results = @($Results) Metadata = @{ diff --git a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 index 33f22fc0c953..9e588dae7a5c 100644 --- a/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 +++ b/Modules/CIPPCore/Public/Webhooks/Test-CIPPAuditLogRules.ps1 @@ -35,7 +35,13 @@ function Test-CIPPAuditLogRules { } } #write-warning 'Getting audit records from Graph API' - $SearchResults = Get-CippAuditLogSearchResults -TenantFilter $TenantFilter -QueryId $SearchId + try { + $SearchResults = Get-CippAuditLogSearchResults -TenantFilter $TenantFilter -QueryId $SearchId + } catch { + Write-Warning "Error getting audit logs: $($_.Exception.Message)" + Write-LogMessage -API 'Webhooks' -message "Error getting audit logs for search $($SearchId)" -LogData (Get-CippException -Exception $_) -sev Error -tenant $TenantFilter + throw $_ + } $LogCount = ($SearchResults | Measure-Object).Count $RunGuid = New-Guid Write-Warning "Logs to process: $LogCount - RunGuid: $($RunGuid) - $($TenantFilter)" From 869cb96961f632fb0c8469e1c82b5b2df0ebd896 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 11 Oct 2024 18:39:16 -0400 Subject: [PATCH 31/31] 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 f22d756da39d..a194c18e86e8 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -6.5.0 +6.5.1