Public/Timing/Initialize-PhaseTimings.ps1
|
<# .NOTES Compatibility shim over the N-level timing core (New-/Initialize-/ Measure-TimingSpan). Declares the single module-scoped default context the 2-level verbs share so existing call sites (provision.ps1) keep their exact signatures - no -Tree argument to thread. The clean, context-explicit API is the TimingSpan verb family; these five verbs exist only to preserve the pre-generalisation surface. #> # The one default context the 2-level verbs operate on. Declared here (not # in a separate file) because Initialize-PhaseTimings must run before any # other 2-level verb, so binding the variable's lifetime to this file keeps # the "init first" contract structurally visible - the same anchor role the # old $script:PhaseTimings list played. Read under StrictMode by the sibling # shims, so it must be declared at load time, before any of them is called. $script:DefaultPhaseTimingTree = $null function Initialize-PhaseTimings { <# .SYNOPSIS Re-declare the default 2-level timing context from a phase list. .DESCRIPTION The 2-level entry point, re-expressed as a thin wrapper over New-TimingSpanTree + Initialize-TimingSpanTree. Creates a fresh default context (clearing any prior state - safe to call again across nested provision runs in one session) and pre-declares each phase, and its sub-steps, as a skeleton so branches that never run still render as SKIPPED. Each -Phases item is either: * a plain string - a top-level phase (leaf). * @{ Name = '...'; SubSteps = @() } - a phase plus its known sub-steps, rendered indented under the parent. Pre-declaring is optional: a sub-step first seen through a sub-step timer is lazily created. It is preferred for sub-steps that may not run (e.g. 'files' when no VM has a files array) so the report shows them as SKIPPED rather than omitting them. .PARAMETER Phases Phase declarations, in run order. Strings are bare phases; hashtables carry pre-declared sub-steps under SubSteps. #> [CmdletBinding()] param( [Parameter(Mandatory)] [object[]] $Phases ) # Translate the 2-level -Phases shape (SubSteps) into the core skeleton # shape (Children) up front, validating with the legacy messages so a # typo in provision.ps1 still fails the same way. The core would also # reject an empty Name, but with a generic message; keeping the checks # here preserves the exact wording the call sites and tests expect. $skeleton = foreach ($item in $Phases) { if ($item -is [hashtable]) { $name = [string]$item['Name'] $subSteps = @() if ($item.ContainsKey('SubSteps') -and $null -ne $item['SubSteps']) { $subSteps = @($item['SubSteps']) } } else { $name = [string]$item $subSteps = @() } if ([string]::IsNullOrWhiteSpace($name)) { throw 'Initialize-PhaseTimings: phase entry has no Name.' } $children = foreach ($subName in $subSteps) { $subNameStr = [string]$subName if ([string]::IsNullOrWhiteSpace($subNameStr)) { throw ( "Initialize-PhaseTimings: phase '$name' has an empty " + 'sub-step name.') } $subNameStr } # A leaf phase declares as a bare string; a phase with sub-steps # declares as a hashtable with a Children array (@() forces an array # even for a single sub-step so the core's foreach walks it). if (@($children).Count -gt 0) { @{ Name = $name; Children = @($children) } } else { $name } } # Fresh context each call - this is the "clears prior state on re-init" # contract. The root is a pure container; the 2-level report banner is # fixed, so the root name is never surfaced (see Write-PhaseTimingReport). $script:DefaultPhaseTimingTree = New-TimingSpanTree -RootName 'Provisioning' Initialize-TimingSpanTree ` -Tree $script:DefaultPhaseTimingTree ` -Skeleton @($skeleton) } |