ExecutionSteps.psm1
# This is a locally sourced Imports file for local development. # It can be imported by the psm1 in local development to add script level variables. # It will merged in the build process. This is for local development only. # region script variables # $script:resourcePath = "$PSScriptRoot\Resources" # Represents an execution step, which can be executed and potentially be cleaned up class ExecutionStep { [string] $Name [object] $State [scriptblock] hidden $_Run [scriptblock] hidden $_Cleanup [scriptblock] hidden $_Final ExecutionStep([string] $Name, [scriptblock] $Run) { $this.Name = $Name $this._Run = $Run $this._Cleanup = $null $this._Final = $null } ExecutionStep([string] $Name, [scriptblock] $Run, [scriptblock] $Cleanup) { $this.Name = $Name $this._Run = $Run $this._Cleanup = $Cleanup $this._Final = $null } ExecutionStep([string] $Name, [scriptblock] $Run, [scriptblock] $Cleanup, [scriptblock] $Final) { $this.Name = $Name $this._Run = $Run $this._Cleanup = $Cleanup $this._Final = $Final } [object] Run() { return (& $this._Run) } [object] Cleanup() { if ($null -eq $this._Cleanup) { return $null } return (& $this._Cleanup) } [object] Final() { if ($null -eq $this._Final) { return $null } return (& $this._Final) } } <# .EXTERNALHELP ExecutionSteps-help.xml #> function Invoke-ExecutionSteps { param( # the list of steps to execute [Parameter(Mandatory = $true)] [ExecutionStep[]] $steps, # if set, don't write human-readable output to host [switch] $silent ) $status = @{ Succeeded = New-Object 'System.Collections.Generic.List[System.Object]' Errored = $null Error = $null } for ($i = 0; $i -lt $steps.Count; ++$i) { $step = $steps[$i] try { if (-Not $silent.IsPresent) { Write-Host -ForegroundColor DarkGray "Running $($step.Name)" } $step.Run() $status.Succeeded.Add($step) } catch { $err = $_ $status.Errored = $step $status.Error = $err if (-Not $silent.IsPresent) { Write-Host -ForegroundColor Red "An error occured during $($step.Name), aborting execution" } # try to undo every previous step for ($j = $i; $j -ge 0; --$j) { $prevStep = $steps[$j] try { if (-Not $silent.IsPresent) { Write-Host -ForegroundColor DarkGray "Cleaning up $($prevStep.Name)" } $prevStep.Cleanup() } catch { Write-Error "${_}" if (-Not $silent.IsPresent) { Write-Host -ForegroundColor Red "!!! An error occured during cleanup of $($prevStep.Name). Manual cleanup may be needed. !!!" } break } } # then summarize the result if (-Not $silent.IsPresent) { Write-Host Write-Host Write-Host -ForegroundColor Red $err } break } } for ($j = $i; $j -ge 0; --$j) { $prevStep = $steps[$j] if (-Not $prevStep) { continue } try { if (-Not $silent.IsPresent) { Write-Host -ForegroundColor DarkGray "Finalizing $($prevStep.Name)" } $prevStep.Final() } catch { Write-Error "${_}" if (-Not $silent.IsPresent) { Write-Host -ForegroundColor Red "!!! An error occured during finalization of $($prevStep.Name). Manual cleanup may be needed. !!!" } break } } if (-Not $status.Error -and -Not $silent.IsPresent) { Write-Host Write-Host Write-Host -ForegroundColor Green "The script was successfully executed" } if ($status.Error) { throw $status.Error } return $null } <# .EXTERNALHELP ExecutionSteps-help.xml #> function New-ExecutionStep { param( # The human-readable name that represents this step [Parameter(Mandatory = $true)] [string] $name, # The scriptblock that will be executed when the step should do its thing [Parameter(Mandatory = $true)] [scriptblock] $run, # An optional scriptblock which will be executed if a later step (or the step itself) throws an error [Parameter(Mandatory = $false)] [scriptblock] $cleanup, # An optional scriptblock which will be executed when script execution ends, regardless of whether it succeeded. Useful for cleaning up stuff that's needed by later steps. [Parameter(Mandatory = $false)] [scriptblock] $final ) [ExecutionStep]::new( $name, $run, $cleanup, $final ) } |