From 2d20479b728517d98f2b148195bb782b59dac5b0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 10 Mar 2024 21:30:20 -0400 Subject: [PATCH 01/29] Alerts tweak --- Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 | 2 +- Modules/CippEntrypoints/CippEntrypoints.psm1 | 5 ++--- Scheduler_GetQueue/run.ps1 | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 index 48b5ac8f474b..0e4b50c8f9f2 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-SchedulerAlert.ps1 @@ -41,7 +41,7 @@ function Push-SchedulerAlert { } if (($Batch | Measure-Object).Count -gt 0) { $InputObject = [PSCustomObject]@{ - OrchestratorName = 'Alerts' + OrchestratorName = 'AlertsOrchestrator' SkipLog = $true Batch = @($Batch) } diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index 67a480956b2d..749a0a41a674 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -52,7 +52,6 @@ function Receive-CippOrchestrationTrigger { param($Context) try { - if (Test-Json -Json $Context.Input) { $OrchestratorInput = $Context.Input | ConvertFrom-Json } else { @@ -67,7 +66,7 @@ function Receive-CippOrchestrationTrigger { #Write-Host ($OrchestratorInput | ConvertTo-Json -Depth 10) $RetryOptions = New-DurableRetryOptions @DurableRetryOptions - if ($Context.IsReplaying -ne $true -and -not $Context.Input.SkipLog) { + if ($Context.IsReplaying -ne $true -and $Context.Input.SkipLog -ne $true) { Write-LogMessage -API $OrchestratorInput.OrchestratorName -tenant $OrchestratorInput.TenantFilter -message "Started $($OrchestratorInput.OrchestratorName)" -sev info } @@ -83,7 +82,7 @@ function Receive-CippOrchestrationTrigger { } } - if ($Context.IsReplaying -ne $true -and -not $Context.Input.SkipLog) { + if ($Context.IsReplaying -ne $true -and $Context.Input.SkipLog -ne $true) { Write-LogMessage -API $OrchestratorInput.OrchestratorName -tenant $tenant -message "Finished $($OrchestratorInput.OrchestratorName)" -sev Info } } catch { diff --git a/Scheduler_GetQueue/run.ps1 b/Scheduler_GetQueue/run.ps1 index 2e80dfd588a1..6d0553001e1e 100644 --- a/Scheduler_GetQueue/run.ps1 +++ b/Scheduler_GetQueue/run.ps1 @@ -35,7 +35,7 @@ $Batch = foreach ($Task in $Tasks) { } } $InputObject = [PSCustomObject]@{ - OrchestratorName = 'Scheduler' + OrchestratorName = 'SchedulerOrchestrator' Batch = @($Batch) } #Write-Host ($InputObject | ConvertTo-Json) From 502e3046d60775958b174b294cb05e5a12b5ec87 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 10 Mar 2024 21:32:48 -0400 Subject: [PATCH 02/29] update init dev env --- Tools/Initialize-DevEnvironment.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/Initialize-DevEnvironment.ps1 b/Tools/Initialize-DevEnvironment.ps1 index 4f4f8f55aa58..e8b67a373ae5 100644 --- a/Tools/Initialize-DevEnvironment.ps1 +++ b/Tools/Initialize-DevEnvironment.ps1 @@ -13,3 +13,4 @@ Import-Module "$CippRoot\Modules\AzBobbyTables" Import-Module "$CippRoot\Modules\DNSHealth" Import-Module "$CippRoot\Modules\CippQueue" Import-Module "$CippRoot\Modules\CippCore" +Get-CIPPAuthentication \ No newline at end of file From 5113751dc0b7f6b612007de569f99f695b2718f3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 11:05:29 +0100 Subject: [PATCH 03/29] check for existence of upn --- Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 | 2 +- .../CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 index 66685982d956..6b933fbf64a0 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAdmins.ps1 @@ -13,7 +13,7 @@ function Push-CIPPAlertMFAAdmins { } if (!$DuoActive) { $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?$top=999&$filter=IsAdmin eq true' -tenantid $($Item.tenant) | Where-Object -Property 'isMfaRegistered' -EQ $false - if ($users) { + if ($users.UserPrincipalName) { Write-AlertMessage -tenant $Item.tenant -message "The following admins do not have MFA registered: $($users.UserPrincipalName -join ', ')" } } else { diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 index a02d2afcdc34..e6401a7b117f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertMFAAlertUsers.ps1 @@ -7,7 +7,7 @@ function Push-CIPPAlertMFAAlertUsers { try { $users = New-GraphGETRequest -uri 'https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails?$filter=isMfaRegistered eq false and userType eq ''member''&$select=userPrincipalName,lastUpdatedDateTime,isMfaRegistered' -tenantid $($Item.tenant) - if ($users) { + if ($users.UserPrincipalName) { Write-AlertMessage -tenant $Item.tenant -message "The following $($users.Count) users do not have MFA registered: $($users.UserPrincipalName -join ', ')" } From 8fcf2f4a95134edca8416d2e7b83790fb4310d75 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 11:53:26 +0100 Subject: [PATCH 04/29] fixes sharepoint quota --- .../Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 index 9614e59d340c..58dedd4888a8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertSharepointQuota.ps1 @@ -11,7 +11,7 @@ function Push-CIPPAlertSharepointQuota { $sharepointToken.Add('accept', 'application/json') $sharepointQuota = (Invoke-RestMethod -Method 'GET' -Headers $sharepointToken -Uri "https://$($tenantName)-admin.sharepoint.com/_api/StorageQuotas()?api-version=1.3.2" -ErrorAction Stop).value if ($sharepointQuota) { - if ($Item.value) { $Value = $Item.value } else { $Value = 90 } + if ($Item.value -Is [Boolean]) { $Value = 90 } else { $Value = $Item.value } $UsedStoragePercentage = [int](($sharepointQuota.GeoUsedStorageMB / $sharepointQuota.TenantStorageMB) * 100) if ($UsedStoragePercentage -gt $Value) { Write-AlertMessage -tenant $($Item.tenant) -message "SharePoint Storage is at $($UsedStoragePercentage)%. Your alert threshold is $($Value)%" From 55ccf3ce8943fdb079bdf992b79c69f42b85a2d5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 12:24:10 +0100 Subject: [PATCH 05/29] fixes depth issues --- .../Public/Entrypoints/Invoke-ExecUserSettings.ps1 | 9 ++++----- .../Public/Entrypoints/Invoke-ListUserSettings.ps1 | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUserSettings.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUserSettings.ps1 index 76469b49b477..ab9092f13c1d 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUserSettings.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecUserSettings.ps1 @@ -11,18 +11,17 @@ function Invoke-ExecUserSettings { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' try { - $object = $request.body.currentSettings | Select-Object * -ExcludeProperty CurrentTenant, pageSizes, sidebarShow, sidebarUnfoldable, _persist | ConvertTo-Json -Compress + $object = $request.body.currentSettings | Select-Object * -ExcludeProperty CurrentTenant, pageSizes, sidebarShow, sidebarUnfoldable, _persist | ConvertTo-Json -Compress -Depth 10 $Table = Get-CippTable -tablename 'UserSettings' $Table.Force = $true Add-CIPPAzDataTableEntity @Table -Entity @{ JSON = "$object" RowKey = "$($Request.body.user)" - PartitionKey = "UserSettings" + PartitionKey = 'UserSettings' } $StatusCode = [HttpStatusCode]::OK - $Results = [pscustomobject]@{"Results" = "Successfully added user settings" } - } - catch { + $Results = [pscustomobject]@{'Results' = 'Successfully added user settings' } + } catch { $ErrorMsg = Get-NormalizedError -message $($_.Exception.Message) $Results = "Function Error: $ErrorMsg" $StatusCode = [HttpStatusCode]::BadRequest diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUserSettings.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUserSettings.ps1 index 274c5d8383f1..50781d922e7b 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUserSettings.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListUserSettings.ps1 @@ -14,8 +14,8 @@ function Invoke-ListUserSettings { try { $Table = Get-CippTable -tablename 'UserSettings' $UserSettings = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq 'allUsers'" - if (!$UserSettings) { Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$username'" } - $UserSettings = $UserSettings | Select-Object -ExpandProperty JSON | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue + if (!$UserSettings) { $userSettings = Get-CIPPAzDataTableEntity @Table -Filter "RowKey eq '$username'" } + $UserSettings = $UserSettings.JSON | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue $StatusCode = [HttpStatusCode]::OK $Results = $UserSettings } catch { From 872449094bf48a7d3294564f20343431c526f6f5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 12 Mar 2024 11:44:19 -0400 Subject: [PATCH 06/29] Tenant Onboarding durable --- .../Entrypoints/Invoke-ExecOnboardTenant.ps1 | 20 +++++++++++------ .../Push-ExecOnboardTenantQueue.ps1 | 22 +++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 index e9e23c2a174e..41365297ca4f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecOnboardTenant.ps1 @@ -52,13 +52,19 @@ function Invoke-ExecOnboardTenant { } Add-CIPPAzDataTableEntity @OnboardTable -Entity $TenantOnboarding -Force -ErrorAction Stop - Push-OutputBinding -Name QueueItem -Value ([pscustomobject]@{ - FunctionName = 'ExecOnboardTenantQueue' - id = $Id - Roles = $Request.Body.gdapRoles - AddMissingGroups = $Request.Body.addMissingGroups - AutoMapRoles = $Request.Body.autoMapRoles - }) + $Item = [pscustomobject]@{ + FunctionName = 'ExecOnboardTenantQueue' + id = $Id + Roles = $Request.Body.gdapRoles + AddMissingGroups = $Request.Body.addMissingGroups + AutoMapRoles = $Request.Body.autoMapRoles + } + + $InputObject = @{ + OrchestratorName = 'OnboardingOrchestrator' + Batch = @($Item) + } + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) } $Steps = $TenantOnboarding.OnboardingSteps | ConvertFrom-Json diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 index 6c12ad723332..24d206b069c7 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ExecOnboardTenantQueue.ps1 @@ -4,11 +4,11 @@ Function Push-ExecOnboardTenantQueue { Entrypoint #> [CmdletBinding()] - param($QueueItem, $TriggerMetadata) + param($Item) try { $DateFormat = '%Y-%m-%d %H:%M:%S' - $Id = $QueueItem.id - #Write-Host ($QueueItem.Roles | ConvertTo-Json) + $Id = $Item.id + #Write-Host ($Item.Roles | ConvertTo-Json) $Start = Get-Date $Logs = [System.Collections.Generic.List[object]]::new() $OnboardTable = Get-CIPPTable -TableName 'TenantOnboarding' @@ -117,7 +117,7 @@ Function Push-ExecOnboardTenantQueue { if ($OnboardingSteps.Step2.Status -eq 'succeeded') { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking group mapping' }) $AccessAssignments = New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships/$Id/accessAssignments" - if ($AccessAssignments.id -and $QueueItem.AutoMapRoles -ne $true) { + if ($AccessAssignments.id -and $Item.AutoMapRoles -ne $true) { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Groups mapped' }) $OnboardingSteps.Step3.Status = 'succeeded' $OnboardingSteps.Step3.Message = 'Your GDAP relationship already has mapped security groups' @@ -136,8 +136,8 @@ Function Push-ExecOnboardTenantQueue { $MissingRoles = [System.Collections.Generic.List[object]]::new() $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Relationship has existing access assignments, checking for missing mappings' }) #Write-Host ($AccessAssignments | ConvertTo-Json -Depth 5) - if ($QueueItem.Roles -and $QueueItem.AutoMapRoles -eq $true) { - foreach ($Role in $QueueItem.Roles) { + if ($Item.Roles -and $Item.AutoMapRoles -eq $true) { + foreach ($Role in $Item.Roles) { if ($AccessAssignments.accessContainer.accessContainerid -notcontains $Role.GroupId -and $Relationship.accessDetails.unifiedRoles.roleDefinitionId -contains $Role.roleDefinitionId) { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = "Adding missing group to relationship: $($Role.GroupName)" }) $MissingRoles.Add([PSCustomObject]$Role) @@ -161,16 +161,16 @@ Function Push-ExecOnboardTenantQueue { } } - if (!$AccessAssignments.id -and !$Invite -and $QueueItem.Roles) { + if (!$AccessAssignments.id -and !$Invite -and $Item.Roles) { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'No access assignments found, using defined role mapping.' }) $MatchingRoles = [System.Collections.Generic.List[object]]::new() - foreach ($Role in $QueueItem.Roles) { + foreach ($Role in $Item.Roles) { if ($Relationship.accessDetails.unifiedRoles.roleDefinitionId -contains $Role.roleDefinitionId) { $MatchingRoles.Add([PSCustomObject]$Role) } } - if (($MatchingRoles | Measure-Object).Count -gt 0 -and $QueueItem.AutoMapRoles -eq $true) { + if (($MatchingRoles | Measure-Object).Count -gt 0 -and $Item.AutoMapRoles -eq $true) { $Invite = [PSCustomObject]@{ 'PartitionKey' = 'invite' 'RowKey' = $Id @@ -224,11 +224,11 @@ Function Push-ExecOnboardTenantQueue { if ($AccessAssignments.status -notcontains 'pending') { $OnboardingSteps.Step3.Message = 'Group check: Access assignments are mapped and active' $OnboardingSteps.Step3.Status = 'succeeded' - if ($QueueItem.AddMissingGroups -eq $true) { + if ($Item.AddMissingGroups -eq $true) { $Logs.Add([PSCustomObject]@{ Date = Get-Date -UFormat $DateFormat; Log = 'Checking for missing groups for SAM user' }) $SamUserId = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me?`$select=id").id $CurrentMemberships = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/me/transitiveMemberOf?`$select=id,displayName" - foreach ($Role in $QueueItem.Roles) { + foreach ($Role in $Item.Roles) { if ($CurrentMemberships.id -notcontains $Role.GroupId) { $PostBody = @{ '@odata.id' = 'https://graph.microsoft.com/v1.0/directoryObjects/{0}' -f $SamUserId From 6d8ccf652d753afa988c18a11b0e0509e2bcc454 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 16:52:26 +0100 Subject: [PATCH 07/29] update license overview --- .../Entrypoints/Invoke-ListLicenses.ps1 | 12 ++++- .../Public/Get-CIPPLicenseOverview.ps1 | 51 +++++++++++-------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 index 27c2db9767a3..03a719a7f259 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 @@ -18,7 +18,11 @@ Function Invoke-ListLicenses { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter $RawGraphRequest = if ($TenantFilter -ne 'AllTenants') { - $GraphRequest = Get-CIPPLicenseOverview -TenantFilter $TenantFilter + $GraphRequest = Get-CIPPLicenseOverview -TenantFilter $TenantFilter | ForEach-Object { + $TermInfo = $_.TermInfo | ConvertFrom-Json -ErrorAction SilentlyContinue + $_.TermInfo = $TermInfo + $_ + } } else { $Table = Get-CIPPTable -TableName cachelicenses $Rows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Timestamp -GT (Get-Date).AddHours(-1) @@ -29,7 +33,11 @@ Function Invoke-ListLicenses { License = 'Loading data for all tenants. Please check back in 1 minute' } } else { - $GraphRequest = $Rows + $GraphRequest = $Rows | ForEach-Object { + $TermInfo = $_.TermInfo | ConvertFrom-Json -ErrorAction SilentlyContinue + $_.TermInfo = $TermInfo + $_ + } } } diff --git a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 index 698d30653774..9f06bddd2512 100644 --- a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 @@ -3,19 +3,17 @@ function Get-CIPPLicenseOverview { [CmdletBinding()] param ( $TenantFilter, - $APIName = "Get License Overview", + $APIName = 'Get License Overview', $ExecutingUser ) $LicRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $TenantFilter - $LicOverviewRequest = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/directory/subscriptions' -tenantid $TenantFilter | Where-Object -Property nextLifecycleDateTime -GT (Get-Date) | Select-Object *, - @{Name = 'consumedUnits'; Expression = { ($LicRequest | Where-Object -Property skuid -EQ $_.skuId).consumedUnits } }, - @{Name = 'prepaidUnits'; Expression = { ($LicRequest | Where-Object -Property skuid -EQ $_.skuId).prepaidUnits } } + $SkuIDs = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/directory/subscriptions' -tenantid $TenantFilter $RawGraphRequest = [PSCustomObject]@{ Tenant = $TenantFilter - Licenses = $LicOverviewRequest + Licenses = $LicRequest } Set-Location (Get-Item $PSScriptRoot).FullName $ConvertTable = Import-Csv Conversiontable.csv @@ -27,33 +25,42 @@ function Get-CIPPLicenseOverview { if ($sku.skuId -in $ExcludedSkuList.GUID) { continue } $PrettyName = ($ConvertTable | Where-Object { $_.guid -eq $sku.skuid }).'Product_Display_Name' | Select-Object -Last 1 if (!$PrettyName) { $PrettyName = $sku.skuPartNumber } - $diff = $sku.nextLifecycleDateTime - $sku.createdDateTime + # Initialize $Term with the default value - $Term = "Term unknown or non-NCE license" - if ($diff.Days -ge 360 -and $diff.Days -le 1089) { - $Term = "Yearly" + $TermInfo = foreach ($Subscription in $skuid.subscriptionIds) { + $SubInfo = $SkuIDs | Where-Object { $_.id -eq $Subscription } + $diff = $SubInfo.nextLifecycleDateTime - $SubInfo.createdDateTime + $Term = 'Term unknown or non-NCE license' + if ($diff.Days -ge 360 -and $diff.Days -le 1089) { + $Term = 'Yearly' + } elseif ($diff.Days -ge 1090 -and $diff.Days -le 1100) { + $Term = '3 Year' + } elseif ($diff.Days -ge 25 -and $diff.Days -le 35) { + $Term = 'Monthly' + } + $TimeUntilRenew = ($subinfo.nextLifecycleDateTime - (Get-Date)).days + [PSCustomObject]@{ + Status = $SubInfo.status + Term = $Term + TotalLicenses = $SubInfo.totalLicenses + TimeUntilRenew = $TimeUntilRenew + NextLifecycle = $SubInfo.nextLifecycleDateTime + SubscriptionId = $subinfo.id + IsTrial = $SubInfo.isTrial + CSPSubscriptionId = $SubInfo.commerceSubscriptionId + OCPSubscriptionId = $SubInfo.ocpSubscriptionId + } } - elseif ($diff.Days -ge 1090 -and $diff.Days -le 1100) { - $Term = "3 Year" - } - elseif ($diff.Days -ge 25 -and $diff.Days -le 35) { - $Term = "Monthly" - } - $TimeUntilRenew = $sku.nextLifecycleDateTime - (Get-Date) [pscustomobject]@{ Tenant = [string]$singlereq.Tenant License = [string]$PrettyName CountUsed = [string]"$($sku.consumedUnits)" CountAvailable = [string]$sku.prepaidUnits.enabled - $sku.consumedUnits - TotalLicenses = [string]"$($sku.TotalLicenses)" + TotalLicenses = [string]"$($sku.prepaidUnits.enabled)" skuId = [string]$sku.skuId skuPartNumber = [string]$PrettyName availableUnits = [string]$sku.prepaidUnits.enabled - $sku.consumedUnits - EstTerm = [string]$Term - TimeUntilRenew = [string]"$($TimeUntilRenew.Days)" - Trial = [bool]$sku.isTrial - dateCreated = [string]$sku.createdDateTime - dateExpires = [string]$sku.nextLifecycleDateTime + TermInfo = [string]($TermInfo | ConvertTo-Json -Depth 10 -Compress) 'PartitionKey' = 'License' 'RowKey' = "$($singlereq.Tenant) - $($sku.skuid)" } From 46727f6d310ad521cf4ba44ac0a9e0d56afa728d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 16:56:13 +0100 Subject: [PATCH 08/29] license overview --- Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 index 9f06bddd2512..d4d057f6ddfc 100644 --- a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 @@ -43,10 +43,10 @@ function Get-CIPPLicenseOverview { Status = $SubInfo.status Term = $Term TotalLicenses = $SubInfo.totalLicenses - TimeUntilRenew = $TimeUntilRenew + DaysUntilRenew = $TimeUntilRenew NextLifecycle = $SubInfo.nextLifecycleDateTime - SubscriptionId = $subinfo.id IsTrial = $SubInfo.isTrial + SubscriptionId = $subinfo.id CSPSubscriptionId = $SubInfo.commerceSubscriptionId OCPSubscriptionId = $SubInfo.ocpSubscriptionId } From 0adb85d0c4d1cda43bd472333bafb5b531d0a2f7 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 17:08:50 +0100 Subject: [PATCH 09/29] ordered list --- Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 index d4d057f6ddfc..9668cd51b50e 100644 --- a/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 +++ b/Modules/CIPPCore/Public/Get-CIPPLicenseOverview.ps1 @@ -27,7 +27,7 @@ function Get-CIPPLicenseOverview { if (!$PrettyName) { $PrettyName = $sku.skuPartNumber } # Initialize $Term with the default value - $TermInfo = foreach ($Subscription in $skuid.subscriptionIds) { + $TermInfo = foreach ($Subscription in $sku.subscriptionIds) { $SubInfo = $SkuIDs | Where-Object { $_.id -eq $Subscription } $diff = $SubInfo.nextLifecycleDateTime - $SubInfo.createdDateTime $Term = 'Term unknown or non-NCE license' From 21a4119ac3b0dc5de9e80bced3a23b93084531db Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 12 Mar 2024 12:27:01 -0400 Subject: [PATCH 10/29] Graph Request durable --- .../Push-ListGraphRequestQueue.ps1 | 46 +++++++++---------- .../GraphRequests/Get-GraphRequestList.ps1 | 21 +++++++-- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ListGraphRequestQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ListGraphRequestQueue.ps1 index 82f12e1df665..1705df9a7f70 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-ListGraphRequestQueue.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ListGraphRequestQueue.ps1 @@ -4,36 +4,36 @@ function Push-ListGraphRequestQueue { Entrypoint #> # Input bindings are passed in via param block. - param($QueueItem, $TriggerMetadata) + param($Item) # Write out the queue message and metadata to the information log. - Write-Host "PowerShell queue trigger function processed work item: $($QueueItem.Endpoint) - $($QueueItem.TenantFilter)" + Write-Host "PowerShell queue trigger function processed work item: $($Item.Endpoint) - $($Item.TenantFilter)" - $TenantQueueName = '{0} - {1}' -f $QueueItem.QueueName, $QueueItem.TenantFilter - Update-CippQueueEntry -RowKey $QueueItem.QueueId -Status 'Processing' -Name $TenantQueueName + $TenantQueueName = '{0} - {1}' -f $Item.QueueName, $Item.TenantFilter + Update-CippQueueEntry -RowKey $Item.QueueId -Status 'Processing' -Name $TenantQueueName $ParamCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) - foreach ($Item in ($QueueItem.Parameters.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) { - $ParamCollection.Add($Item.Key, $Item.Value) + foreach ($Param in ($Item.Parameters.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) { + $ParamCollection.Add($Param.Key, $Param.Value) } - $PartitionKey = $QueueItem.PartitionKey + $PartitionKey = $Item.PartitionKey - $TableName = ('cache{0}' -f ($QueueItem.Endpoint -replace '[^A-Za-z0-9]'))[0..62] -join '' - Write-Host $TableName + $TableName = ('cache{0}' -f ($Item.Endpoint -replace '[^A-Za-z0-9]'))[0..62] -join '' + Write-Host "Queue Table: $TableName" $Table = Get-CIPPTable -TableName $TableName - $Filter = "PartitionKey eq '{0}' and Tenant eq '{1}'" -f $PartitionKey, $QueueItem.TenantFilter - Write-Host $Filter + $Filter = "PartitionKey eq '{0}' and Tenant eq '{1}'" -f $PartitionKey, $Item.TenantFilter + Write-Host "Filter: $Filter" Get-CIPPAzDataTableEntity @Table -Filter $Filter -Property PartitionKey, RowKey | Remove-AzDataTableEntity @Table $GraphRequestParams = @{ - TenantFilter = $QueueItem.TenantFilter - Endpoint = $QueueItem.Endpoint - Parameters = $QueueItem.Parameters - NoPagination = $QueueItem.NoPagination - ReverseTenantLookupProperty = $QueueItem.ReverseTenantLookupProperty - ReverseTenantLookup = $QueueItem.ReverseTenantLookup + TenantFilter = $Item.TenantFilter + Endpoint = $Item.Endpoint + Parameters = $Item.Parameters + NoPagination = $Item.NoPagination + ReverseTenantLookupProperty = $Item.ReverseTenantLookupProperty + ReverseTenantLookup = $Item.ReverseTenantLookup SkipCache = $true } @@ -41,7 +41,7 @@ function Push-ListGraphRequestQueue { Get-GraphRequestList @GraphRequestParams } catch { [PSCustomObject]@{ - Tenant = $QueueItem.Tenant + Tenant = $Item.Tenant CippStatus = "Could not connect to tenant. $($_.Exception.message)" } } @@ -49,9 +49,9 @@ function Push-ListGraphRequestQueue { $GraphResults = foreach ($Request in $RawGraphRequest) { $Json = ConvertTo-Json -Depth 5 -Compress -InputObject $Request [PSCustomObject]@{ - TenantFilter = [string]$QueueItem.TenantFilter - QueueId = [string]$QueueItem.QueueId - QueueType = [string]$QueueItem.QueueType + TenantFilter = [string]$Item.TenantFilter + QueueId = [string]$Item.QueueId + QueueType = [string]$Item.QueueType RowKey = [string](New-Guid) PartitionKey = [string]$PartitionKey Data = [string]$Json @@ -59,9 +59,9 @@ function Push-ListGraphRequestQueue { } try { Add-CIPPAzDataTableEntity @Table -Entity $GraphResults -Force | Out-Null - Update-CippQueueEntry -RowKey $QueueItem.QueueId -Status 'Completed' + Update-CippQueueEntry -RowKey $Item.QueueId -Status 'Completed' } catch { Write-Host "Queue Error: $($_.Exception.Message)" - Update-CippQueueEntry -RowKey $QueueItem.QueueId -Status 'Failed' + Update-CippQueueEntry -RowKey $Item.QueueId -Status 'Failed' } } \ No newline at end of file diff --git a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 index 3b3b4660a0bf..52cc2c3778dc 100644 --- a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 +++ b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 @@ -158,9 +158,9 @@ function Get-GraphRequestList { } Write-Host 'Pushing output bindings' try { - Get-Tenants -IncludeErrors | ForEach-Object { + $Batch = Get-Tenants -IncludeErrors | ForEach-Object { $TenantFilter = $_.defaultDomainName - $QueueTenant = [PSCustomObject]@{ + [PSCustomObject]@{ FunctionName = 'ListGraphRequestQueue' TenantFilter = $TenantFilter Endpoint = $Endpoint @@ -175,8 +175,15 @@ function Get-GraphRequestList { ReverseTenantLookup = $ReverseTenantLookup.IsPresent } - Push-OutputBinding -Name QueueItem -Value $QueueTenant + #Push-OutputBinding -Name QueueItem -Value $QueueTenant + } + + $InputObject = @{ + OrchestratorName = 'GraphRequestOrchestrator' + Batch = @($Batch) } + #Write-Host ($InputObject | ConvertTo-Json -Depth 5) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) } catch { Write-Host "QUEUE ERROR: $($_.Exception.Message)" } @@ -234,7 +241,13 @@ function Get-GraphRequestList { ReverseTenantLookup = $ReverseTenantLookup.IsPresent } - Push-OutputBinding -Name QueueItem -Value $QueueTenant + $InputObject = @{ + OrchestratorName = 'GraphRequestOrchestrator' + Batch = @($QueueTenant) + } + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + + #Push-OutputBinding -Name QueueItem -Value $QueueTenant [PSCustomObject]@{ QueueMessage = ('Loading {0} rows for {1}. Please check back after the job completes' -f $Count, $TenantFilter) From ea858fcf4aa55185afecbdf98501c8f7e0c2be75 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 17:38:07 +0100 Subject: [PATCH 11/29] cleanup --- MailProviders/Mesh.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 MailProviders/Mesh.json diff --git a/MailProviders/Mesh.json b/MailProviders/Mesh.json deleted file mode 100644 index 3109cc8c4876..000000000000 --- a/MailProviders/Mesh.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Name": "Mesh Email Security", - "_MxComment": "https://docs.emailsecurity.app/help-center/connection-details", - "MxMatch": "emailsecurity.app", - "_SpfComment": "https://docs.emailsecurity.app/help-center/connection-details", - "SpfInclude": "spf1.emailsecurity.app", - "_DkimComment": "No configuration found", - "Selectors": [""] -} \ No newline at end of file From 598be08dda54dbd666b4feffeb3e3d453c8849ff Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 12 Mar 2024 18:07:49 +0100 Subject: [PATCH 12/29] added hard exit fail --- Modules/CIPPCore/Public/Entrypoints/Invoke-AddUser.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddUser.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddUser.ps1 index c1d7512b14d6..41c1004a8cf6 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-AddUser.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-AddUser.ps1 @@ -59,6 +59,7 @@ Function Invoke-AddUser { } catch { Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantid) -message "Failed to create user. Error:$($_.Exception.Message)" -Sev 'Error' $body = $results.add("Failed to create user. $($_.Exception.Message)" ) + exit 1 } try { From 684f9fdd4c9cf61500d1b2fd44d85aa297b8780c Mon Sep 17 00:00:00 2001 From: Mo Date: Wed, 13 Mar 2024 11:36:20 +0000 Subject: [PATCH 13/29] Adding group members missing output --- Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 index 2ff3c406102b..a4c66a07cb7b 100644 --- a/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 +++ b/Modules/CIPPCore/Public/Add-CIPPGroupMember.ps1 @@ -16,7 +16,7 @@ function Add-CIPPGroupMember( } else { New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)" -tenantid $TenantFilter -type patch -body $addmemberbody -Verbose } - $Message = "Successfully added user $($Member) to $GroupId." + $Message = "Successfully added user $($Member) to $($GroupId)." Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $Message -Sev 'Info' return $message return From b2dd27611b0a4384b681a674da3d2d821e512b9a Mon Sep 17 00:00:00 2001 From: Mo Date: Wed, 13 Mar 2024 12:18:15 +0000 Subject: [PATCH 14/29] Add output for what group the user was removed from --- Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 b/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 index 9567039fe8e4..54c6a33e1a9d 100644 --- a/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 +++ b/Modules/CIPPCore/Public/Remove-CIPPGroupMember.ps1 @@ -16,7 +16,7 @@ function Remove-CIPPGroupMember( } else { New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($GroupId)/members/$($Member)/`$ref" -tenantid $TenantFilter -type DELETE -body '{}' -Verbose } - $Message = "Successfully removed user $($Member) from $GroupId." + $Message = "Successfully removed user $($Member) from $($GroupId)." Write-LogMessage -user $ExecutingUser -API $APIName -tenant $TenantFilter -message $Message -Sev 'Info' return $message } catch { From e71c9ccb6f1cad34d061424ab23931fa602ad96c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 13 Mar 2024 09:57:52 -0400 Subject: [PATCH 15/29] Webhooks Durable --- .../Invoke-ListPendingWebhooks.ps1 | 41 ++++++ .../Entrypoints/Invoke-PublicWebhooks.ps1 | 128 ++++++++++++++++++ .../Entrypoints/Push-PublicWebhookProcess.ps1 | 17 +++ PublicWebhooks/function.json | 22 --- PublicWebhooks/run.ps1 | 37 ----- PublicWebhooksProcess/function.json | 10 -- PublicWebhooksProcess/run.ps1 | 79 ----------- Scheduler_GetWebhooks/function.json | 15 ++ Scheduler_GetWebhooks/run.ps1 | 13 ++ 9 files changed, 214 insertions(+), 148 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/Invoke-ListPendingWebhooks.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 delete mode 100644 PublicWebhooks/function.json delete mode 100644 PublicWebhooks/run.ps1 delete mode 100644 PublicWebhooksProcess/function.json delete mode 100644 PublicWebhooksProcess/run.ps1 create mode 100644 Scheduler_GetWebhooks/function.json create mode 100644 Scheduler_GetWebhooks/run.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListPendingWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListPendingWebhooks.ps1 new file mode 100644 index 000000000000..24c7020f0e31 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListPendingWebhooks.ps1 @@ -0,0 +1,41 @@ +using namespace System.Net + +Function Invoke-ListPendingWebhooks { + <# + .FUNCTIONALITY + Entrypoint + #> + [CmdletBinding()] + param($Request, $TriggerMetadata) + + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + + # Write to the Azure Functions log stream. + Write-Host 'PowerShell HTTP trigger function processed a request.' + try { + $Table = Get-CIPPTable -TableName 'WebhookIncoming' + $Webhooks = Get-CIPPAzDataTableEntity @Table + $Results = $Webhooks | Select-Object -ExcludeProperty RowKey, PartitionKey, ETag, Timestamp + $PendingWebhooks = foreach ($Result in $Results) { + foreach ($Property in $Result.PSObject.Properties.Name) { + if (Test-Json -Json $Result.$Property -ErrorAction SilentlyContinue) { + $Result.$Property = $Result.$Property | ConvertFrom-Json + } + } + $Result + } + } catch { + $PendingWebhooks = @() + } + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @{ + Results = @($PendingWebhooks) + Metadata = @{ + Count = ($PendingWebhooks | Measure-Object).Count + } + } + }) +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 new file mode 100644 index 000000000000..97c135235cd6 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-PublicWebhooks.ps1 @@ -0,0 +1,128 @@ +using namespace System.Net +function Invoke-PublicWebhooks { + # Input bindings are passed in via param block. + param($Request, $TriggerMetadata) + + Set-Location (Get-Item $PSScriptRoot).Parent.FullName + $WebhookTable = Get-CIPPTable -TableName webhookTable + $WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming + $Webhooks = Get-CIPPAzDataTableEntity @WebhookTable + Write-Host 'Received request' + Write-Host "CIPPID: $($request.Query.CIPPID)" + $url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 + Write-Host $url + if ($Request.Query.CIPPID -in $Webhooks.RowKey) { + Write-Host 'Found matching CIPPID' + if ($Webhooks.Resource -eq 'M365AuditLogs') { + Write-Host "Found M365AuditLogs - This is an old entry, we'll deny so Microsoft stops sending it." + $body = 'This webhook is not authorized, its an old entry.' + $StatusCode = [HttpStatusCode]::Forbidden + } + if ($Request.query.ValidationToken -or $Request.body.validationCode) { + Write-Host 'Validation token received' + $body = $request.query.ValidationToken + $StatusCode = [HttpStatusCode]::OK + } else { + Write-Host 'Received request' + Write-Host "CIPPID: $($request.Query.CIPPID)" + $url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 + Write-Host $url + + $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID + + if ($Request.Query.Type -eq 'GraphSubscription') { + # Graph Subscriptions + [pscustomobject]$ReceivedItem = $Request.Body.value + $Entity = [PSCustomObject]@{ + PartitionKey = 'Webhook' + RowKey = [string](New-Guid).Guid + Type = $Request.Query.Type + Data = [string]($ReceivedItem | ConvertTo-Json -Depth 10) + CIPPID = $Request.Query.CIPPID + WebhookInfo = [string]($WebhookInfo | ConvertTo-Json -Depth 10) + FunctionName = 'PublicWebhookProcess' + } + Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity + ## Push webhook data to queue + #Invoke-CippGraphWebhookProcessing -Data $ReceivedItem -CIPPID $request.Query.CIPPID -WebhookInfo $Webhookinfo + + } else { + # Auditlog Subscriptions + try { + foreach ($ReceivedItem In ($Request.body)) { + $ReceivedItem = [pscustomobject]$ReceivedItem + Write-Host "Received Item: $($ReceivedItem | ConvertTo-Json -Depth 15 -Compress))" + $TenantFilter = (Get-Tenants | Where-Object -Property customerId -EQ $ReceivedItem.TenantId).defaultDomainName + Write-Host "Webhook TenantFilter: $TenantFilter" + $ConfigTable = get-cipptable -TableName 'SchedulerConfig' + $Alertconfig = Get-CIPPAzDataTableEntity @ConfigTable | Where-Object { $_.Tenant -eq $TenantFilter -or $_.Tenant -eq 'AllTenants' } + $Operations = @(($AlertConfig.if | ConvertFrom-Json -ErrorAction SilentlyContinue).selection) + 'UserLoggedIn' + $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID + #Increased download efficiency: only download the data we need for processing. Todo: Change this to load from table or dynamic source. + $MappingTable = [pscustomobject]@{ + 'UserLoggedIn' = 'Audit.AzureActiveDirectory' + 'Add member to role.' = 'Audit.AzureActiveDirectory' + 'Disable account.' = 'Audit.AzureActiveDirectory' + 'Update StsRefreshTokenValidFrom Timestamp.' = 'Audit.AzureActiveDirectory' + 'Enable account.' = 'Audit.AzureActiveDirectory' + 'Disable Strong Authentication.' = 'Audit.AzureActiveDirectory' + 'Reset user password.' = 'Audit.AzureActiveDirectory' + 'Add service principal.' = 'Audit.AzureActiveDirectory' + 'HostedIP' = 'Audit.AzureActiveDirectory' + 'badRepIP' = 'Audit.AzureActiveDirectory' + 'UserLoggedInFromUnknownLocation' = 'Audit.AzureActiveDirectory' + 'customfield' = 'AnyLog' + 'anyAlert' = 'AnyLog' + 'New-InboxRule' = 'Audit.Exchange' + 'Set-InboxRule' = 'Audit.Exchange' + } + #Compare $Operations to $MappingTable. If there is a match, we make a new variable called $LogsToDownload + #Example: $Operations = 'UserLoggedIn', 'Set-InboxRule' makes : $LogsToDownload = @('Audit.AzureActiveDirectory',Audit.Exchange) + $LogsToDownload = $Operations | Where-Object { $MappingTable.$_ } | ForEach-Object { $MappingTable.$_ } + Write-Host "Our operations: $Operations" + Write-Host "Logs to download: $LogsToDownload" + if ($ReceivedItem.ContentType -in $LogsToDownload -or 'AnyLog' -in $LogsToDownload) { + $Data = New-GraphPostRequest -type GET -uri "https://manage.office.com/api/v1.0/$($ReceivedItem.tenantId)/activity/feed/audit/$($ReceivedItem.contentid)" -tenantid $TenantFilter -scope 'https://manage.office.com/.default' + } else { + Write-Host "No data to download for $($ReceivedItem.ContentType)" + continue + } + Write-Host "Data found: $($data.count) items" + $DataToProcess = if ('anylog' -NotIn $LogsToDownload) { $Data | Where-Object -Property Operation -In $Operations } else { $Data } + Write-Host "Data to process found: $($DataToProcess.count) items" + foreach ($Item in $DataToProcess) { + Write-Host "Processing $($item.operation)" + + ## Push webhook data to table + $Entity = [PSCustomObject]@{ + PartitionKey = 'Webhook' + RowKey = [string](New-Guid).Guid + Type = 'AuditLog' + Data = [string]($Item | ConvertTo-Json -Depth 10) + CIPPURL = $CIPPURL + TenantFilter = $TenantFilter + FunctionName = 'PublicWebhookProcess' + } + Add-CIPPAzDataTableEntity @WebhookIncoming -Entity $Entity -Force + #Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url + } + } + } catch { + Write-Host "Webhook Failed: $($_.Exception.Message). Line number $($_.InvocationInfo.ScriptLineNumber)" + } + } + + $Body = 'Webhook Recieved' + $StatusCode = [HttpStatusCode]::OK + } + } else { + $Body = 'This webhook is not authorized.' + $StatusCode = [HttpStatusCode]::Forbidden + } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $Body + }) +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 new file mode 100644 index 000000000000..99a932d35e79 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 @@ -0,0 +1,17 @@ +function Push-PublicWebhookProcess { + param($Item) + + try { + if ($Item.Type -eq 'GraphSubscription') { + Invoke-CippGraphWebhookProcessing -Data ($Item.Data | ConvertFrom-Json) -CIPPID $Item.CIPPID -WebhookInfo ($Item.Webhookinfo | ConvertFrom-Json) + } elseif ($Item.Type -eq 'AuditLog') { + Invoke-CippWebhookProcessing -TenantFilter $Item.TenantFilter -Data ($Item.Data | ConvertFrom-Json) -CIPPPURL $Item.CIPPURL + } + $WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming + $Entity = $Item | Select-Object -Property RowKey, PartitionKey + Remove-AzDataTableEntity @WebhookIncoming -Entity $Entity + } catch { + Write-Host "Webhook Exception: $($_.Exception.Message)" + } + +} \ No newline at end of file diff --git a/PublicWebhooks/function.json b/PublicWebhooks/function.json deleted file mode 100644 index f59adac3eb84..000000000000 --- a/PublicWebhooks/function.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "Request", - "methods": ["get", "post"] - }, - { - "type": "http", - "direction": "out", - "name": "Response" - }, - { - "type": "queue", - "direction": "out", - "name": "QueueWebhook", - "queueName": "webhooksqueue" - } - ] -} diff --git a/PublicWebhooks/run.ps1 b/PublicWebhooks/run.ps1 deleted file mode 100644 index 2faa35804e05..000000000000 --- a/PublicWebhooks/run.ps1 +++ /dev/null @@ -1,37 +0,0 @@ -using namespace System.Net - -# Input bindings are passed in via param block. -param($Request, $TriggerMetadata) - -Set-Location (Get-Item $PSScriptRoot).Parent.FullName -$WebhookTable = Get-CIPPTable -TableName webhookTable -$Webhooks = Get-CIPPAzDataTableEntity @WebhookTable -Write-Host 'Received request' -Write-Host "CIPPID: $($request.Query.CIPPID)" -$url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 -Write-Host $url -if ($Request.Query.CIPPID -in $Webhooks.RowKey) { - Write-Host 'Found matching CIPPID' - if ($Webhooks.Resource -eq 'M365AuditLogs') { - Write-Host "Found M365AuditLogs - This is an old entry, we'll deny so Microsoft stops sending it." - $body = 'This webhook is not authorized, its an old entry.' - $StatusCode = [HttpStatusCode]::Forbidden - } - if ($Request.query.ValidationToken -or $Request.body.validationCode) { - Write-Host 'Validation token received' - $body = $request.query.ValidationToken - } else { - Push-OutputBinding -Name QueueWebhook -Value $Request - $Body = 'Webhook Recieved' - $StatusCode = [HttpStatusCode]::OK - } -} else { - $body = 'This webhook is not authorized.' - $StatusCode = [HttpStatusCode]::Forbidden -} - -# Associate values to output bindings by calling 'Push-OutputBinding'. -Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = $StatusCode - Body = $body - }) diff --git a/PublicWebhooksProcess/function.json b/PublicWebhooksProcess/function.json deleted file mode 100644 index d358059b9e50..000000000000 --- a/PublicWebhooksProcess/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "webhooksqueue" - } - ] -} diff --git a/PublicWebhooksProcess/run.ps1 b/PublicWebhooksProcess/run.ps1 deleted file mode 100644 index 50bbde87cfb2..000000000000 --- a/PublicWebhooksProcess/run.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -using namespace System.Net - -# Input bindings are passed in via param block. -param($QueueItem, $TriggerMetadata) - -$Request = $QueueItem - -$WebhookTable = Get-CIPPTable -TableName webhookTable -$Webhooks = Get-AzDataTableEntity @WebhookTable -Write-Host 'Received request' -Write-Host "CIPPID: $($request.Query.CIPPID)" -$url = ($request.headers.'x-ms-original-url').split('/API') | Select-Object -First 1 -Write-Host $url -if ($Request.query.CIPPID -in $Webhooks.RowKey) { - Write-Host 'Found matching CIPPID' - $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID - - if ($Request.Query.Type -eq 'GraphSubscription') { - # Graph Subscriptions - [pscustomobject]$ReceivedItem = $Request.Body.value - Invoke-CippGraphWebhookProcessing -Data $ReceivedItem -CIPPID $request.Query.CIPPID -WebhookInfo $Webhookinfo - - } else { - # Auditlog Subscriptions - try { - foreach ($ReceivedItem In ($Request.body)) { - $ReceivedItem = [pscustomobject]$ReceivedItem - Write-Host "Received Item: $($ReceivedItem | ConvertTo-Json -Depth 15 -Compress))" - $TenantFilter = (Get-Tenants | Where-Object -Property customerId -EQ $ReceivedItem.TenantId).defaultDomainName - Write-Host "Webhook TenantFilter: $TenantFilter" - $ConfigTable = get-cipptable -TableName 'SchedulerConfig' - $Alertconfig = Get-CIPPAzDataTableEntity @ConfigTable | Where-Object { $_.Tenant -eq $TenantFilter -or $_.Tenant -eq 'AllTenants' } - $Operations = ($AlertConfig.if | ConvertFrom-Json -ErrorAction SilentlyContinue).selection + 'UserLoggedIn' - $Webhookinfo = $Webhooks | Where-Object -Property RowKey -EQ $Request.query.CIPPID - #Increased download efficiency: only download the data we need for processing. Todo: Change this to load from table or dynamic source. - $MappingTable = [pscustomobject]@{ - 'UserLoggedIn' = 'Audit.AzureActiveDirectory' - 'Add member to role.' = 'Audit.AzureActiveDirectory' - 'Disable account.' = 'Audit.AzureActiveDirectory' - 'Update StsRefreshTokenValidFrom Timestamp.' = 'Audit.AzureActiveDirectory' - 'Enable account.' = 'Audit.AzureActiveDirectory' - 'Disable Strong Authentication.' = 'Audit.AzureActiveDirectory' - 'Reset user password.' = 'Audit.AzureActiveDirectory' - 'Add service principal.' = 'Audit.AzureActiveDirectory' - 'HostedIP' = 'Audit.AzureActiveDirectory' - 'badRepIP' = 'Audit.AzureActiveDirectory' - 'UserLoggedInFromUnknownLocation' = 'Audit.AzureActiveDirectory' - 'customfield' = 'AnyLog' - 'anyAlert' = 'AnyLog' - 'New-InboxRule' = 'Audit.Exchange' - 'Set-InboxRule' = 'Audit.Exchange' - } - #Compare $Operations to $MappingTable. If there is a match, we make a new variable called $LogsToDownload - #Example: $Operations = 'UserLoggedIn', 'Set-InboxRule' makes : $LogsToDownload = @('Audit.AzureActiveDirectory',Audit.Exchange) - $LogsToDownload = $Operations | Where-Object { $MappingTable.$_ } | ForEach-Object { $MappingTable.$_ } - Write-Host "Our operations: $Operations" - Write-Host "Logs to download: $LogsToDownload" - if ($ReceivedItem.ContentType -in $LogsToDownload -or 'AnyLog' -in $LogsToDownload) { - $Data = New-GraphPostRequest -type GET -uri "https://manage.office.com/api/v1.0/$($ReceivedItem.tenantId)/activity/feed/audit/$($ReceivedItem.contentid)" -tenantid $TenantFilter -scope 'https://manage.office.com/.default' - } else { - Write-Host "No data to download for $($ReceivedItem.ContentType)" - continue - } - Write-Host "Data found: $($data.count) items" - $DataToProcess = if ('anylog' -NotIn $LogsToDownload) { $Data | Where-Object -Property Operation -In $Operations } else { $Data } - Write-Host "Data to process found: $($DataToProcess.count) items" - foreach ($Item in $DataToProcess) { - Write-Host "Processing $($item.operation)" - Invoke-CippWebhookProcessing -TenantFilter $TenantFilter -Data $Item -CIPPPURL $url - } - } - } catch { - Write-Host "Webhook Failed: $($_.Exception.Message). Line number $($_.InvocationInfo.ScriptLineNumber)" - } - } - -} else { - Write-Host 'Unauthorised Webhook' -} diff --git a/Scheduler_GetWebhooks/function.json b/Scheduler_GetWebhooks/function.json new file mode 100644 index 000000000000..f30537d11b34 --- /dev/null +++ b/Scheduler_GetWebhooks/function.json @@ -0,0 +1,15 @@ +{ + "bindings": [ + { + "name": "Timer", + "schedule": "0 */15 * * * *", + "direction": "in", + "type": "timerTrigger" + }, + { + "name": "starter", + "type": "durableClient", + "direction": "in" + } + ] +} diff --git a/Scheduler_GetWebhooks/run.ps1 b/Scheduler_GetWebhooks/run.ps1 new file mode 100644 index 000000000000..9b890b878588 --- /dev/null +++ b/Scheduler_GetWebhooks/run.ps1 @@ -0,0 +1,13 @@ +param($Timer) + +$Table = Get-CIPPTable -TableName WebhookIncoming +$Webhooks = Get-CIPPAzDataTableEntity @Table +$InputObject = [PSCustomObject]@{ + OrchestratorName = 'WebhookOrchestrator' + Batch = @($Webhooks) + SkipLog = $true +} +#Write-Host ($InputObject | ConvertTo-Json) +$InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) +Write-Host "Started orchestration with ID = '$InstanceId'" +#$Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId \ No newline at end of file From 06e7763f3413edbc2eae6f55afa955830e998250 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 13 Mar 2024 16:41:36 -0400 Subject: [PATCH 16/29] Scheduler Durable --- ExecScheduledCommand/function.json | 10 --- ExecScheduledCommand/run.ps1 | 83 ------------------ .../Entrypoints/Push-ExecScheduledCommand.ps1 | 85 +++++++++++++++++++ Modules/CippEntrypoints/CippEntrypoints.psm1 | 4 +- Scheduler_UserTasks/function.json | 7 +- Scheduler_UserTasks/run.ps1 | 32 ++++--- 6 files changed, 112 insertions(+), 109 deletions(-) delete mode 100644 ExecScheduledCommand/function.json delete mode 100644 ExecScheduledCommand/run.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Push-ExecScheduledCommand.ps1 diff --git a/ExecScheduledCommand/function.json b/ExecScheduledCommand/function.json deleted file mode 100644 index e4c27b23b985..000000000000 --- a/ExecScheduledCommand/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "scheduledcommandprocessor" - } - ] -} diff --git a/ExecScheduledCommand/run.ps1 b/ExecScheduledCommand/run.ps1 deleted file mode 100644 index d0031f771c5c..000000000000 --- a/ExecScheduledCommand/run.ps1 +++ /dev/null @@ -1,83 +0,0 @@ -# Input bindings are passed in via param block. -param($QueueItem, $TriggerMetadata) - -$Table = Get-CippTable -tablename 'ScheduledTasks' -$task = $QueueItem.TaskInfo -$commandParameters = $QueueItem.Parameters - -$tenant = $QueueItem.Parameters['TenantFilter'] -Write-Host 'started task' -try { - try { - $results = & $QueueItem.command @commandParameters - } catch { - $results = "Task Failed: $($_.Exception.Message)" - - } - - Write-Host 'ran the command' - if ($results -is [String]) { - $results = @{ Results = $results } - } - if ($results -is [array] -and $results[0] -is [string]) { - $results = $results | Where-Object { $_ -is [string] } - $results = $results | ForEach-Object { @{ Results = $_ } } - } - - $results = $results | Select-Object * -ExcludeProperty RowKey, PartitionKey - - $StoredResults = $results | ConvertTo-Json -Compress -Depth 20 | Out-String - if ($StoredResults.Length -gt 64000 -or $task.Tenant -eq 'AllTenants') { - $StoredResults = @{ Results = 'The results for this query are too long to store in this table, or the query was meant for All Tenants. Please use the options to send the results to another target to be able to view the results. ' } | ConvertTo-Json -Compress - } -} catch { - $errorMessage = $_.Exception.Message - if ($task.Recurrence -gt 0) { $State = 'Failed - Planned' } else { $State = 'Failed' } - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$errorMessage" - TaskState = $State - } - Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to execute task $($task.Name): $errorMessage" -sev Error -} - - -$TableDesign = '' -$HTML = ($results | Select-Object * -ExcludeProperty RowKey, PartitionKey | ConvertTo-Html -Fragment) -replace '', "$TableDesign
" | Out-String -$title = "Scheduled Task $($task.Name) - $($task.ExpectedRunTime)" -Write-Host $title -switch -wildcard ($task.PostExecution) { - '*psa*' { Send-CIPPAlert -Type 'psa' -Title $title -HTMLContent $HTML } - '*email*' { Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML } - '*webhook*' { - $Webhook = [PSCustomObject]@{ - 'Tenant' = $tenant - 'TaskInfo' = $QueueItem.TaskInfo - 'Results' = $Results - } - Send-CIPPAlert -Type 'webhook' -Title $title -JSONContent $($Webhook | ConvertTo-Json -Depth 20) - } -} - -Write-Host 'ran the command' - -if ($task.Recurrence -le '0' -or $task.Recurrence -eq $null) { - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$StoredResults" - TaskState = 'Completed' - } -} else { - $nextRun = (Get-Date).AddDays($task.Recurrence) - $nextRunUnixTime = [int64]($nextRun - (Get-Date '1/1/1970')).TotalSeconds - Update-AzDataTableEntity @Table -Entity @{ - PartitionKey = $task.PartitionKey - RowKey = $task.RowKey - Results = "$StoredResults" - TaskState = 'Planned' - ScheduledTime = "$nextRunUnixTime" - } -} -Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Successfully executed task: $($task.name)" -sev Info \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ExecScheduledCommand.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ExecScheduledCommand.ps1 new file mode 100644 index 000000000000..8e53dcbd8bd4 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ExecScheduledCommand.ps1 @@ -0,0 +1,85 @@ +function Push-ExecScheduledCommand { + # Input bindings are passed in via param block. + param($Item) + + $Table = Get-CippTable -tablename 'ScheduledTasks' + $task = $Item.TaskInfo + $commandParameters = $Item.Parameters + + $tenant = $Item.Parameters['TenantFilter'] + Write-Host 'started task' + try { + try { + $results = & $Item.command @commandParameters + } catch { + $results = "Task Failed: $($_.Exception.Message)" + + } + + Write-Host 'ran the command' + if ($results -is [String]) { + $results = @{ Results = $results } + } + if ($results -is [array] -and $results[0] -is [string]) { + $results = $results | Where-Object { $_ -is [string] } + $results = $results | ForEach-Object { @{ Results = $_ } } + } + + $results = $results | Select-Object * -ExcludeProperty RowKey, PartitionKey + + $StoredResults = $results | ConvertTo-Json -Compress -Depth 20 | Out-String + if ($StoredResults.Length -gt 64000 -or $task.Tenant -eq 'AllTenants') { + $StoredResults = @{ Results = 'The results for this query are too long to store in this table, or the query was meant for All Tenants. Please use the options to send the results to another target to be able to view the results. ' } | ConvertTo-Json -Compress + } + } catch { + $errorMessage = $_.Exception.Message + if ($task.Recurrence -gt 0) { $State = 'Failed - Planned' } else { $State = 'Failed' } + Update-AzDataTableEntity @Table -Entity @{ + PartitionKey = $task.PartitionKey + RowKey = $task.RowKey + Results = "$errorMessage" + TaskState = $State + } + Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to execute task $($task.Name): $errorMessage" -sev Error + } + + + $TableDesign = '' + $HTML = ($results | Select-Object * -ExcludeProperty RowKey, PartitionKey | ConvertTo-Html -Fragment) -replace '
', "$TableDesign
" | Out-String + $title = "Scheduled Task $($task.Name) - $($task.ExpectedRunTime)" + Write-Host $title + switch -wildcard ($task.PostExecution) { + '*psa*' { Send-CIPPAlert -Type 'psa' -Title $title -HTMLContent $HTML } + '*email*' { Send-CIPPAlert -Type 'email' -Title $title -HTMLContent $HTML } + '*webhook*' { + $Webhook = [PSCustomObject]@{ + 'Tenant' = $tenant + 'TaskInfo' = $Item.TaskInfo + 'Results' = $Results + } + Send-CIPPAlert -Type 'webhook' -Title $title -JSONContent $($Webhook | ConvertTo-Json -Depth 20) + } + } + + Write-Host 'ran the command' + + if ($task.Recurrence -le '0' -or $task.Recurrence -eq $null) { + Update-AzDataTableEntity @Table -Entity @{ + PartitionKey = $task.PartitionKey + RowKey = $task.RowKey + Results = "$StoredResults" + TaskState = 'Completed' + } + } else { + $nextRun = (Get-Date).AddDays($task.Recurrence) + $nextRunUnixTime = [int64]($nextRun - (Get-Date '1/1/1970')).TotalSeconds + Update-AzDataTableEntity @Table -Entity @{ + PartitionKey = $task.PartitionKey + RowKey = $task.RowKey + Results = "$StoredResults" + TaskState = 'Planned' + ScheduledTime = "$nextRunUnixTime" + } + } + Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Successfully executed task: $($task.name)" -sev Info +} \ No newline at end of file diff --git a/Modules/CippEntrypoints/CippEntrypoints.psm1 b/Modules/CippEntrypoints/CippEntrypoints.psm1 index 749a0a41a674..15a25671cc9c 100644 --- a/Modules/CippEntrypoints/CippEntrypoints.psm1 +++ b/Modules/CippEntrypoints/CippEntrypoints.psm1 @@ -66,7 +66,7 @@ function Receive-CippOrchestrationTrigger { #Write-Host ($OrchestratorInput | ConvertTo-Json -Depth 10) $RetryOptions = New-DurableRetryOptions @DurableRetryOptions - if ($Context.IsReplaying -ne $true -and $Context.Input.SkipLog -ne $true) { + if ($Context.IsReplaying -ne $true -and $OrchestratorInput.SkipLog -ne $true) { Write-LogMessage -API $OrchestratorInput.OrchestratorName -tenant $OrchestratorInput.TenantFilter -message "Started $($OrchestratorInput.OrchestratorName)" -sev info } @@ -82,7 +82,7 @@ function Receive-CippOrchestrationTrigger { } } - if ($Context.IsReplaying -ne $true -and $Context.Input.SkipLog -ne $true) { + if ($Context.IsReplaying -ne $true -and $OrchestratorInput.SkipLog -ne $true) { Write-LogMessage -API $OrchestratorInput.OrchestratorName -tenant $tenant -message "Finished $($OrchestratorInput.OrchestratorName)" -sev Info } } catch { diff --git a/Scheduler_UserTasks/function.json b/Scheduler_UserTasks/function.json index de2a7380d759..f7af84092121 100644 --- a/Scheduler_UserTasks/function.json +++ b/Scheduler_UserTasks/function.json @@ -7,10 +7,9 @@ "type": "timerTrigger" }, { - "type": "queue", - "direction": "out", - "name": "Msg", - "queueName": "scheduledcommandprocessor" + "name": "starter", + "type": "durableClient", + "direction": "in" } ] } diff --git a/Scheduler_UserTasks/run.ps1 b/Scheduler_UserTasks/run.ps1 index 802d4624473f..8ad06065a2fa 100644 --- a/Scheduler_UserTasks/run.ps1 +++ b/Scheduler_UserTasks/run.ps1 @@ -3,12 +3,12 @@ param($Timer) $Table = Get-CippTable -tablename 'ScheduledTasks' $Filter = "TaskState eq 'Planned' or TaskState eq 'Failed - Planned'" $tasks = Get-CIPPAzDataTableEntity @Table -Filter $Filter -foreach ($task in $tasks) { +$Batch = foreach ($task in $tasks) { $tenant = $task.Tenant $currentUnixTime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds if ($currentUnixTime -ge $task.ScheduledTime) { try { - Update-AzDataTableEntity @Table -Entity @{ + $null = Update-AzDataTableEntity @Table -Entity @{ PartitionKey = $task.PartitionKey RowKey = $task.RowKey ExecutedTime = "$currentUnixTime" @@ -19,25 +19,27 @@ foreach ($task in $tasks) { if (!$task.Parameters) { $task.Parameters = @{} } $ScheduledCommand = [pscustomobject]@{ - Command = $task.Command - Parameters = $task.Parameters - TaskInfo = $task + Command = $task.Command + Parameters = $task.Parameters + TaskInfo = $task + FunctionName = 'ExecScheduledCommand' } if ($task.Tenant -eq 'AllTenants') { - $Results = Get-Tenants | ForEach-Object { + Get-Tenants | ForEach-Object { $ScheduledCommand.Parameters['TenantFilter'] = $_.defaultDomainName - Push-OutputBinding -Name Msg -Value $ScheduledCommand + $ScheduledCommand + #Push-OutputBinding -Name Msg -Value $ScheduledCommand } } else { $ScheduledCommand.Parameters['TenantFilter'] = $task.Tenant - $Results = Push-OutputBinding -Name Msg -Value $ScheduledCommand + $ScheduledCommand + #$Results = Push-OutputBinding -Name Msg -Value $ScheduledCommand } - } catch { $errorMessage = $_.Exception.Message - Update-AzDataTableEntity @Table -Entity @{ + $null = Update-AzDataTableEntity @Table -Entity @{ PartitionKey = $task.PartitionKey RowKey = $task.RowKey Results = "$errorMessage" @@ -47,4 +49,14 @@ foreach ($task in $tasks) { Write-LogMessage -API 'Scheduler_UserTasks' -tenant $tenant -message "Failed to execute task $($task.Name): $errorMessage" -sev Error } } +} +if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'UserTaskOrchestrator' + Batch = @($Batch) + SkipLog = $true + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started orchestration with ID = '$InstanceId'" } \ No newline at end of file From b7b29fae2fe946d744a4230f38d7b49fe3732042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Bentsen=20Kj=C3=A6rg=C3=A5rd=20=28KBK=29?= Date: Thu, 14 Mar 2024 11:54:24 +0100 Subject: [PATCH 17/29] Some error handling --- .../Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 | 6 ++++-- .../Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 | 3 +-- .../Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 index 07571db760aa..412cc1103f8a 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertApnCertExpiry.ps1 @@ -17,7 +17,9 @@ function Push-CIPPAlertApnCertExpiry { if ($Apn.expirationDateTime -lt (Get-Date).AddDays(30) -and $Apn.expirationDateTime -gt (Get-Date).AddDays(-7)) { Write-AlertMessage -tenant $($QueueItem.tenant) -message ('Intune: Apple Push Notification certificate for {0} is expiring on {1}' -f $Apn.appleIdentifier, $Apn.expirationDateTime) } - } catch {} + } catch { + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Failed to check APN certificate expiry for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" + } } $LastRun = @{ RowKey = 'ApnCertExpiry' @@ -25,6 +27,6 @@ function Push-CIPPAlertApnCertExpiry { } Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force } catch { - # Error handling + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Failed to check APN certificate expiry for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 index 82bcde9bb74f..46c13638e956 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertAppSecretExpiry.ps1 @@ -6,7 +6,6 @@ function Push-CIPPAlertAppSecretExpiry { $TriggerMetadata ) $LastRunTable = Get-CIPPTable -Table AlertLastRun - try { $Filter = "RowKey eq 'AppSecretExpiry' and PartitionKey eq '{0}'" -f $QueueItem.tenantid @@ -34,7 +33,7 @@ function Push-CIPPAlertAppSecretExpiry { Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force } } catch { - + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Failed to check App registration expiry for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" } } diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 index 804750e60705..e8f15d6bbcce 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-CIPPAlertDepTokenExpiry.ps1 @@ -27,6 +27,6 @@ function Push-CIPPAlertDepTokenExpiry { Add-CIPPAzDataTableEntity @LastRunTable -Entity $LastRun -Force } } catch { - # Error handling + Write-AlertMessage -tenant $($QueueItem.tenant) -message "Failed to check Apple Device Enrollment Program token expiry for $($QueueItem.tenant): $(Get-NormalizedError -message $_.Exception.message)" } } From d321b8de4926d43e38176aefc2efb77a0944d9a3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 10:04:45 -0400 Subject: [PATCH 18/29] CPV Durable Cleanup queue output bindings --- .../Entrypoints/Push-UpdatePermissionsQueue.ps1 | 17 +++++++++++++++++ Scheduler_GetQueue/function.json | 6 ------ Scheduler_Standards/function.json | 6 ------ UpdatePermissions/function.json | 7 +++---- UpdatePermissions/run.ps1 | 17 +++++++++++++---- UpdatePermissionsQueue/function.json | 10 ---------- UpdatePermissionsQueue/run.ps1 | 15 --------------- 7 files changed, 33 insertions(+), 45 deletions(-) create mode 100644 Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 delete mode 100644 UpdatePermissionsQueue/function.json delete mode 100644 UpdatePermissionsQueue/run.ps1 diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 new file mode 100644 index 000000000000..e1d72b14e867 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-UpdatePermissionsQueue.ps1 @@ -0,0 +1,17 @@ +function Push-UpdatePermissionsQueue { + # Input bindings are passed in via param block. + param($Item) + Write-Host "Applying permissions for $($Item.defaultDomainName)" + $Table = Get-CIPPTable -TableName cpvtenants + $CPVRows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Tenant -EQ $Item.customerId + if (!$CPVRows -or $ENV:ApplicationID -notin $CPVRows.applicationId) { + Write-LogMessage -tenant $Item.defaultDomainName -tenantId $Item.customerId -message 'A New tenant has been added, or a new CIPP-SAM Application is in use' -Sev 'Warn' -API 'NewTenant' + Write-Host 'Adding CPV permissions' + Set-CIPPCPVConsent -Tenantfilter $Item.defaultDomainName + } + + Add-CIPPApplicationPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.defaultDomainName + Add-CIPPDelegatedPermission -RequiredResourceAccess 'CippDefaults' -ApplicationId $ENV:ApplicationID -tenantfilter $Item.defaultDomainName + + Write-LogMessage -tenant $Item.defaultDomainName -tenantId $Item.customerId -message "Updated permissions for $($Item.defaultDomainName)" -Sev 'Info' -API 'UpdatePermissionsQueue' +} \ No newline at end of file diff --git a/Scheduler_GetQueue/function.json b/Scheduler_GetQueue/function.json index 122f86c71d70..56e4cf0cfda1 100644 --- a/Scheduler_GetQueue/function.json +++ b/Scheduler_GetQueue/function.json @@ -6,12 +6,6 @@ "direction": "in", "type": "timerTrigger" }, - { - "type": "queue", - "direction": "out", - "name": "QueueItem", - "queueName": "CIPPGenericQueue" - }, { "name": "starter", "type": "durableClient", diff --git a/Scheduler_Standards/function.json b/Scheduler_Standards/function.json index 81d53b9a1598..e071591357a0 100644 --- a/Scheduler_Standards/function.json +++ b/Scheduler_Standards/function.json @@ -6,12 +6,6 @@ "direction": "in", "type": "timerTrigger" }, - { - "type": "queue", - "direction": "out", - "name": "QueueItem", - "queueName": "CIPPGenericQueue" - }, { "name": "starter", "type": "durableClient", diff --git a/UpdatePermissions/function.json b/UpdatePermissions/function.json index a7fdc6ca8da8..7e97fe568d29 100644 --- a/UpdatePermissions/function.json +++ b/UpdatePermissions/function.json @@ -7,10 +7,9 @@ "schedule": "0 0 0 * * *" }, { - "type": "queue", - "direction": "out", - "name": "Msg", - "queueName": "cpvqueue" + "name": "starter", + "type": "durableClient", + "direction": "in" } ] } diff --git a/UpdatePermissions/run.ps1 b/UpdatePermissions/run.ps1 index 5707bb45734a..03d3c0e1cc41 100644 --- a/UpdatePermissions/run.ps1 +++ b/UpdatePermissions/run.ps1 @@ -1,7 +1,16 @@ # Input bindings are passed in via param block. param($Timer) -$Tenants = get-tenants -IncludeErrors | Where-Object { $_.customerId -ne $env:TenantId } -foreach ($Row in $Tenants) { - Push-OutputBinding -Name Msg -Value $row -} \ No newline at end of file +try { + $Tenants = Get-Tenants -IncludeErrors | Where-Object { $_.customerId -ne $env:TenantId } | ForEach-Object { $_ | Add-Member -NotePropertyName FunctionName -NotePropertyValue 'UpdatePermissionsQueue'; $_ } + + if (($Tenants | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'UpdatePermissionsOrchestrator' + Batch = @($Tenants) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" + } +} catch {} \ No newline at end of file diff --git a/UpdatePermissionsQueue/function.json b/UpdatePermissionsQueue/function.json deleted file mode 100644 index 7fc4f12cef56..000000000000 --- a/UpdatePermissionsQueue/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "cpvqueue" - } - ] -} diff --git a/UpdatePermissionsQueue/run.ps1 b/UpdatePermissionsQueue/run.ps1 deleted file mode 100644 index 9b147478e274..000000000000 --- a/UpdatePermissionsQueue/run.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -# Input bindings are passed in via param block. -param($QueueItem, $TriggerMetadata) -Write-Host "Applying permissions for $($QueueItem.defaultDomainName)" -$Table = Get-CIPPTable -TableName cpvtenants -$CPVRows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Tenant -EQ $QueueItem.customerId -if (!$CPVRows -or $ENV:ApplicationID -notin $CPVRows.applicationId) { - Write-LogMessage -tenant $queueitem.defaultDomainName -tenantId $queueitem.customerId -message "A New tenant has been added, or a new CIPP-SAM Application is in use" -Sev "Warn" -API "NewTenant" - Write-Host "Adding CPV permissions" - Set-CIPPCPVConsent -Tenantfilter $QueueItem.defaultDomainName -} - -Add-CIPPApplicationPermission -RequiredResourceAccess "CippDefaults" -ApplicationId $ENV:ApplicationID -tenantfilter $QueueItem.defaultDomainName -Add-CIPPDelegatedPermission -RequiredResourceAccess "CippDefaults" -ApplicationId $ENV:ApplicationID -tenantfilter $QueueItem.defaultDomainName - -Write-LogMessage -tenant $QueueItem.defaultDomainName -tenantId $queueitem.customerId -message "Updated permissions for $($QueueItem.defaultDomainName)" -Sev "Info" -API "UpdatePermissionsQueue" \ No newline at end of file From dbc431a84cf5c07552fd3eb5c5f0ebf9b55b36e9 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 11:34:13 -0400 Subject: [PATCH 19/29] GDAP Invite durable --- ExecGDAPInviteApproved_Timer/function.json | 7 +++---- ExecGDAPInviteQueue/function.json | 10 ---------- ExecGDAPInviteQueue/run.ps1 | 7 ------- .../Entrypoints/Push-ExecGDAPInviteQueue.ps1 | 9 +++++++++ .../CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 | 15 +++++++++++++-- 5 files changed, 25 insertions(+), 23 deletions(-) delete mode 100644 ExecGDAPInviteQueue/function.json delete mode 100644 ExecGDAPInviteQueue/run.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Push-ExecGDAPInviteQueue.ps1 diff --git a/ExecGDAPInviteApproved_Timer/function.json b/ExecGDAPInviteApproved_Timer/function.json index 32b454a2a015..f8904bbb0a7f 100644 --- a/ExecGDAPInviteApproved_Timer/function.json +++ b/ExecGDAPInviteApproved_Timer/function.json @@ -7,10 +7,9 @@ "schedule": "0 0 */3 * * *" }, { - "type": "queue", - "direction": "out", - "name": "gdapinvitequeue", - "queueName": "gdapinvitequeue" + "name": "starter", + "type": "durableClient", + "direction": "in" } ] } diff --git a/ExecGDAPInviteQueue/function.json b/ExecGDAPInviteQueue/function.json deleted file mode 100644 index e51e66299d6f..000000000000 --- a/ExecGDAPInviteQueue/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "gdapinvitequeue" - } - ] -} diff --git a/ExecGDAPInviteQueue/run.ps1 b/ExecGDAPInviteQueue/run.ps1 deleted file mode 100644 index 1b95a443bab0..000000000000 --- a/ExecGDAPInviteQueue/run.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -# Input bindings are passed in via param block. -param( $QueueItem, $TriggerMetadata) - -# Write out the queue message and metadata to the information log. -Write-Host "PowerShell queue trigger function processed work item: $($QueueItem.customer.displayName)" - -Set-CIPPGDAPInviteGroups -Relationship $QueueItem \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ExecGDAPInviteQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ExecGDAPInviteQueue.ps1 new file mode 100644 index 000000000000..833b498eb34e --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ExecGDAPInviteQueue.ps1 @@ -0,0 +1,9 @@ +function Push-ExecGDAPInviteQueue { + # Input bindings are passed in via param block. + param($Item) + + # Write out the queue message and metadata to the information log. + Write-Host "PowerShell queue trigger function processed work item: $($Item.customer.displayName)" + + Set-CIPPGDAPInviteGroups -Relationship $Item +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 index 407eccddc796..e2ae2dba708d 100644 --- a/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 +++ b/Modules/CIPPCore/Public/Set-CIPPGDAPInviteGroups.ps1 @@ -35,12 +35,23 @@ function Set-CIPPGDAPInviteGroups { if (($InviteList | Measure-Object).Count -gt 0) { $Activations = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/tenantRelationships/delegatedAdminRelationships?`$filter=status eq 'active'" - foreach ($Activation in $Activations) { + $Batch = foreach ($Activation in $Activations) { if ($InviteList.RowKey -contains $Activation.id) { Write-Host "Mapping groups for GDAP relationship: $($Activation.customer.displayName) - $($Activation.id)" - Push-OutputBinding -Name gdapinvitequeue -Value $Activation + $Activation | Add-Member -NotePropertyName FunctionName -NotePropertyValue 'ExecGDAPInviteQueue' + $Activation } } + if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'GDAPInviteOrchestrator' + Batch = @($Batch) + SkipLog = $true + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started GDAP Invite orchestration with ID = '$InstanceId'" + } } } } From 75fa35998f987c55457b2acaa2f4d3056c93115b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 11:52:50 -0400 Subject: [PATCH 20/29] Cleanup webhook entry in finally block --- .../Public/Entrypoints/Push-PublicWebhookProcess.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 index 99a932d35e79..4d321a825f4e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Push-PublicWebhookProcess.ps1 @@ -7,11 +7,11 @@ function Push-PublicWebhookProcess { } elseif ($Item.Type -eq 'AuditLog') { Invoke-CippWebhookProcessing -TenantFilter $Item.TenantFilter -Data ($Item.Data | ConvertFrom-Json) -CIPPPURL $Item.CIPPURL } - $WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming - $Entity = $Item | Select-Object -Property RowKey, PartitionKey - Remove-AzDataTableEntity @WebhookIncoming -Entity $Entity } catch { Write-Host "Webhook Exception: $($_.Exception.Message)" + } finally { + $WebhookIncoming = Get-CIPPTable -TableName WebhookIncoming + $Entity = $Item | Select-Object -Property RowKey, PartitionKey + Remove-AzDataTableEntity @WebhookIncoming -Entity $Entity } - } \ No newline at end of file From 4b583940162aced85facb4d321947e014be8c044 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 12:27:40 -0400 Subject: [PATCH 21/29] Licenses & Mailbox Rules durables Remove outer parallel loop --- ListLicensesAllTenants/function.json | 10 ---- ListLicensesAllTenants/run.ps1 | 28 --------- .../Entrypoints/Invoke-ListLicenses.ps1 | 18 +++++- .../Invoke-ListLicensesAllTenants.ps1 | 35 ----------- .../Entrypoints/Invoke-ListMailboxRules.ps1 | 24 +++++++- .../Invoke-ListMailboxRulesAllTenants.ps1 | 60 ------------------- .../Entrypoints/Push-ListLicensesQueue.ps1 | 22 +++++++ .../Push-ListMailboxRulesQueue.ps1 | 53 ++++++++++++++++ Z_CIPPHttpTrigger/function.json | 12 ---- 9 files changed, 113 insertions(+), 149 deletions(-) delete mode 100644 ListLicensesAllTenants/function.json delete mode 100644 ListLicensesAllTenants/run.ps1 delete mode 100644 Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicensesAllTenants.ps1 delete mode 100644 Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRulesAllTenants.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Push-ListLicensesQueue.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Push-ListMailboxRulesQueue.ps1 diff --git a/ListLicensesAllTenants/function.json b/ListLicensesAllTenants/function.json deleted file mode 100644 index eee973c7ede2..000000000000 --- a/ListLicensesAllTenants/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "licqueue" - } - ] -} diff --git a/ListLicensesAllTenants/run.ps1 b/ListLicensesAllTenants/run.ps1 deleted file mode 100644 index abc69465c094..000000000000 --- a/ListLicensesAllTenants/run.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -# Input bindings are passed in via param block. -param([string] $QueueItem, $TriggerMetadata) - -# Write out the queue message and metadata to the information log. -Write-Host "PowerShell queue trigger function processed work item: $QueueItem" - -$RawGraphRequest = Get-Tenants | ForEach-Object -Parallel { - $domainName = $_.defaultDomainName - Import-Module '.\Modules\AzBobbyTables' - Import-Module '.\Modules\CIPPCore' - try { - Write-Host "Processing $domainName" - Get-CIPPLicenseOverview -TenantFilter $domainName - } - catch { - [pscustomobject]@{ - Tenant = [string]$domainName - License = "Could not connect to client: $($_.Exception.Message)" - 'PartitionKey' = 'License' - 'RowKey' = "$($domainName)-$(New-Guid)" - } - } -} - -$Table = Get-CIPPTable -TableName cachelicenses -foreach ($GraphRequest in $RawGraphRequest) { - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null -} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 index 03a719a7f259..6870b020be6e 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicenses.ps1 @@ -18,7 +18,7 @@ Function Invoke-ListLicenses { # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter $RawGraphRequest = if ($TenantFilter -ne 'AllTenants') { - $GraphRequest = Get-CIPPLicenseOverview -TenantFilter $TenantFilter | ForEach-Object { + $GraphRequest = Get-CIPPLicenseOverview -TenantFilter $TenantFilter | ForEach-Object { $TermInfo = $_.TermInfo | ConvertFrom-Json -ErrorAction SilentlyContinue $_.TermInfo = $TermInfo $_ @@ -27,13 +27,25 @@ Function Invoke-ListLicenses { $Table = Get-CIPPTable -TableName cachelicenses $Rows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Timestamp -GT (Get-Date).AddHours(-1) if (!$Rows) { - Push-OutputBinding -Name LicenseQueue -Value (Get-Date).ToString() + #Push-OutputBinding -Name LicenseQueue -Value (Get-Date).ToString() $GraphRequest = [PSCustomObject]@{ Tenant = 'Loading data for all tenants. Please check back in 1 minute' License = 'Loading data for all tenants. Please check back in 1 minute' } + $Tenants = Get-Tenants -IncludeErrors | ForEach-Object { $_ | Add-Member -NotePropertyName FunctionName -NotePropertyValue 'ListLicensesQueue'; $_ } + + if (($Tenants | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'ListLicensesOrchestrator' + Batch = @($Tenants) + SkipLog = $true + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" + } } else { - $GraphRequest = $Rows | ForEach-Object { + $GraphRequest = $Rows | ForEach-Object { $TermInfo = $_.TermInfo | ConvertFrom-Json -ErrorAction SilentlyContinue $_.TermInfo = $TermInfo $_ diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicensesAllTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicensesAllTenants.ps1 deleted file mode 100644 index 7d6c25d9daa7..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListLicensesAllTenants.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -using namespace System.Net - -Function Invoke-ListLicensesAllTenants { - <# - .FUNCTIONALITY - Entrypoint - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - - $RawGraphRequest = Get-Tenants | ForEach-Object -Parallel { - $domainName = $_.defaultDomainName - - Import-Module '.\Modules\AzBobbyTables' - Import-Module '.\Modules\CIPPCore' - try { - Write-Host "Processing $domainName" - Get-CIPPLicenseOverview -TenantFilter $domainName - } catch { - [pscustomobject]@{ - Tenant = [string]$domainName - License = "Could not connect to client: $($_.Exception.Message)" - 'PartitionKey' = 'License' - 'RowKey' = "$($domainName)-$(New-Guid)" - } - } - } - - $Table = Get-CIPPTable -TableName cachelicenses - foreach ($GraphRequest in $RawGraphRequest) { - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null - } - -} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRules.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRules.ps1 index a4ca980ba7d3..d1de06beada8 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRules.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRules.ps1 @@ -19,14 +19,36 @@ Function Invoke-ListMailboxRules { $TenantFilter = $Request.Query.TenantFilter $Table = Get-CIPPTable -TableName cachembxrules + if ($TenantFilter -ne 'AllTenants') { + $Table.Filter = "Tenant eq '$TenantFilter'" + } $Rows = Get-CIPPAzDataTableEntity @Table | Where-Object -Property Timestamp -GT (Get-Date).Addhours(-1) if (!$Rows) { - Push-OutputBinding -Name mbxrulequeue -Value $TenantFilter + #Push-OutputBinding -Name mbxrulequeue -Value $TenantFilter $GraphRequest = [PSCustomObject]@{ Tenant = 'Loading data. Please check back in 1 minute' Licenses = 'Loading data. Please check back in 1 minute' } + $Batch = if ($TenantFilter -eq 'AllTenants') { + Get-Tenants -IncludeErrors | ForEach-Object { $_ | Add-Member -NotePropertyName FunctionName -NotePropertyValue 'ListMailboxRulesQueue'; $_ } + } else { + [PSCustomObject]@{ + defaultDomainName = $TenantFilter + FunctionName = 'ListMailboxRulesQueue' + } + } + if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'ListMailboxRulesOrchestrator' + Batch = @($Batch) + SkipLog = $true + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" + } + } else { if ($TenantFilter -ne 'AllTenants') { $Rows = $Rows | Where-Object -Property Tenant -EQ $TenantFilter diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRulesAllTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRulesAllTenants.ps1 deleted file mode 100644 index b3909976d4c9..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMailboxRulesAllTenants.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -using namespace System.Net - -Function Invoke-ListMailboxRulesAllTenants { - <# - .FUNCTIONALITY - Entrypoint - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - $Tenants = if ($QueueItem -ne 'AllTenants') { - [PSCustomObject]@{ - defaultDomainName = $QueueItem - } - } else { - Get-Tenants - } - $Tenants | ForEach-Object -Parallel { - $domainName = $_.defaultDomainName - Import-Module '.\Modules\CIPPcore' - Import-Module '.\Modules\AzBobbyTables' - - try { - - $Rules = New-ExoRequest -tenantid $domainName -cmdlet 'Get-Mailbox' | ForEach-Object -Parallel { - New-ExoRequest -Anchor $_.UserPrincipalName -tenantid $domainName -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $_.GUID } - } - foreach ($Rule in $Rules) { - $GraphRequest = @{ - Rules = [string]($Rule | ConvertTo-Json) - RowKey = [string](New-Guid).guid - Tenant = [string]$domainName - PartitionKey = 'mailboxrules' - } - $Table = Get-CIPPTable -TableName cachembxrules - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null - } - } catch { - $Rules = @{ - Name = "Could not connect to tenant $($_.Exception.message)" - } | ConvertTo-Json - $GraphRequest = @{ - Rules = [string]$Rules - RowKey = [string]$domainName - Tenant = [string]$domainName - - PartitionKey = 'mailboxrules' - } - $Table = Get-CIPPTable -TableName cachembxrules - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null - } - } - - - - $Table = Get-CIPPTable -TableName cachembxrules - Write-Host "$($GraphRequest.RowKey) - $($GraphRequest.tenant)" - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null - -} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ListLicensesQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ListLicensesQueue.ps1 new file mode 100644 index 000000000000..f587fa65fe39 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ListLicensesQueue.ps1 @@ -0,0 +1,22 @@ +function Push-ListLicensesQueue { + # Input bindings are passed in via param block. + param($Item) + + # Write out the queue message and metadata to the information log. + Write-Host "PowerShell queue trigger function processed work item: $($Item.defaultDomainName)" + + $domainName = $Item.defaultDomainName + $GraphRequest = try { + Write-Host "Processing $domainName" + Get-CIPPLicenseOverview -TenantFilter $domainName + } catch { + [pscustomobject]@{ + Tenant = [string]$domainName + License = "Could not connect to client: $($_.Exception.Message)" + 'PartitionKey' = 'License' + 'RowKey' = "$($domainName)-$((New-Guid).Guid)" + } + } + $Table = Get-CIPPTable -TableName cachelicenses + Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null +} \ No newline at end of file diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ListMailboxRulesQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ListMailboxRulesQueue.ps1 new file mode 100644 index 000000000000..5c7ba40b2f06 --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ListMailboxRulesQueue.ps1 @@ -0,0 +1,53 @@ +function Push-ListMailboxRulesQueue { + # Input bindings are passed in via param block. + param($Item) + + # Write out the queue message and metadata to the information log. + Write-Host "PowerShell queue trigger function processed work item: $($Item.defaultDomainName)" + + $domainName = $Item.defaultDomainName + + $Table = Get-CIPPTable -TableName cachembxrules + try { + $Rules = New-ExoRequest -tenantid $domainName -cmdlet 'Get-Mailbox' -Select 'userPrincipalName,GUID' | ForEach-Object -Parallel { + Import-Module CippCore + $MbxRules = New-ExoRequest -Anchor $_.UserPrincipalName -tenantid $using:domainName -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $_.GUID } + foreach ($Rule in $MbxRules) { + $Rule | Add-Member -NotePropertyName 'UserPrincipalName' -NotePropertyValue $_.userPrincipalName + $Rule + } + } + if (($Rules | Measure-Object).Count -gt 0) { + foreach ($Rule in $Rules) { + $GraphRequest = [PSCustomObject]@{ + Rules = [string]($Rule | ConvertTo-Json) + RowKey = [string](New-Guid).guid + Tenant = [string]$domainName + PartitionKey = 'mailboxrules' + } + + } + } else { + $Rules = @{ + Name = 'No rules found' + } | ConvertTo-Json + $GraphRequest = [PSCustomObject]@{ + Rules = [string]$Rules + RowKey = [string]$domainName + Tenant = [string]$domainName + PartitionKey = 'mailboxrules' + } + } + } catch { + $Rules = @{ + Name = "Could not connect to tenant $($_.Exception.message)" + } | ConvertTo-Json + $GraphRequest = [PSCustomObject]@{ + Rules = [string]$Rules + RowKey = [string]$domainName + Tenant = [string]$domainName + PartitionKey = 'mailboxrules' + } + } + Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null +} diff --git a/Z_CIPPHttpTrigger/function.json b/Z_CIPPHttpTrigger/function.json index 90c8dc9ea6af..815789ce62d6 100644 --- a/Z_CIPPHttpTrigger/function.json +++ b/Z_CIPPHttpTrigger/function.json @@ -27,18 +27,6 @@ "name": "Subscription", "queueName": "AlertSubscriptions" }, - { - "type": "queue", - "direction": "out", - "name": "LicenseQueue", - "queueName": "licqueue" - }, - { - "type": "queue", - "direction": "out", - "name": "mbxrulequeue", - "queueName": "mbxrulequeue" - }, { "type": "queue", "direction": "out", From 1b4d937f2bf8d1d2ef2e88198c8dc89799bacf3d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 13:16:57 -0400 Subject: [PATCH 22/29] MFA Report durable Cleanup functions and output bindings --- ListMFAUsersAllTenants/function.json | 10 --- ListMFAUsersAllTenants/run.ps1 | 57 ----------------- ListMailboxRulesAllTenants/function.json | 10 --- ListMailboxRulesAllTenants/run.ps1 | 61 ------------------ .../Entrypoints/Invoke-ListMFAUsers.ps1 | 19 +++++- .../Invoke-ListMFAUsersAllTenants.ps1 | 63 ------------------- .../Entrypoints/Push-ListMFAUsersQueue.ps1 | 50 +++++++++++++++ Z_CIPPHttpTrigger/function.json | 12 ---- 8 files changed, 67 insertions(+), 215 deletions(-) delete mode 100644 ListMFAUsersAllTenants/function.json delete mode 100644 ListMFAUsersAllTenants/run.ps1 delete mode 100644 ListMailboxRulesAllTenants/function.json delete mode 100644 ListMailboxRulesAllTenants/run.ps1 delete mode 100644 Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsersAllTenants.ps1 create mode 100644 Modules/CIPPCore/Public/Entrypoints/Push-ListMFAUsersQueue.ps1 diff --git a/ListMFAUsersAllTenants/function.json b/ListMFAUsersAllTenants/function.json deleted file mode 100644 index 7bb9da79d405..000000000000 --- a/ListMFAUsersAllTenants/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "mfaqueue" - } - ] -} diff --git a/ListMFAUsersAllTenants/run.ps1 b/ListMFAUsersAllTenants/run.ps1 deleted file mode 100644 index 8ab611e514fe..000000000000 --- a/ListMFAUsersAllTenants/run.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -# Input bindings are passed in via param block. -param([string] $QueueItem, $TriggerMetadata) - -# Write out the queue message and metadata to the information log. -Write-Host "PowerShell queue trigger function processed work item: $QueueItem" - - -Write-Information "Item: $QueueItem" -Write-Information ($TriggerMetadata | ConvertTo-Json) - -try { - Update-CippQueueEntry -RowKey $QueueItem -Status 'Running' - - $GraphRequest = Get-Tenants | ForEach-Object -Parallel { - $domainName = $_.defaultDomainName - Import-Module '.\modules\CippCore' - $Table = Get-CIPPTable -TableName cachemfa - Try { - $GraphRequest = Get-CIPPMFAState -TenantFilter $domainName -ErrorAction Stop - } - catch { - $GraphRequest = $null - } - if (!$GraphRequest) { - $GraphRequest = @{ - Tenant = [string]$tenantName - UPN = [string]$domainName - AccountEnabled = 'none' - PerUser = [string]'Could not connect to tenant' - MFARegistration = 'none' - CoveredByCA = [string]'Could not connect to tenant' - CoveredBySD = 'none' - RowKey = [string]"$domainName" - PartitionKey = 'users' - } - } - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null - } -} -catch { - $Table = Get-CIPPTable -TableName cachemfa - $GraphRequest = @{ - Tenant = [string]$tenantName - UPN = [string]$domainName - AccountEnabled = 'none' - PerUser = [string]'Could not connect to tenant' - MFARegistration = 'none' - CoveredByCA = [string]'Could not connect to tenant' - CoveredBySD = 'none' - RowKey = [string]"$domainName" - PartitionKey = 'users' - } - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null -} -finally { - Update-CippQueueEntry -RowKey $QueueItem -Status "Completed" -} diff --git a/ListMailboxRulesAllTenants/function.json b/ListMailboxRulesAllTenants/function.json deleted file mode 100644 index 776ec6ebfdcb..000000000000 --- a/ListMailboxRulesAllTenants/function.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "mbxrulequeue" - } - ] -} diff --git a/ListMailboxRulesAllTenants/run.ps1 b/ListMailboxRulesAllTenants/run.ps1 deleted file mode 100644 index 36b671ad11f6..000000000000 --- a/ListMailboxRulesAllTenants/run.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -# Input bindings are passed in via param block. -param([string] $QueueItem, $TriggerMetadata) - -# Write out the queue message and metadata to the information log. -Write-Host "PowerShell queue trigger function processed work item: $QueueItem" - -$Tenants = if ($QueueItem -ne 'AllTenants') { - [PSCustomObject]@{ - defaultDomainName = $QueueItem - } -} else { - Get-Tenants -} -$Tenants | ForEach-Object -Parallel { - $domainName = $_.defaultDomainName - Import-Module CippCore - Import-Module AzBobbyTables - $Table = Get-CIPPTable -TableName cachembxrules - try { - $Rules = New-ExoRequest -tenantid $domainName -cmdlet 'Get-Mailbox' -Select 'userPrincipalName,GUID' | ForEach-Object -Parallel { - Import-Module CippCore - $MbxRules = New-ExoRequest -Anchor $_.UserPrincipalName -tenantid $using:domainName -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $_.GUID } - foreach ($Rule in $MbxRules) { - $Rule | Add-Member -NotePropertyName 'UserPrincipalName' -NotePropertyValue $_.userPrincipalName - $Rule - } - } - if (($Rules | Measure-Object).Count -gt 0) { - foreach ($Rule in $Rules) { - $GraphRequest = [PSCustomObject]@{ - Rules = [string]($Rule | ConvertTo-Json) - RowKey = [string](New-Guid).guid - Tenant = [string]$domainName - PartitionKey = 'mailboxrules' - } - - } - } else { - $Rules = @{ - Name = 'No rules found' - } | ConvertTo-Json - $GraphRequest = [PSCustomObject]@{ - Rules = [string]$Rules - RowKey = [string]$domainName - Tenant = [string]$domainName - PartitionKey = 'mailboxrules' - } - } - } catch { - $Rules = @{ - Name = "Could not connect to tenant $($_.Exception.message)" - } | ConvertTo-Json - $GraphRequest = [PSCustomObject]@{ - Rules = [string]$Rules - RowKey = [string]$domainName - Tenant = [string]$domainName - PartitionKey = 'mailboxrules' - } - } - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null -} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 index 32314d2298d8..99209bfc9c8f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsers.ps1 @@ -24,9 +24,24 @@ Function Invoke-ListMFAUsers { if (!$Rows) { $Queue = New-CippQueueEntry -Name 'MFA Users - All Tenants' -Link '/identity/reports/mfa-report?customerId=AllTenants' Write-Information ($Queue | ConvertTo-Json) - Push-OutputBinding -Name mfaqueue -Value $Queue.RowKey + #Push-OutputBinding -Name mfaqueue -Value $Queue.RowKey $GraphRequest = [PSCustomObject]@{ - UPN = 'Loading data for all tenants. Please check back in 10 minutes' + UPN = 'Loading data for all tenants. Please check back in a few minutes' + } + $Batch = Get-Tenants -IncludeErrors | ForEach-Object { + $_ | Add-Member -NotePropertyName FunctionName -NotePropertyValue 'ListMFAUsersQueue' + $_ | Add-Member -NotePropertyName QueueId -NotePropertyValue $Queue.RowKey + $_ + } + if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'ListMFAUsersOrchestrator' + Batch = @($Batch) + SkipLog = $true + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" } } else { $GraphRequest = $Rows diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsersAllTenants.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsersAllTenants.ps1 deleted file mode 100644 index 8123a963e1ff..000000000000 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ListMFAUsersAllTenants.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -using namespace System.Net - -Function Invoke-ListMFAUsersAllTenants { - <# - .FUNCTIONALITY - Entrypoint - #> - [CmdletBinding()] - param($Request, $TriggerMetadata) - - - - Write-Information "Item: $QueueItem" - Write-Information ($TriggerMetadata | ConvertTo-Json) - - try { - Update-CippQueueEntry -RowKey $QueueItem -Status 'Running' - - $GraphRequest = Get-Tenants | ForEach-Object -Parallel { - $domainName = $_.defaultDomainName - Import-Module '.\modules\CippCore' - Import-Module '.\Modules\AzBobbyTables' - - $Table = Get-CIPPTable -TableName cachemfa - Try { - $GraphRequest = Get-CIPPMFAState -TenantFilter $domainName -ErrorAction Stop - } catch { - $GraphRequest = $null - } - if (!$GraphRequest) { - $GraphRequest = @{ - Tenant = [string]$tenantName - UPN = [string]$domainName - AccountEnabled = 'none' - PerUser = [string]'Could not connect to tenant' - MFARegistration = 'none' - CoveredByCA = [string]'Could not connect to tenant' - CoveredBySD = 'none' - RowKey = [string]"$domainName" - PartitionKey = 'users' - } - } - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null - } - } catch { - $Table = Get-CIPPTable -TableName cachemfa - $GraphRequest = @{ - Tenant = [string]$tenantName - UPN = [string]$domainName - AccountEnabled = 'none' - PerUser = [string]'Could not connect to tenant' - MFARegistration = 'none' - CoveredByCA = [string]'Could not connect to tenant' - CoveredBySD = 'none' - RowKey = [string]"$domainName" - PartitionKey = 'users' - } - Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null - } finally { - Update-CippQueueEntry -RowKey $QueueItem -Status 'Completed' - } - -} diff --git a/Modules/CIPPCore/Public/Entrypoints/Push-ListMFAUsersQueue.ps1 b/Modules/CIPPCore/Public/Entrypoints/Push-ListMFAUsersQueue.ps1 new file mode 100644 index 000000000000..89e9d1dbd3bd --- /dev/null +++ b/Modules/CIPPCore/Public/Entrypoints/Push-ListMFAUsersQueue.ps1 @@ -0,0 +1,50 @@ +function Push-ListMFAUsersQueue { + # Input bindings are passed in via param block. + param($Item) + + # Write out the queue message and metadata to the information log. + Write-Host "PowerShell queue trigger function processed work item: $($Item.defaultDomainName)" + + try { + Update-CippQueueEntry -RowKey $Item.QueueId -Status 'Running' -Name $Item.displayName + $domainName = $Item.defaultDomainName + $Table = Get-CIPPTable -TableName cachemfa + Try { + $GraphRequest = Get-CIPPMFAState -TenantFilter $domainName -ErrorAction Stop + } catch { + $GraphRequest = $null + } + if (!$GraphRequest) { + $GraphRequest = @{ + Tenant = [string]$domainName + UPN = [string]$domainName + AccountEnabled = 'none' + PerUser = [string]'Could not connect to tenant' + MFARegistration = 'none' + CoveredByCA = [string]'Could not connect to tenant' + CoveredBySD = 'none' + RowKey = [string]"$domainName" + PartitionKey = 'users' + } + } + Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null + + } catch { + $Table = Get-CIPPTable -TableName cachemfa + $GraphRequest = @{ + Tenant = [string]$domainName + UPN = [string]$domainName + AccountEnabled = 'none' + PerUser = [string]'Could not connect to tenant' + MFARegistration = 'none' + CoveredByCA = [string]'Could not connect to tenant' + CoveredBySD = 'none' + RowKey = [string]"$domainName" + PartitionKey = 'users' + } + Add-CIPPAzDataTableEntity @Table -Entity $GraphRequest -Force | Out-Null + } finally { + Update-CippQueueEntry -RowKey $QueueItem -Status 'Completed' + } + +} \ No newline at end of file diff --git a/Z_CIPPHttpTrigger/function.json b/Z_CIPPHttpTrigger/function.json index 815789ce62d6..f5a94dad93d7 100644 --- a/Z_CIPPHttpTrigger/function.json +++ b/Z_CIPPHttpTrigger/function.json @@ -27,24 +27,12 @@ "name": "Subscription", "queueName": "AlertSubscriptions" }, - { - "type": "queue", - "direction": "out", - "name": "mfaqueue", - "queueName": "mfaqueue" - }, { "type": "queue", "direction": "out", "name": "mailboxstats", "queueName": "generalAllTenantQueue" }, - { - "type": "queue", - "direction": "out", - "name": "listusers", - "queueName": "generalAllTenantQueue" - }, { "type": "queue", "direction": "out", From 850bfdbc0198adaff3dd5632dfc9fa69cd941e58 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 13:21:41 -0400 Subject: [PATCH 23/29] cleanup bindings --- Z_CIPPHttpTrigger/function.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Z_CIPPHttpTrigger/function.json b/Z_CIPPHttpTrigger/function.json index f5a94dad93d7..a77f42a0ea97 100644 --- a/Z_CIPPHttpTrigger/function.json +++ b/Z_CIPPHttpTrigger/function.json @@ -51,12 +51,6 @@ "name": "alertqueue", "queueName": "alertqueue" }, - { - "type": "queue", - "direction": "out", - "name": "gdapinvitequeue", - "queueName": "gdapinvitequeue" - }, { "type": "queue", "direction": "out", @@ -75,12 +69,6 @@ "name": "offboardingmailbox", "queueName": "offboardingmailbox" }, - { - "type": "queue", - "direction": "out", - "name": "QueueWebhook", - "queueName": "webhooksqueue" - }, { "name": "starter", "type": "durableClient", From d3cdbeb6ae63cb53f9d5706eb032ecc7762ccb8d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 14:00:28 -0400 Subject: [PATCH 24/29] NinjaOne Durable --- ExecExtensionNinjaOneQueue/function.json | 16 --- ExecExtensionNinjaOneQueue/run.ps1 | 13 -- .../Invoke-ExecExtensionMapping.ps1 | 116 ++++++++++-------- .../Entrypoints/Invoke-ExecExtensionSync.ps1 | 42 +++++-- .../Invoke-NinjaOneExtensionScheduler.ps1 | 109 ++++++++++++++++ .../NinjaOne/Invoke-NinjaOneOrgMapping.ps1 | 47 ++++--- .../NinjaOne/Invoke-NinjaOneSync.ps1 | 25 +++- .../NinjaOne/Push-NinjaOneQueue.ps1 | 15 +++ Scheduler_Extensions/function.json | 5 + Scheduler_Extensions/run.ps1 | 82 +------------ 10 files changed, 272 insertions(+), 198 deletions(-) delete mode 100644 ExecExtensionNinjaOneQueue/function.json delete mode 100644 ExecExtensionNinjaOneQueue/run.ps1 create mode 100644 Modules/CippExtensions/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 create mode 100644 Modules/CippExtensions/NinjaOne/Push-NinjaOneQueue.ps1 diff --git a/ExecExtensionNinjaOneQueue/function.json b/ExecExtensionNinjaOneQueue/function.json deleted file mode 100644 index 058a42bd6db9..000000000000 --- a/ExecExtensionNinjaOneQueue/function.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "bindings": [ - { - "name": "QueueItem", - "type": "queueTrigger", - "direction": "in", - "queueName": "NinjaOneQueue" - }, - { - "type": "queue", - "direction": "out", - "name": "NinjaProcess", - "queueName": "NinjaOneQueue" - } - ] -} diff --git a/ExecExtensionNinjaOneQueue/run.ps1 b/ExecExtensionNinjaOneQueue/run.ps1 deleted file mode 100644 index 21720a79b6a5..000000000000 --- a/ExecExtensionNinjaOneQueue/run.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -# Input bindings are passed in via param block. -param($QueueItem, $TriggerMetadata) - -# Write out the queue message and metadata to the information log. -Write-Host "PowerShell NinjaOne queue trigger function processed work item: $($QueueItem.NinjaAction)" - - -Switch ($QueueItem.NinjaAction) { - 'StartAutoMapping' { Invoke-NinjaOneOrgMapping } - 'AutoMapTenant' { Invoke-NinjaOneOrgMappingTenant -QueueItem $QueueItem } - 'SyncTenant' { Invoke-NinjaOneTenantSync -QueueItem $QueueItem } - 'SyncTenants' {Invoke-NinjaOneSync} -} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionMapping.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionMapping.ps1 index 1e4c8c8677e3..f35e5a38c94f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionMapping.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionMapping.ps1 @@ -1,6 +1,6 @@ - using namespace System.Net +using namespace System.Net - Function Invoke-ExecExtensionMapping { +Function Invoke-ExecExtensionMapping { <# .FUNCTIONALITY Entrypoint @@ -8,74 +8,82 @@ [CmdletBinding()] param($Request, $TriggerMetadata) - $APIName = $TriggerMetadata.FunctionName -Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' + $APIName = $TriggerMetadata.FunctionName + Write-LogMessage -user $request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug' -# Write to the Azure Functions log stream. -Write-Host 'PowerShell HTTP trigger function processed a request.' -$Table = Get-CIPPTable -TableName CippMapping + # Write to the Azure Functions log stream. + Write-Host 'PowerShell HTTP trigger function processed a request.' + $Table = Get-CIPPTable -TableName CippMapping -if ($Request.Query.List) { - switch ($Request.Query.List) { - 'Halo' { - $body = Get-HaloMapping -CIPPMapping $Table - } - - 'NinjaOrgs' { - $Body = Get-NinjaOneOrgMapping -CIPPMapping $Table - } - - 'NinjaFields' { - $Body = Get-NinjaOneFieldMapping -CIPPMapping $Table - - } - } -} - -try { - if ($Request.Query.AddMapping) { - switch ($Request.Query.AddMapping) { + if ($Request.Query.List) { + switch ($Request.Query.List) { 'Halo' { - $body = Set-HaloMapping -CIPPMapping $Table -APIName $APIName -Request $Request + $body = Get-HaloMapping -CIPPMapping $Table } - + 'NinjaOrgs' { - $Body = Set-NinjaOneOrgMapping -CIPPMapping $Table -APIName $APIName -Request $Request + $Body = Get-NinjaOneOrgMapping -CIPPMapping $Table } - + 'NinjaFields' { - $Body = Set-NinjaOneFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -TriggerMetadata $TriggerMetadata + $Body = Get-NinjaOneFieldMapping -CIPPMapping $Table + } } } -} -catch { - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "mapping API failed. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed. $($_.Exception.Message)" } -} -try { - if ($Request.Query.AutoMapping) { - switch ($Request.Query.AutoMapping) { - 'NinjaOrgs' { - Push-OutputBinding -Name NinjaProcess -Value @{'NinjaAction' = 'StartAutoMapping' } - $Body = [pscustomobject]@{'Results' = 'Automapping Request has been queued. Exact name matches will appear first and matches on device names and serials will take longer. Please check the CIPP Logbook and refresh the page once complete.' } - } + try { + if ($Request.Query.AddMapping) { + switch ($Request.Query.AddMapping) { + 'Halo' { + $body = Set-HaloMapping -CIPPMapping $Table -APIName $APIName -Request $Request + } + 'NinjaOrgs' { + $Body = Set-NinjaOneOrgMapping -CIPPMapping $Table -APIName $APIName -Request $Request + } + 'NinjaFields' { + $Body = Set-NinjaOneFieldMapping -CIPPMapping $Table -APIName $APIName -Request $Request -TriggerMetadata $TriggerMetadata + } + } } + } catch { + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "mapping API failed. $($_.Exception.Message)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = "Failed. $($_.Exception.Message)" } } -} -catch { - Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "mapping API failed. $($_.Exception.Message)" -Sev 'Error' - $body = [pscustomobject]@{'Results' = "Failed. $($_.Exception.Message)" } -} -# Associate values to output bindings by calling 'Push-OutputBinding'. -Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $body - }) + try { + if ($Request.Query.AutoMapping) { + switch ($Request.Query.AutoMapping) { + 'NinjaOrgs' { + #Push-OutputBinding -Name NinjaProcess -Value @{'NinjaAction' = 'StartAutoMapping' } + $Batch = [PSCustomObject]@{ + 'NinjaAction' = 'StartAutoMapping' + 'FunctionName' = 'NinjaOneQueue' + } + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" + $Body = [pscustomobject]@{'Results' = 'Automapping Request has been queued. Exact name matches will appear first and matches on device names and serials will take longer. Please check the CIPP Logbook and refresh the page once complete.' } + } + } + } + } catch { + Write-LogMessage -API $APINAME -user $request.headers.'x-ms-client-principal' -message "mapping API failed. $($_.Exception.Message)" -Sev 'Error' + $body = [pscustomobject]@{'Results' = "Failed. $($_.Exception.Message)" } } + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $body + }) + +} diff --git a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionSync.ps1 b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionSync.ps1 index effb15af199d..57e44b3f946f 100644 --- a/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionSync.ps1 +++ b/Modules/CIPPCore/Public/Entrypoints/Invoke-ExecExtensionSync.ps1 @@ -42,28 +42,50 @@ Function Invoke-ExecExtensionSync { $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } if ($Request.Query.TenantID) { - $Tenant = $TenantsToProcess | Where-Object {$_.RowKey -eq $Request.Query.TenantID} - if (($Tenant | Measure-Object).count -eq 1){ - Push-OutputBinding -Name NinjaProcess -Value @{ + $Tenant = $TenantsToProcess | Where-Object { $_.RowKey -eq $Request.Query.TenantID } + if (($Tenant | Measure-Object).count -eq 1) { + <#Push-OutputBinding -Name NinjaProcess -Value @{ 'NinjaAction' = 'SyncTenant' 'MappedTenant' = $Tenant + }#> + $Batch = [PSCustomObject]@{ + 'NinjaAction' = 'SyncTenant' + 'MappedTenant' = $Tenant + 'FunctionName' = 'NinjaOneQueue' + } + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" + $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queued for $($Tenant.NinjaOneName)" } } else { - $Results = [pscustomobject]@{'Results' = "Tenant was not found." } - } - + $Results = [pscustomobject]@{'Results' = 'Tenant was not found.' } + } + } else { - - Push-OutputBinding -Name NinjaProcess -Value @{ + <#Push-OutputBinding -Name NinjaProcess -Value @{ 'NinjaAction' = 'SyncTenants' + }#> + $Batch = [PSCustomObject]@{ + 'NinjaAction' = 'SyncTenants' + 'FunctionName' = 'NinjaOneQueue' } - + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" $Results = [pscustomobject]@{'Results' = "NinjaOne Synchronization Queuing $(($TenantsToProcess | Measure-Object).count) Tenants" } } - + } catch { $Results = [pscustomobject]@{'Results' = "Could not start NinjaOne Sync: $($_.Exception.Message)" } Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message "Could not start NinjaOne Sync $($_.Exception.Message)" -sev Error diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 new file mode 100644 index 000000000000..02ac73202eb6 --- /dev/null +++ b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneExtensionScheduler.ps1 @@ -0,0 +1,109 @@ +function Invoke-NinjaOneExtensionScheduler { + $Table = Get-CIPPTable -TableName NinjaOneSettings + $Settings = (Get-AzDataTableEntity @Table) + $TimeSetting = ($Settings | Where-Object { $_.RowKey -eq 'NinjaSyncTime' }).SettingValue + + + if (($TimeSetting | Measure-Object).count -ne 1) { + [int]$TimeSetting = Get-Random -Minimum 1 -Maximum 95 + $AddObject = @{ + PartitionKey = 'NinjaConfig' + RowKey = 'NinjaSyncTime' + 'SettingValue' = $TimeSetting + } + Add-AzDataTableEntity @Table -Entity $AddObject -Force + } + + Write-Host "Ninja Time Setting: $TimeSetting" + + $LastRunTime = Get-Date(($Settings | Where-Object { $_.RowKey -eq 'NinjaLastRunTime' }).SettingValue) + + Write-Host "Last Run: $LastRunTime" + + $CurrentTime = Get-Date + $CurrentInterval = ($CurrentTime.Hour * 4) + [math]::Floor($CurrentTime.Minute / 15) + + Write-Host "Current Interval: $CurrentInterval" + + $CIPPMapping = Get-CIPPTable -TableName CippMapping + $Filter = "PartitionKey eq 'NinjaOrgsMapping'" + $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } + + if ($Null -eq $LastRunTime -or $LastRunTime -le (Get-Date).addhours(-25) -or $TimeSetting -eq $CurrentInterval) { + Write-Host 'Executing' + $Batch = foreach ($Tenant in $TenantsToProcess | Sort-Object lastEndTime) { + <#Push-OutputBinding -Name NinjaProcess -Value @{ + 'NinjaAction' = 'SyncTenant' + 'MappedTenant' = $Tenant + } + Start-Sleep -Seconds 1#> + [PSCustomObject]@{ + 'NinjaAction' = 'SyncTenant' + 'MappedTenant' = $Tenant + 'FunctionName' = 'NinjaOneQueue' + } + } + if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" + } + + $AddObject = @{ + PartitionKey = 'NinjaConfig' + RowKey = 'NinjaLastRunTime' + 'SettingValue' = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffK') + } + Add-AzDataTableEntity @Table -Entity $AddObject -Force + + Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "NinjaOne Daily Synchronization Queued for $(($TenantsToProcess | Measure-Object).count) Tenants" -Sev 'Info' + + } else { + if ($LastRunTime -lt (Get-Date).AddMinutes(-90)) { + $TenantsToProcess | ForEach-Object { + if ($Null -ne $_.lastEndTime -and $_.lastEndTime -ne '') { + $_.lastEndTime = (Get-Date($_.lastEndTime)) + } else { + $_ | Add-Member -NotePropertyName lastEndTime -NotePropertyValue $Null -Force + } + + if ($Null -ne $_.lastStartTime -and $_.lastStartTime -ne '') { + $_.lastStartTime = (Get-Date($_.lastStartTime)) + } else { + $_ | Add-Member -NotePropertyName lastStartTime -NotePropertyValue $Null -Force + } + } + $CatchupTenants = $TenantsToProcess | Where-Object { (((($_.lastEndTime -eq $Null) -or ($_.lastStartTime -gt $_.lastEndTime)) -and ($_.lastStartTime -lt (Get-Date).AddMinutes(-30)))) -or ($_.lastStartTime -lt $LastRunTime) } + $Batch = foreach ($Tenant in $CatchupTenants) { + #Push-OutputBinding -Name NinjaProcess -Value @{ + # 'NinjaAction' = 'SyncTenant' + # 'MappedTenant' = $Tenant + #} + [PSCustomObject]@{ + NinjaAction = 'SyncTenant' + MappedTenant = $Tenant + FunctionName = 'NinjaOneQueue' + } + } + if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" + } + + if (($CatchupTenants | Measure-Object).count -gt 0) { + Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "NinjaOne Synchronization Catchup Queued for $(($CatchupTenants | Measure-Object).count) Tenants" -Sev 'Info' + } + + } + + } +} \ No newline at end of file diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 index 0590894a8fec..f351d43ee039 100644 --- a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 +++ b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneOrgMapping.ps1 @@ -3,30 +3,30 @@ function Invoke-NinjaOneOrgMapping { [System.Collections.Generic.List[PSCustomObject]]$MatchedM365Tenants = @() [System.Collections.Generic.List[PSCustomObject]]$MatchedNinjaOrgs = @() - $ExcludeSerials = @("0", "SystemSerialNumber", "To Be Filled By O.E.M.", "System Serial Number", "0123456789", "123456789", "............") + $ExcludeSerials = @('0', 'SystemSerialNumber', 'To Be Filled By O.E.M.', 'System Serial Number', '0123456789', '123456789', '............') $CIPPMapping = Get-CIPPTable -TableName CippMapping - + #Get available mappings $Mappings = [pscustomobject]@{} $Filter = "PartitionKey eq 'NinjaOrgsMapping'" Get-AzDataTableEntity @CIPPMapping -Filter $Filter | ForEach-Object { $Mappings | Add-Member -NotePropertyName $_.RowKey -NotePropertyValue @{ label = "$($_.NinjaOneName)"; value = "$($_.NinjaOne)" } } - + #Get Available Tenants $Tenants = Get-Tenants #Get available Ninja clients $Table = Get-CIPPTable -TableName Extensionsconfig $Configuration = ((Get-AzDataTableEntity @Table).config | ConvertFrom-Json).NinjaOne - + $Token = Get-NinjaOneToken -configuration $Configuration - + # Fetch Ninja Orgs $After = 0 $PageSize = 1000 $NinjaOrgs = do { - $Result = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/organizations?pageSize=$PageSize&after=$After" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 + $Result = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/organizations?pageSize=$PageSize&after=$After" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 $Result $ResultCount = ($Result.id | Measure-Object -Maximum) $After = $ResultCount.maximum @@ -51,11 +51,11 @@ function Invoke-NinjaOneOrgMapping { $After = 0 $PageSize = 1000 $NinjaDevicesRaw = do { - $Result = (Invoke-WebRequest -uri "https://$($Configuration.Instance)/api/v2/devices-detailed?pageSize=$PageSize&after=$After" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -depth 100 + $Result = (Invoke-WebRequest -Uri "https://$($Configuration.Instance)/api/v2/devices-detailed?pageSize=$PageSize&after=$After" -Method GET -Headers @{Authorization = "Bearer $($token.access_token)" } -ContentType 'application/json').content | ConvertFrom-Json -Depth 100 $Result $ResultCount = ($Result.id | Measure-Object -Maximum) $After = $ResultCount.maximum - + } while ($ResultCount.count -eq $PageSize) @@ -71,12 +71,12 @@ function Invoke-NinjaOneOrgMapping { } # Remove any devices with duplicate serials - $ParsedNinjaDevices = $NinjaDevices | Where-Object { $_.Serial -in (($NinjaDevices | Group-Object Serial | where-object { $_.count -eq 1 }).name) } + $ParsedNinjaDevices = $NinjaDevices | Where-Object { $_.Serial -in (($NinjaDevices | Group-Object Serial | Where-Object { $_.count -eq 1 }).name) } # First lets match on Org names foreach ($Tenant in $Tenants | Where-Object { $_.customerId -notin $MatchedM365Tenants.customerId }) { - $MatchedOrg = $NinjaOrgs | where-object { $_.name -eq $Tenant.displayName } + $MatchedOrg = $NinjaOrgs | Where-Object { $_.name -eq $Tenant.displayName } if (($MatchedOrg | Measure-Object).count -eq 1) { $MatchedM365Tenants.add($Tenant) $MatchedNinjaOrgs.add($MatchedOrg) @@ -87,23 +87,34 @@ function Invoke-NinjaOneOrgMapping { 'NinjaOneName' = "$($MatchedOrg.name)" } Add-AzDataTableEntity @CIPPMapping -Entity $AddObject -Force - Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Organization name match for $($Tenant.customerId). to $($($MatchedOrg.name))" -Sev 'Info' + Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "Added mapping from Organization name match for $($Tenant.customerId). to $($($MatchedOrg.name))" -Sev 'Info' } } # Now Let match on remaining Tenants - Foreach ($Tenant in $Tenants | Where-Object { $_.customerId -notin $MatchedM365Tenants.customerId }) { - - Push-OutputBinding -Name NinjaProcess -Value @{ + $Batch = Foreach ($Tenant in $Tenants | Where-Object { $_.customerId -notin $MatchedM365Tenants.customerId }) { + <#Push-OutputBinding -Name NinjaProcess -Value @{ + 'NinjaAction' = 'AutoMapTenant' + 'M365Tenant' = $Tenant + 'NinjaOrgs' = $NinjaOrgs | Where-Object { $_.id -notin $MatchedNinjaOrgs } + 'NinjaDevices' = $ParsedNinjaDevices + }#> + [PSCustomObject]@{ 'NinjaAction' = 'AutoMapTenant' 'M365Tenant' = $Tenant 'NinjaOrgs' = $NinjaOrgs | Where-Object { $_.id -notin $MatchedNinjaOrgs } 'NinjaDevices' = $ParsedNinjaDevices + 'FunctionName' = 'NinjaOneQueue' } - - Start-Sleep -Seconds 1 - + } + if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" } } - \ No newline at end of file diff --git a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneSync.ps1 b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneSync.ps1 index a168d291575b..df7d6f67111a 100644 --- a/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneSync.ps1 +++ b/Modules/CippExtensions/NinjaOne/Invoke-NinjaOneSync.ps1 @@ -7,12 +7,26 @@ function Invoke-NinjaOneSync { $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } - foreach ($Tenant in $TenantsToProcess) { - Push-OutputBinding -Name NinjaProcess -Value @{ + $Batch = foreach ($Tenant in $TenantsToProcess) { + <#Push-OutputBinding -Name NinjaProcess -Value @{ 'NinjaAction' = 'SyncTenant' 'MappedTenant' = $Tenant } - Start-Sleep -Seconds 1 + Start-Sleep -Seconds 1#> + [PSCustomObject]@{ + 'NinjaAction' = 'SyncTenant' + 'MappedTenant' = $Tenant + 'FunctionName' = 'NinjaOneQueue' + } + } + if (($Batch | Measure-Object).Count -gt 0) { + $InputObject = [PSCustomObject]@{ + OrchestratorName = 'NinjaOneOrchestrator' + Batch = @($Batch) + } + #Write-Host ($InputObject | ConvertTo-Json) + $InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5) + Write-Host "Started permissions orchestration with ID = '$InstanceId'" } $AddObject = @{ @@ -23,10 +37,9 @@ function Invoke-NinjaOneSync { Add-AzDataTableEntity @Table -Entity $AddObject -Force - Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "NinjaOne Synchronization Queued for $(($TenantsToProcess | Measure-Object).count) Tenants" -Sev 'Info' + Write-LogMessage -API 'NinjaOneAutoMap_Queue' -user 'CIPP' -message "NinjaOne Synchronization Queued for $(($TenantsToProcess | Measure-Object).count) Tenants" -Sev 'Info' } catch { Write-LogMessage -API 'Scheduler_Billing' -tenant 'none' -message "Could not start NinjaOne Sync $($_.Exception.Message)" -sev Error } - + } - \ No newline at end of file diff --git a/Modules/CippExtensions/NinjaOne/Push-NinjaOneQueue.ps1 b/Modules/CippExtensions/NinjaOne/Push-NinjaOneQueue.ps1 new file mode 100644 index 000000000000..09fe668a97b4 --- /dev/null +++ b/Modules/CippExtensions/NinjaOne/Push-NinjaOneQueue.ps1 @@ -0,0 +1,15 @@ +function Push-NinjaOneQueue { + # Input bindings are passed in via param block. + param($Item) + + # Write out the queue message and metadata to the information log. + Write-Host "PowerShell NinjaOne queue trigger function processed work item: $($Item.NinjaAction)" + + Switch ($Item.NinjaAction) { + 'StartAutoMapping' { Invoke-NinjaOneOrgMapping } + 'AutoMapTenant' { Invoke-NinjaOneOrgMappingTenant -QueueItem $Item } + 'SyncTenant' { Invoke-NinjaOneTenantSync -QueueItem $Item } + 'SyncTenants' { Invoke-NinjaOneSync } + } + +} \ No newline at end of file diff --git a/Scheduler_Extensions/function.json b/Scheduler_Extensions/function.json index f3e5317f409a..7474f0f13334 100644 --- a/Scheduler_Extensions/function.json +++ b/Scheduler_Extensions/function.json @@ -11,6 +11,11 @@ "direction": "out", "name": "NinjaProcess", "queueName": "NinjaOneQueue" + }, + { + "name": "starter", + "type": "durableClient", + "direction": "in" } ] } diff --git a/Scheduler_Extensions/run.ps1 b/Scheduler_Extensions/run.ps1 index 66af8649ebf7..58e228ebbe1d 100644 --- a/Scheduler_Extensions/run.ps1 +++ b/Scheduler_Extensions/run.ps1 @@ -10,85 +10,5 @@ Write-Host 'Started Scheduler for Extensions' # NinjaOne Extension if ($Configuration.NinjaOne.Enabled -eq $True) { - - $Table = Get-CIPPTable -TableName NinjaOneSettings - $Settings = (Get-AzDataTableEntity @Table) - $TimeSetting = ($Settings | Where-Object { $_.RowKey -eq 'NinjaSyncTime' }).SettingValue - - - if (($TimeSetting | Measure-Object).count -ne 1) { - [int]$TimeSetting = Get-Random -Minimum 1 -Maximum 95 - $AddObject = @{ - PartitionKey = 'NinjaConfig' - RowKey = 'NinjaSyncTime' - 'SettingValue' = $TimeSetting - } - Add-AzDataTableEntity @Table -Entity $AddObject -Force - } - - Write-Host "Ninja Time Setting: $TimeSetting" - - $LastRunTime = Get-Date(($Settings | Where-Object { $_.RowKey -eq 'NinjaLastRunTime' }).SettingValue) - - Write-Host "Last Run: $LastRunTime" - - $CurrentTime = Get-Date - $CurrentInterval = ($CurrentTime.Hour * 4) + [math]::Floor($CurrentTime.Minute / 15) - - Write-Host "Current Interval: $CurrentInterval" - - $CIPPMapping = Get-CIPPTable -TableName CippMapping - $Filter = "PartitionKey eq 'NinjaOrgsMapping'" - $TenantsToProcess = Get-AzDataTableEntity @CIPPMapping -Filter $Filter | Where-Object { $Null -ne $_.NinjaOne -and $_.NinjaOne -ne '' } - - if ($Null -eq $LastRunTime -or $LastRunTime -le (Get-Date).addhours(-25) -or $TimeSetting -eq $CurrentInterval) { - Write-Host 'Executing' - foreach ($Tenant in $TenantsToProcess | Sort-Object lastEndTime) { - Push-OutputBinding -Name NinjaProcess -Value @{ - 'NinjaAction' = 'SyncTenant' - 'MappedTenant' = $Tenant - } - Start-Sleep -Seconds 1 - - } - - $AddObject = @{ - PartitionKey = 'NinjaConfig' - RowKey = 'NinjaLastRunTime' - 'SettingValue' = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffK') - } - Add-AzDataTableEntity @Table -Entity $AddObject -Force - - Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "NinjaOne Daily Synchronization Queued for $(($TenantsToProcess | Measure-Object).count) Tenants" -Sev 'Info' - - } else { - if ($LastRunTime -lt (Get-Date).AddMinutes(-90)) { - $TenantsToProcess | ForEach-Object { - if ($Null -ne $_.lastEndTime -and $_.lastEndTime -ne '') { - $_.lastEndTime = (Get-Date($_.lastEndTime)) - } else { - $_ | Add-Member -NotePropertyName lastEndTime -NotePropertyValue $Null -Force - } - - if ($Null -ne $_.lastStartTime -and $_.lastStartTime -ne '') { - $_.lastStartTime = (Get-Date($_.lastStartTime)) - } else { - $_ | Add-Member -NotePropertyName lastStartTime -NotePropertyValue $Null -Force - } - } - $CatchupTenants = $TenantsToProcess | Where-Object { (((($_.lastEndTime -eq $Null) -or ($_.lastStartTime -gt $_.lastEndTime)) -and ($_.lastStartTime -lt (Get-Date).AddMinutes(-30)))) -or ($_.lastStartTime -lt $LastRunTime) } - foreach ($Tenant in $CatchupTenants) { - Push-OutputBinding -Name NinjaProcess -Value @{ - 'NinjaAction' = 'SyncTenant' - 'MappedTenant' = $Tenant - } - Start-Sleep -Seconds 1 - } - if (($CatchupTenants | Measure-Object).count -gt 0) { - Write-LogMessage -API 'NinjaOneSync' -user 'CIPP' -message "NinjaOne Synchronization Catchup Queued for $(($CatchupTenants | Measure-Object).count) Tenants" -Sev 'Info' - } - - } - - } + Invoke-NinjaOneExtensionScheduler } \ No newline at end of file From 532bed35504dca068c40941365ff50e6d64ce5fd Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:35:18 -0700 Subject: [PATCH 25/29] Add or update the Azure App Service build and deployment workflow config --- .github/workflows/dev_cippckdtz.yml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/dev_cippckdtz.yml diff --git a/.github/workflows/dev_cippckdtz.yml b/.github/workflows/dev_cippckdtz.yml new file mode 100644 index 000000000000..6e0c53e9df0a --- /dev/null +++ b/.github/workflows/dev_cippckdtz.yml @@ -0,0 +1,30 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/azure/functions-action +# More GitHub Actions for Azure: https://github.com/Azure/actions + +name: Build and deploy Powershell project to Azure Function App - cippckdtz + +on: + push: + branches: + - dev + workflow_dispatch: + +env: + AZURE_FUNCTIONAPP_PACKAGE_PATH: '.' # set this to the path to your web app project, defaults to the repository root + +jobs: + deploy: + runs-on: windows-latest + + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@v4 + + - name: 'Run Azure Functions Action' + uses: Azure/functions-action@v1 + id: fa + with: + app-name: 'cippckdtz' + slot-name: 'Production' + package: ${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }} + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_2101C7175BFB47E58240ABD1E72E81C2 }} \ No newline at end of file From 078eefdacc7857ca0dedc023fe911c7898b96e53 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 15:02:32 -0400 Subject: [PATCH 26/29] Update CippExtensions.psd1 --- Modules/CippExtensions/CippExtensions.psd1 | Bin 11436 -> 9666 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Modules/CippExtensions/CippExtensions.psd1 b/Modules/CippExtensions/CippExtensions.psd1 index 437cc32f8661e599f3a75a3a0abb378391030900..8a30d23c6ef143845bbf7f162aa4dd3128cf7174 100644 GIT binary patch delta 16 XcmZ1zdB}T%f#_rhIjPMiG8F;%-iNwx**56K^n3KodQ#V+`Qn2o}HF-09d9 z$c;EMPlmJo0!iPx$hHK=ldlYGaF>|5xO6R(QMSvU<{L4 z98}RoOYT+1r2u!6W z)|kV0+wOR6PNWAcdLdCCGRO6t!OZMCT&Fit<(Sl|G})IpO$xh{)SFQJO6qJPPaoLI z@jX0wyp$X+vlW~zo@;gfV$&$y!;i& Date: Thu, 14 Mar 2024 20:36:32 -0400 Subject: [PATCH 27/29] Graph Request - Add %tenantid% replace option --- .../Public/GraphRequests/Get-GraphRequestList.ps1 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 index 52cc2c3778dc..49c0c3fa801e 100644 --- a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 +++ b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 @@ -112,6 +112,17 @@ function Get-GraphRequestList { $QueueReference = '{0}-{1}' -f $TenantFilter, $PartitionKey $RunningQueue = Get-CippQueue | Where-Object { $_.Reference -eq $QueueReference -and $_.Status -ne 'Completed' -and $_.Status -ne 'Failed' } + if ($TenantFilter -ne 'AllTenants' -and $Endpoint -match '%tenantid%') { + $TenantId = (Get-Tenants -IncludeErrors | Where-Object { $_.defaultDomainName -eq $TenantFilter -or $_.customerId -eq $TenantFilter }).customerId + $Endpoint = $Endpoint -replace '%tenantid%', $TenantId + $GraphQuery = [System.UriBuilder]('https://graph.microsoft.com/{0}/{1}' -f $Version, $Endpoint) + $ParamCollection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) + foreach ($Item in ($Parameters.GetEnumerator() | Sort-Object -CaseSensitive -Property Key)) { + $ParamCollection.Add($Item.Key, $Item.Value) + } + $GraphQuery.Query = $ParamCollection.ToString() + } + if (!$Rows) { switch ($TenantFilter) { 'AllTenants' { From 1a34d93dcc5727f05d60504cfca692967e85d8e1 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 14 Mar 2024 20:42:59 -0400 Subject: [PATCH 28/29] Update Get-GraphRequestList.ps1 --- Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 index 49c0c3fa801e..1117f95d196d 100644 --- a/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 +++ b/Modules/CIPPCore/Public/GraphRequests/Get-GraphRequestList.ps1 @@ -68,6 +68,7 @@ function Get-GraphRequestList { $TableName = ('cache{0}' -f ($Endpoint -replace '[^A-Za-z0-9]'))[0..62] -join '' Write-Host "Table: $TableName" + $Endpoint = $Endpoint -replace '^/', '' $DisplayName = ($Endpoint -split '/')[0] if ($QueueNameOverride) { From 7707162a21be4693ae840cf5d103ba78661796b8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 15 Mar 2024 14:04:32 +0100 Subject: [PATCH 29/29] upp version --- version_latest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version_latest.txt b/version_latest.txt index fb467b15735a..e230c8396d19 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.2.2 \ No newline at end of file +5.3.0 \ No newline at end of file