Private/Remediation/Remediation.Start.ps1
|
function Get-TcRemediationSnapshot { <# .SYNOPSIS The snapshot of a run: the one given, or the latest of the output folder. #> [CmdletBinding()] [OutputType([System.Collections.IDictionary])] param( [AllowNull()][object]$Snapshot, [Parameter(Mandatory)][string]$Output ) if ($Snapshot -is [System.Collections.IDictionary]) { return $Snapshot } if ($Snapshot) { return (Read-TcSnapshot -Path (Resolve-TcPath -Path ([string]$Snapshot))) } $folder = @(Get-ChildItem -LiteralPath $Output -Directory -Filter '*-assessment' -ErrorAction SilentlyContinue | Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName 'snapshot.json') } | Sort-Object Name) if (-not $folder.Count) { throw "No snapshot in $Output. Run Invoke-TcAssessment first." } Read-TcSnapshot -Path (Join-Path $folder[-1].FullName 'snapshot.json') } function Assert-TcSnapshotAge { <# .SYNOPSIS A write rests on the snapshot: refuses one older than the limit. #> [CmdletBinding()] param( [Parameter(Mandatory)][System.Collections.IDictionary]$Snapshot, [Parameter(Mandatory)][int]$Limit ) $meta = Get-TcProperty $Snapshot 'meta' $text = [string](Get-TcProperty $meta 'finished' (Get-TcProperty $meta 'started')) $date = [datetime]::MinValue if (-not [datetime]::TryParse($text, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::None, [ref]$date)) { throw 'This snapshot has no date, so its age cannot be checked. Run Invoke-TcAssessment again.' } $age = ((Get-Date) - $date).TotalDays if ($age -gt $Limit) { throw ('This snapshot is older than the limit: {0:N0} days, limit {1}. Run Invoke-TcAssessment again.' -f $age, $Limit) } } function Assert-TcModuleFamily { <# .SYNOPSIS Stops before a script loads a module family that crashes PowerShell next to one already loaded. #> [CmdletBinding()] param( [AllowEmptyString()][string]$Service ) $families = @{ Exo = @('ExchangeOnlineManagement'); Scc = @('ExchangeOnlineManagement'); Teams = @('MicrosoftTeams'); Pnp = @('PnP.PowerShell') } if (-not $families.ContainsKey($Service)) { return } $others = @($families.Keys | Where-Object { $families[$_][0] -ne $families[$Service][0] } | ForEach-Object { $families[$_][0] } | Select-Object -Unique) $loaded = @(Get-Module -Name $others -ErrorAction SilentlyContinue | ForEach-Object Name) if ($loaded.Count) { throw "This script uses $($families[$Service][0]), and $($loaded -join ', ') is loaded in this process. Together they stop PowerShell: run it in a new window." } } function Invoke-TcRemediationScript { <# .SYNOPSIS Runs a remediation in this function's scope. .DESCRIPTION The script is dot-sourced so it runs in the module, under its strict mode, and sees the private functions. Its variables and its functions end with this call. Calling it with & would create a new script scope, and $script: in the engine would stop reaching the module. #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$Path ) if (-not (Test-Path -LiteralPath $Path)) { throw "Script not found: $Path." } . $Path } function Get-TcRemediationOutcome { <# .SYNOPSIS The result of a run, for the person or the script that started it. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [AllowEmptyString()][string]$Failure = '' ) $run = $script:TcRemediationRun $tally = Get-TcRemediationTally [pscustomobject]@{ Id = $run['Id']; Mode = $(if ($run['Apply']) { 'Apply' } else { 'Preview' }); Code = $run['Code']; Tenants = @($run['Tenants']); Folder = $run['Folder']; Actions = $tally['Total']; Applied = $tally['Applied'] AlreadyInPlace = $tally['AlreadyInPlace']; Failed = $tally['Failed']; ForAPerson = $tally['ForAPerson']; Waiting = $tally['Waiting']; PendingDecisions = $run['Pending']; Alerts = $run['Alerts']; Error = $Failure; Notice = $script:TcUntestedNotice } } |