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
    )
}