ITFabrik.Stepper.psm1
|
# Built file. Do not edit directly; edit source files and rebuild. # Build timestamp: 2026-03-06T12:47:18+00:00 # region Private\State.ps1 # Module-scoped state and helpers (not exported) if (-not $script:StepStack) { $script:StepStack = New-Object System.Collections.Stack } if (-not $script:StepStateLock) { $script:StepStateLock = New-Object object } function Push-Step { param([Step]$Step) [System.Threading.Monitor]::Enter($script:StepStateLock) try { $null = $script:StepStack.Push($Step) } finally { [System.Threading.Monitor]::Exit($script:StepStateLock) } } function Pop-Step { [System.Threading.Monitor]::Enter($script:StepStateLock) try { if ($script:StepStack.Count -gt 0) { return $script:StepStack.Pop() } } finally { [System.Threading.Monitor]::Exit($script:StepStateLock) } } function Get-StepStackTop { [System.Threading.Monitor]::Enter($script:StepStateLock) try { if ($script:StepStack.Count -gt 0) { return $script:StepStack.Peek() } } finally { [System.Threading.Monitor]::Exit($script:StepStateLock) } } # (Removed) StopExecution flag was unused; keeping state minimal # endregion # region Private\Helpers.ps1 # Initialisation du flag d'exécution interne d'un Step if ($null -eq $script:InsideStep) { $script:InsideStep = $false } <# .SYNOPSIS Affiche un message d'étape avec indentation. .DESCRIPTION Affiche un message d'étape à l'écran, en gris, avec un niveau d'indentation optionnel. .PARAMETER Message Le message à afficher. .PARAMETER IndentLevel Niveau d'indentation (nombre d'espaces). #> function Get-StepManagerLogger { [CmdletBinding()] param() $currentScopeVariable = Get-Variable -Name 'StepManagerLogger' -ErrorAction SilentlyContinue if ($null -ne $currentScopeVariable) { return $currentScopeVariable.Value } foreach ($scope in @('Script', 'Global')) { $variable = Get-Variable -Name 'StepManagerLogger' -Scope $scope -ErrorAction SilentlyContinue if ($null -ne $variable) { return $variable.Value } } return $null } function Write-StepMessage { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Severity, [Parameter(Mandatory)] [string]$Message, [int]$IndentLevel = 0, [string]$StepName = '', [string]$ForegroundColor ) # Détection de PowerShell 7+ $isPwsh7 = $PSVersionTable.PSVersion.Major -ge 7 # Dictionnaire d'icônes par sévérité (Unicode, fallback via [Severity] si non supporté) $icons = @{ 'Info' = 'ℹ' 'Success' = '✓' 'Warning' = '⚠' 'Error' = '✖' 'Debug' = '⚙' 'Verbose' = '…' } $prefixRaw = if ($isPwsh7 -and $icons.ContainsKey($Severity)) { $icons[$Severity] } else { "[$Severity]" } # Padding spécifique par sévérité pour un alignement optimal $nbsp = [char]0x2007 function Get-NbspString($count) { [string]::new(@($nbsp) * $count) } switch ($Severity) { 'Info' { $prefix = $prefixRaw + (Get-NbspString 4) ; $ForegroundColor = if(-not $ForegroundColor){ 'Gray'} else{$ForegroundColor} } 'Success' { $prefix = $prefixRaw + (Get-NbspString 3) ; $ForegroundColor = if(-not $ForegroundColor){ 'Green'} else{$ForegroundColor}} 'Warning' { $prefix = $prefixRaw + (Get-NbspString 4) ; $ForegroundColor = if(-not $ForegroundColor){ 'Yellow'} else{$ForegroundColor}} 'Error' { $prefix = $prefixRaw + (Get-NbspString 3) ; $ForegroundColor = if(-not $ForegroundColor){ 'Red'} else{$ForegroundColor}} 'Debug' { $prefix = $prefixRaw + (Get-NbspString 3) ; $ForegroundColor = if(-not $ForegroundColor){ 'Cyan'} else{$ForegroundColor}} 'Verbose' { $prefix = $prefixRaw + (Get-NbspString 3) ; $ForegroundColor = if(-not $ForegroundColor){ 'Magenta'} else{$ForegroundColor}} default { $prefix = $prefixRaw + (Get-NbspString 3) ; $ForegroundColor = if(-not $ForegroundColor){ 'White'} else{$ForegroundColor}} } $indent = if ($IndentLevel -gt 0) { ' ' * ($IndentLevel * 2) } else { '' } $now = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $step = if ($StepName) { "[$StepName]" } else { '' } # Ne plus afficher le Component ici pour éviter la duplication d'étiquettes $text = "[$now] $prefix$indent$step $Message" Write-Host $text -ForegroundColor $ForegroundColor } # endregion # region Private\Classes\Step.ps1 class Step { [string]$Name [ValidateSet('Pending','Success','Error')] [string]$Status = 'Pending' [int]$Level = 0 [Step]$ParentStep = $null [System.Collections.Generic.List[Step]]$Children = [System.Collections.Generic.List[Step]]::new() [string]$Detail = '' [bool]$ContinueOnError = $false [datetime]$StartTime [Nullable[datetime]]$EndTime [TimeSpan]$Duration = [TimeSpan]::Zero Step([string]$Name, [Step]$ParentStep = $null, [bool]$ContinueOnError = $false) { $this.Name = $Name $this.Status = 'Pending' $this.Detail = '' $this.ParentStep = $ParentStep $this.Level = if ($ParentStep) { $ParentStep.Level + 1 } else { 0 } $this.ContinueOnError = $ContinueOnError $this.StartTime = Get-Date $this.EndTime = $null if ($ParentStep) { $ParentStep.Children.Add($this) } } [string] ToString() { $dur = if ($this.EndTime) { ($this.EndTime - $this.StartTime) } else { [TimeSpan]::Zero } return "{0} [{1}] L{2} ({3:c})" -f $this.Name, $this.Status, $this.Level, $dur } } # endregion # region Private\Functions\Complete-Step.ps1 function Complete-Step { [CmdletBinding()] param() $current = Get-CurrentStep if ($current) { $current.EndTime = Get-Date if ($null -ne $current.StartTime -and $null -ne $current.EndTime) { $current.Duration = ($current.EndTime - $current.StartTime) } } Invoke-Logger -Component 'StepManager' -Severity 'Verbose' -Message "Étape [$($current.Name)] terminée." -IndentLevel ($current.Level) # Dépile le niveau d'indentation contextuel (protégé pour runspace) [System.Threading.Monitor]::Enter($script:StepStateLock) try { if ($script:CurrentStepIndentStack) { $script:CurrentStepIndentStack = $script:CurrentStepIndentStack[0..($script:CurrentStepIndentStack.Count-2)] if ($script:CurrentStepIndentStack.Count -eq 0) { Remove-Variable -Name CurrentStepIndentStack -Scope Script -ErrorAction SilentlyContinue } } } finally { [System.Threading.Monitor]::Exit($script:StepStateLock) } # Pop current step and restore parent (if any) $null = Pop-Step } # endregion # region Private\Functions\Get-CurrentStep.ps1 function Get-CurrentStep { [CmdletBinding()] param() return (Get-StepStackTop) } # endregion # region Private\Functions\Invoke-Logger.ps1 <# .SYNOPSIS Centralise l'affichage des messages de log pour StepManager. .DESCRIPTION Permet d'afficher des messages typés (Info, Success, Warning, Error, Debug, Verbose) avec indentation et support d'un logger personnalisé. Si aucun logger n'est défini, affiche le message formaté dans la console. Les messages Debug ne sont affichés que si $DebugPreference le permet. .PARAMETER Component Nom du composant ou de l'étape à l'origine du message. .PARAMETER Message Le message à afficher. .PARAMETER Severity Le niveau de sévérité du message : Info, Success, Warning, Error, Debug, Verbose. .PARAMETER IndentLevel Niveau d'indentation pour l'affichage (entier, optionnel). #> function Invoke-Logger { param( [Parameter(Mandatory)] [string]$Component, [Parameter(Mandatory)] [string]$Message, [Parameter(Mandatory)] [ValidateSet('Info','Success','Warning','Error','Debug','Verbose')] [string]$Severity, [int]$IndentLevel = [int]::MinValue ) # Indentation: respecte une valeur explicite sinon calcule depuis la step courante $explicitIndent = ($IndentLevel -ne [int]::MinValue) $finalIndent = if ($explicitIndent) { $IndentLevel } else { 0 } if (-not $explicitIndent) { if ($script:InsideStep) { try { $currentStep = Get-CurrentStep if ($null -ne $currentStep) { $finalIndent = $currentStep.Level + 1 } else { $finalIndent = 1 } } catch { $finalIndent = 1 } } } # Gestion du niveau Debug et Verbose if ($Severity -eq 'Debug') { if ($DebugPreference -eq 'SilentlyContinue') { return } } if ($Severity -eq 'Verbose') { if ($VerbosePreference -eq 'SilentlyContinue') { return } } $logger = Get-StepManagerLogger if ($null -eq $logger) { Write-StepMessage -Severity $Severity -Message ("[$Component] $Message") -IndentLevel $finalIndent } else { & $logger $Component $Message $Severity $finalIndent } } # endregion # region Private\Functions\New-Step.ps1 <# .SYNOPSIS Crée une nouvelle étape et la pousse dans la pile d'exécution. .DESCRIPTION Crée un objet Step, gère l'imbrication et l'état, et affiche le nom de l'étape. Les messages sont affichés en gris par défaut. .PARAMETER Name Nom de l'étape à créer. .PARAMETER ContinueOnError Indique si l'exécution doit continuer en cas d'erreur dans l'étape. .OUTPUTS Step #> function New-Step { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [switch]$ContinueOnError ) if (-not $PSCmdlet.ShouldProcess($Name, 'Create step')) { return } $parent = Get-CurrentStep $step = [Step]::new($Name, $parent, $ContinueOnError.IsPresent) # Gestion d'une pile d'indentation contextuelle pour chaque Step imbriquée (protégée pour runspace) [System.Threading.Monitor]::Enter($script:StepStateLock) try { if (-not $script:CurrentStepIndentStack) { $script:CurrentStepIndentStack = @() } $script:CurrentStepIndentStack += ($step.Level + 1) } finally { [System.Threading.Monitor]::Exit($script:StepStateLock) } Push-Step -Step $step Invoke-Logger -Component 'StepManager' -Message "Création de l'étape : $Name" -Severity Verbose -IndentLevel $step.Level Invoke-Logger -Component 'StepManager' -Message "$Name" -Severity Info -IndentLevel $step.Level return $step } # endregion # region Private\Functions\Set-Step.ps1 function Set-Step { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [ValidateSet('Pending','Success','Error')] [string]$Status, [string]$Detail = '' ) $current = Get-CurrentStep if (-not $current) { return } if (-not $PSCmdlet.ShouldProcess($current.Name, "Set step status to $Status")) { return } $current.Status = $Status $current.Detail = $Detail if ($Status -eq 'Error') { # Laisser l'auto-indentation gérer le niveau quand non spécifié Invoke-Logger -Component 'StepManager' -Severity 'Error' -Message "Erreur dans l'étape [$($current.Name)] : $Detail" } else{ Invoke-Logger -Component 'StepManager' -Severity 'Verbose' -Message "Étape [$($current.Name)] définie sur le statut : $Status" } } # endregion # region Public\Invoke-Step.ps1 <# .SYNOPSIS Exécute un bloc de script dans une étape avec gestion d'état, d'imbrication et d'erreur. .DESCRIPTION Encapsule l'exécution d'un ScriptBlock dans une étape typée, avec gestion du statut (Success, Error), imbrication et option de poursuite sur erreur. Les messages sont affichés en gris par défaut. .PARAMETER Name Nom de l'étape à exécuter. .PARAMETER ScriptBlock Bloc de code à exécuter dans l'étape. .PARAMETER ContinueOnError Indique si l'exécution doit continuer en cas d'erreur dans l'étape. Par défaut : $false. .OUTPUTS Step .EXAMPLE Invoke-Step -Name 'Préparation' -ScriptBlock { # Instructions de préparation } .EXAMPLE Invoke-Step -Name 'Installation' -ScriptBlock { Invoke-Step -Name 'Télécharger' -ScriptBlock { # Téléchargement } -ContinueOnError Invoke-Step -Name 'Configurer' -ScriptBlock { # Configuration } } .EXAMPLE $items = 'A', 'B', 'C' $steps = foreach ($item in $items) { Invoke-Step -Name "Traitement $item" -ScriptBlock { # Traitement spécifique à $item "Traitement de $item terminé." } } # $steps contient la liste des objets Step pour chaque élément .EXAMPLE Invoke-Step -Name 'Exemple' -ScriptBlock { throw 'Erreur volontaire' } -ContinueOnError # L'étape sera en statut 'Error', mais l'exécution continue Invoke-Step -Name 'Exemple' -ScriptBlock { throw 'Erreur volontaire' } # L'étape sera en statut 'Error' (propagation possible selon le parent) #> function Invoke-Step { [CmdletBinding()] [OutputType('Step')] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [ValidateScript({ if ($_.Length -gt 80) { throw "Le nom de l'étape ne doit pas dépasser 80 caractères." } if ($_ -match '[\\/:*?"<>|]') { throw "Le nom de l'étape contient des caractères interdits (\\ / : * ? \"" < > |)." } return $true })] [string]$Name, [switch]$ContinueOnError = $false, [Parameter(Mandatory)] [ScriptBlock]$ScriptBlock, [switch]$PassThru ) # Autoriser les ScriptBlock vides (no-op) $step = New-Step -Name $Name -ContinueOnError:$ContinueOnError.IsPresent $errorDetail = $null $threw = $false $shouldThrow = $false $script:InsideStep = $true try { $null = & $ScriptBlock } catch { $threw = $true $errorDetail = $_.Exception.Message Set-Step -Status Error -Detail $errorDetail | Out-Null # Propagation contrôlée $shouldThrow = -not $ContinueOnError.IsPresent if ($shouldThrow -and $step.ParentStep -and $step.ParentStep.ContinueOnError) { $shouldThrow = $false } } finally { $script:InsideStep = $false if (-not $threw) { Set-Step -Status Success | Out-Null } Complete-Step } if ($threw -and $shouldThrow) { throw } if ($PassThru) { return $step } } # endregion # region Public\Write-Log.ps1 <# .SYNOPSIS Fonction publique pour écrire un message de log utilisateur dans StepManager. .DESCRIPTION Permet à l'utilisateur d'écrire un message dans le log StepManager, avec un niveau d'indentation adapté pour les logs utilisateurs (toujours un cran de plus que la Step courante). .PARAMETER Message Le message à afficher. .PARAMETER Severity Le niveau de sévérité du message : Info, Success, Warning, Error, Debug, Verbose. .EXAMPLE Write-Log -Message 'Début du traitement' -Severity 'Info' #> function Write-Log { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Message, [Parameter(Mandatory=$false)] [ValidateSet('Info','Success','Warning','Error','Debug','Verbose')] [string]$Severity = 'Info' ) try { $currentStep = Get-CurrentStep if ($null -ne $currentStep) { $finalIndent = $currentStep.Level + 1 } else { $finalIndent = 1 } } catch { $finalIndent = 1 } if ($Severity -eq 'Info') { $ForegroundColor = 'DarkGray' } $logger = Get-StepManagerLogger if ($null -eq $logger) { # Affiche uniquement le StepName pour éviter la duplication [Step][Component] Write-StepMessage -Severity $Severity -Message $Message -IndentLevel $finalIndent -StepName $($currentStep.Name) -ForegroundColor $ForegroundColor } else { $component = if ($null -ne $currentStep -and -not [string]::IsNullOrWhiteSpace($currentStep.Name)) { $currentStep.Name } else { 'StepManager' } & $logger $component $Message $Severity $finalIndent } } # endregion # Exports Export-ModuleMember -Function Invoke-Step,Write-Log -Alias @() |