Invoke-ExpressionAt.ps1

<#
.SYNOPSIS
    Execute shell expressions in different locations.
#>


#Requires -Version 5
Set-StrictMode -Version Latest

function Invoke-SpreadExpression {
    <#
    .SYNOPSIS
        Invoke given expression in multiple paths.
    .DESCRIPTION
        By default the target paths are read from a local ./.spread file.
        Each line should contain a target path. The expression is created by
        concatenating all positional arguments. Alternatively expressions
        may be passed through the pipeline.
    .PARAMETER Command
        The expression to run.
    .PARAMETER PathFile
        Override the source file for target paths.
    #>

    [CmdletBinding()]
    Param([Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)][string]$Command,
          [Parameter(Mandatory=$false)][string]$PathFile = './.spread')

    Begin {
        $Paths = $(Get-Content -ErrorAction Stop $PathFile)
    }

    Process {
        $Paths | Invoke-ExpressionAt -Command $Command
    }
}

function Invoke-ExpressionAt {
    <#
    .SYNOPSIS
        Run an expression in a given path.
    .DESCRIPTION
        The expression is only run if the target path actually exists.
        If multiple paths are specified, the expression is run sequentially
        in each of them.
        The expressions's execution status and return code is inspected to
        determine whether it succeeded. If a failure is detected,
        a non-terminating error is raised.
    .PARAMETER Path
        The path where the expression is run.
    .PARAMETER Command
        The expression to run.
    #>

    [CmdletBinding()]
    Param([Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][alias("PSPath")][string[]]$Path,
          [Parameter(Mandatory=$true, Position=0, ValueFromRemainingArguments=$true)][string]$Command)

    Process {
        $Path | ForEach-Object { __HandleSingleExpression $_ $Command }
    }
}

function __HandleSingleExpression {
    [CmdletBinding()]
    Param([string]$Path, [string]$Command)

    Write-Host "`n$Path> $Command`n" -ForegroundColor Cyan
    Push-Location $Path -StackName invoke
    if ($?) {
        try {
            __ExecuteExpression $Command
        } finally {
            Pop-Location -StackName invoke
        }
    } else {
        Write-Warning "cannot access location - skipping $Path\$Command"
    }
}

function __ExecuteExpression {
    [CmdletBinding()]
    Param([string]$It)

    $Global:LASTEXITCODE = $null
    Invoke-Expression $It
    if ($Global:LASTEXITCODE) {
        Write-Error -Category OperationStopped -CategoryActivity "Invocation" -TargetObject $It "$(Get-Location).Path/$It failed (exit code $LASTEXITCODE)"
    }
}