Private/Invoke-NSPConditionalAccessGuidedDeployment.ps1
|
function Invoke-NSPConditionalAccessGuidedDeployment { <# .SYNOPSIS Dashboard: one task's deployment from start to finish: plan, review only that task's items, run, re-inventory, repeat. .DESCRIPTION -Focus picks the task's items from a full deployment plan (a scriptblock given each item; $true keeps it). Anything those items depend on is added. Other items in the plan are left undecided. Some items wait for others: a policy that needs a new group is planned only once the group exists. So after a run, the dashboard takes a fresh inventory and plans again, and offers the items that have now become possible (up to -MaxPasses). It stops when nothing in focus is left to decide, the operator approves nothing, or it is not signed in to re-inventory. #> [CmdletBinding()] param( [Parameter(Mandatory)]$Session, [Parameter(Mandatory)][scriptblock]$Focus, [string]$Title = 'Review the plan', [ValidateRange(1, 5)][int]$MaxPasses = 3, # Extra arguments for New-NSPConditionalAccessDeploymentPlan, such as @{ GroupSlot = 'MFAExempt' }. [hashtable]$PlanArguments = @{} ) for ($pass = 1; $pass -le $MaxPasses; $pass++) { $state = $Session.State if (-not $state.Inventory) { throw 'No saved inventory yet. Sign in (C), and the dashboard takes one.' } if (-not $state.ProfilePath) { throw 'No tenant profile yet. Use All tools > Tenant profile > Build the profile, or N for a new tenant.' } Write-NSPConditionalAccessProgress -Activity Dashboard -Status 'Planning: comparing the templates with the inventory' $plan = New-NSPConditionalAccessDeploymentPlan -Inventory $state.Inventory.FullName -TenantProfile $state.ProfilePath -Save -ConfigRoot $Session.ConfigRoot @PlanArguments Write-NSPConditionalAccessProgress -Activity Dashboard -Completed $byId = @{} foreach ($item in $plan.Items) { $byId[$item.ItemId] = $item } $wanted = [System.Collections.Generic.HashSet[string]]::new() foreach ($item in $plan.Items) { if (& $Focus $item) { [void]$wanted.Add($item.ItemId) } } $queue = [System.Collections.Generic.Queue[string]]::new() foreach ($id in @($wanted)) { $queue.Enqueue($id) } while ($queue.Count) { $id = $queue.Dequeue() foreach ($dependency in @($byId[$id].DependsOn | Where-Object { $_ })) { if ($byId.ContainsKey($dependency) -and $wanted.Add($dependency)) { $queue.Enqueue($dependency) } } } $ids = @($plan.Items | Where-Object { $wanted.Contains($_.ItemId) } | ForEach-Object { $_.ItemId }) if (-not $ids.Count) { Write-NSPConsoleLine ' This plan has nothing for this task.' -Role Muted return } $passTitle = if ($pass -gt 1) { "$Title (step $pass)" } else { $Title } $approved = Invoke-NSPConditionalAccessPlanReview -PlanPath $plan.Path -Focus $ids -Title $passTitle -TenantProfile $state.ProfilePath if (-not $approved) { Write-NSPConsoleLine ' Nothing approved, so nothing runs. The plan is saved; come back to it any time.' -Role Muted return } $waiting = @($plan.Items | Where-Object { $wanted.Contains($_.ItemId) -and $_.Action -eq 'PendingPrerequisite' }) # A bound synced group changes the policies that use it, which only the next plan shows. $bound = @((Get-Content -LiteralPath $plan.Path -Raw | ConvertFrom-Json).Items | Where-Object { $_.Decision -eq 'Approved' -and $_.Action -eq 'BindSyncedGroup' }) $ran = Invoke-NSPConditionalAccessDashboardRun -PlanPath $plan.Path -TenantProfile $state.ProfilePath -ConfigRoot $Session.ConfigRoot if (-not $ran) { return } $Session.InventoryWanted = $true if ((-not $waiting.Count -and -not $bound.Count) -or $pass -eq $MaxPasses) { return } Write-NSPConsoleLine '' if ($waiting.Count) { Write-NSPConsoleLine " $($waiting.Count) item(s) were waiting for what just ran: $(@($waiting | ForEach-Object { $_.ItemId }) -join ', ')." -Role Heading } if ($bound.Count) { Write-NSPConsoleLine " Bound $(@($bound | ForEach-Object { $_.DisplayName }) -join ', '). The policies that use it are updated in the next step." -Role Heading } if (-not (Read-NSPConfirm -Prompt 'Take a fresh inventory and plan them now?' -DefaultYes)) { return } if (-not (Invoke-NSPConditionalAccessDashboardInventory -Session $Session -Reason 'Re-reading the tenant for the next step')) { return } } } |