Invoke-Script.ps1

function Invoke-Script {
    <#
    .DESCRIPTION
    Invoke the provided script or command and measure the time it takes to execute it.
 
    .PARAMETER ScriptBlock
    A ScriptBlock to be executed.
 
    $trace = Invoke-Script -ScriptBlock { Import-Module $PSScriptRoot\Planets.psm1; Get-Planet } -Repeat 3
 
    When running a script file use the same syntax, you can specify parameters if you need:
    $trace = Invoke-Script -ScriptBlock { & $PSScriptRoot\MyScript.ps1 } -Repeat 3
 
    .PARAMETER Repeat
    Run the provided script or command this many times in the same session. Default is 1.
    The first two runs are usually much slower than the subsequent runs. Using 3 or more
    is good to get the idea of overall performance, but if you are optimizing for fast single
    run you might want to keep 1.
 
    .PARAMETER Preheat
    Run the provided script or command this many times without tracing it to warm up the session.
    This is good for measuring normal performance excluding startup. Default is 0.
 
    .PARAMETER Flag
    A hash table of feature flags to be enabled to do before/after comparisons. The script or command will run
    twice in each repetition. Once as before changes and once as after changes.
 
    For this to work your code needs to cooperate:
 
    Each key from the hashtable will be defined as a $global: variable. Use that variable in `if` to wrap
    the new code to test it against the old code.
 
    Use $true, $false as the values. The hashtable represents the after side. Each key will be
    defined with the value for after side. Each key will be defined with $false for the before side.
 
    In this case example is worth 1000 words:
 
    Invoke-Script -ScriptBlock { & MyScript.ps1 } -Flag @{ _profiler = $true }
 
    Original code in MyScript.ps1 you are trying to improve:
    $values = 1..1000
    foreach ($v in $Values) {
        $newValues += $v + 10
    }
 
    Modified code with changes that you think are faster wrapped in if using the feature flags:
    if ($_profiler) {
        $values = [System.Linq.Enumerable]::Range(1,1000)
    }
    else {
        $values = 1..1000
    }
 
    if ($_profiler) {
        # after side
        $newValues = foreach ($v in $Values) { $v + 10 }
    }
    else {
        # before side
        foreach ($v in $Values) {
            $newValues += $v + 10
        }
    }
 
    A single feature flag might be good enough for many purposes, but imagine there are multiple improvements
    that migh contradict each other, in that case you are better of adding different flag for each one:
 
    Invoke-Script -ScriptBlock { & MyScript.ps1 } -Flag @{ _iteration = $true; _enumerable = $true }
 
    if ($_enumerable) {
        $values = [System.Linq.Enumerable]::Range(1,1000)
    }
    else {
        $values = 1..1000
    }
 
    if ($_iteration) {
        # B side
        $newValues = foreach ($v in $Values) { $v + 10 }
    }
    else {
        # A side
        foreach ($v in $Values) {
            $newValues += $v + 10
        }
    }
 
    Using two or more feature flags you can test them together as shown above, or one without the other:
 
    Invoke-Script -ScriptBlock { & MyScript.ps1 } -Flag @{ _iteration = $true; _enumerable = $false }
    #>


    param(
        [Parameter(Mandatory)]
        [ScriptBlock] $ScriptBlock,
        [uint32] $Repeat = 1,
        [uint32] $Preheat = 0,
        [Hashtable] $Flag
    )

    $ErrorView = "Normal"
    $ErrorActionPreference = "Stop"

    Assert-PowerShellVersion

    Write-Host -ForegroundColor Magenta "Running in PowerShell $($PSVersionTable.PSVersion)."
    if ($Flag) {
        Write-Host -ForegroundColor Magenta "Flags for After runs:"
        foreach ($f in $Flag.GetEnumerator()) {
            Write-Host -ForegroundColor Magenta " $($f.Key) = $($f.Value)"
        }
    }

    if (0 -ge $Repeat) {
        $Repeat = 1
    }

    if (0 -ge $Preheat) {
        $Preheat = 0
    }

    if (0 -lt $Preheat) { 
        foreach ($i in 1..$Preheat) {
            Write-Host -Foreground Magenta  "Warm up $i"
            if (0 -lt $Flag.Count) { 
                foreach ($p in $Flag.GetEnumerator()) {
                    $v = if ($i % 2 -eq 1) { 
                        $false
                    } 
                    else {
                        $p.Value
                    }

                    Set-Variable -Scope Global -Name $p.Key -Value $v
                }
            }
            $sw = [System.Diagnostics.Stopwatch]::StartNew()
            try {
                $null = & $ScriptBlock
                $sw.Stop()
            }
            catch {
                $sw.Stop()
                Write-Host "Warm up failed with $_."
            }
        }
    }

    $run = [Collections.Generic.List[object]]@()

    foreach ($i in 1..$Repeat) {
        $sides = if ($null -ne $Flag) { "Before", "After" } else { "After" }        
        
        foreach ($side in $sides) {
            Write-Host -Foreground Magenta  "Run $i$(if (1 -lt $sides.Count) { " - $side" })"
            if (0 -lt $Flag.Count) { 
                foreach ($p in $Flag.GetEnumerator()) {
                    $v = if ("Before" -eq $side) { 
                        $false
                    } 
                    else {
                        $p.Value
                    }

                    Set-Variable -Scope Global -Name $p.Key -Value $v
                }
            }
            $err = $null
            $sw = [System.Diagnostics.Stopwatch]::StartNew()
            try {
                $null = & $ScriptBlock
                $sw.Stop()
                Write-Host -Foreground Magenta  "Run $i$(if (1 -lt $sides.Count) { " - $side" }) finished after $($sw.Elapsed)"
            }
            catch { 
                $sw.Stop()
                $err = $_
                Write-Host "Run $i$(if (1 -lt $sides.Count) { " - $side" }) failed after $($sw.Elapsed) with $_."
            }

            $null = $run.Add(@{ Error = $err; SelfDuration = $sw.Elapsed; Side = $side })
        }
    }

    Write-Host -Foreground Magenta  "Done."

    if ($Flag) { 
        for ($r = 0; $r -lt $run.Count; $r = $r + 2) { 
            $b = $run[$r]
            $a = $run[$r + 1]

            $diff = [int]($a.SelfDuration.TotalMilliseconds - $b.SelfDuration.TotalMilliseconds)
            if ($diff -eq 0) { 
                $beforeColor = $afterColor = "Yellow"
            }
            elseif ($diff -gt 0) { 
                $beforeColor = "Green"
                $afterColor = "Red"
            }
            else { 
                $beforeColor = "Red"
                $afterColor = "Green"
            }

            Write-Host -NoNewline "Run $(${r}/2): "
            Write-Host -ForegroundColor $beforeColor $b.SelfDuration -NoNewline 
            Write-Host -NoNewline " -> "
            Write-Host -ForegroundColor $afterColor $a.SelfDuration -NoNewline 
            Write-Host " ($($diff) ms)"
        }
    }
}