Start-Jojoba.ps1

<#
 
.SYNOPSIS
Creates a job for each incoming pipeline object and captures the output into a
test case object.
 
.DESCRIPTION
Start-Jojoba is used in the process {} block of a function that uses Jojoba.
It should wrap all the code within that block.
 
.PARAMETER ScriptBlock
This is the logic to carry out.
 
.INPUTS
All inputs aside from the ScriptBlock are taken from the calling function.
 
.OUTPUTS
A test case object.
 
#>


function Start-Jojoba {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [scriptblock] $ScriptBlock,

        [Parameter(ValueFromRemainingArguments)]
        $Jojoba
    )

    begin {
    }

    process {
        $configuration = Get-JojobaConfiguration $PSCmdlet

        if (!$configuration.Throttle) {
            #region Direct run
            Write-Verbose "Starting inside thread for $($configuration.Name)"

            # Fill out the test case
            $jojobaTestCase = [PSCustomObject] @{
                UserName        = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
                Suite           = $configuration.Suite
                Timestamp       = Get-Date
                Time            = 0
                ClassName       = $configuration.ClassName
                Name            = $configuration.Name
                Result          = "Pass"
                Message         = New-Object Collections.ArrayList
                Data            = New-Object Collections.ArrayList
                CriticalFailure = $false
            }

            $jojobaMessages = try {
                &$ScriptBlock *>&1
            } catch {
                # Handle an uncaught stop as a test block failure. This saves
                # having to write test code for everything if the exception is
                # self explanatory
                # This isn't a Critical Failure though.
                Write-JojobaFail $_.ToString()
            }

            foreach ($jojobaMessage in ($jojobaMessages | Where-Object { $null -ne $_ })) {
                if ($jojobaMessage -is [string]) {
                    [void] $jojobaTestCase.Data.Add($jojobaMessage)
                } elseif ($jojobaMessage.GetType().FullName -eq "System.Management.Automation.InformationRecord") {
                    # Used in PS5 to capture Write-Host output
                    if ($jojobaMessage.Tags.Contains("PSHOST")) {
                        [void] $jojobaTestCase.Data.Add($jojobaMessage)
                    } else {
                        [void] $jojobaTestCase.Data.Add($jojobaMessage)
                    }
                } elseif ($jojobaMessage -is [System.Management.Automation.VerboseRecord]) {
                    [void] $jojobaTestCase.Data.Add("VERBOSE: $jojobaMessage")
                } elseif ($jojobaMessage -is [System.Management.Automation.WarningRecord]) {
                    [void] $jojobaTestCase.Data.Add("WARNING: $jojobaMessage")
                } elseif ($jojobaMessage -is [System.Management.Automation.ErrorRecord]) {
                    # Exceptions also get wrapped in an ErrorRecord
                    [void] $jojobaTestCase.Data.Add($jojobaMessage)
                } else {
                    # Expand complex objects
                    [void] $jojobaTestCase.Data.Add(($jojobaMessage | Format-List | Out-String | ForEach-Object {
                      $_ -replace "(?m)\A\s+", "" -replace "(?m)^\s(\s+)\Z", "" }))
                }
            }

            # Calculate other useful information for the test case for use by Jenkins
            $jojobaTestCase.Time = ((Get-Date) - $jojobaTestCase.Timestamp).TotalSeconds

            # Write out the test case after getting rid of {} marks
            $jojobaTestCase.Message = $jojobaTestCase.Message -join [Environment]::NewLine
            $jojobaTestCase.Data = $jojobaTestCase.Data -join [Environment]::NewLine

            $jojobaTestCase
            #endregion
        } else {
            #region Parallel run
            # These are arguments which will be splatted for use by PoshRSJob
            $jobArguments = @{
                Name            = $configuration.Function
                Throttle        = $configuration.Throttle
                Batch           = $configuration.Batch
                ModulesToImport = $configuration.Suite
                FunctionsToLoad = if (!$configuration.Module) {
                    $configuration.Function
                } else {
                    $null
                }
                Verbose         = $VerbosePreference
            }
            if ($configuration.Unsafe) {
                $jobArguments.ScriptBlock = [scriptblock]::Create("`$_ | $($configuration.Function) -JojobaThrottle 0")
            } else {
                $jobArguments.ScriptBlock = [scriptblock]::Create("Set-StrictMode -Version Latest; `$ErrorActionPreference = `"Stop`"; `$_ | $($configuration.Function) -JojobaThrottle 0")
            }

            # Add any extra switches and parameters to the scriptblock so they
            # can be passed to the caller. This can't handle complex objects -
            # those should be piped in instead.
            $PSCmdlet.GetVariableValue("MyInvocation").BoundParameters.GetEnumerator() | ForEach-Object {
                # The main pipeline object is skipped here because it is passed
                # in via the pipeline
                if ($_.Key -ne $configuration.InputName) {
                    # Jojoba arguments are an array that is copied as-is, except
                    # for JojobaThrottle, as this is called with a 0 to run the
                    # actual code once it has been boxed into jobs
                    if ($_.Key -eq $configuration.ArgumentName) {
                        $_.Value | ForEach-Object {
                            if ($_ -eq "-JojobaThrottle") {
                                $_ = "-JojobaThrottleOld"
                            }
                            $jobArguments.ScriptBlock = [scriptblock]::Create("$($jobArguments.ScriptBlock) $_")
                        }
                    } elseif ($_.Value -is [System.Management.Automation.SwitchParameter]) {
                        $jobArguments.ScriptBlock = [scriptblock]::Create("$($jobArguments.ScriptBlock) -$($_.Key):`$$($_.Value)")
                    } elseif ($_.Value -is [bool]) {
                        $jobArguments.ScriptBlock = [scriptblock]::Create("$($jobArguments.ScriptBlock) -$($_.Key) `$$($_.Value)")
                    } elseif ($_.Value -is [string]) {
                        $jobArguments.ScriptBlock = [scriptblock]::Create("$($jobArguments.ScriptBlock) -$($_.Key) '$($_.Value.Replace("'", "''"))'")
                    } elseif ($_.Value -is [array]) {
                        $jobArguments.ScriptBlock = [scriptblock]::Create("$($jobArguments.ScriptBlock) -$($_.Key) $(($_.Value | ForEach-Object { "'$($_.ToString().Replace("'", "''"))'" }) -join ",")")
                    } else {
                        $jobArguments.ScriptBlock = [scriptblock]::Create("$($jobArguments.ScriptBlock) -$($_.Key) $($_.Value)")
                    }
                }
            }

            Write-Verbose "Scheduling $($jobArguments.Name) batch $($jobArguments.Batch) throttle $($jobArguments.Throttle) modules $($jobArguments.ModulesToImport) functions $($jobArguments.FunctionsToLoad) script $($jobArguments.ScriptBlock)"
            # If the function was called with a pipeline (it should have been)
            # then pass that on in the pipeline.
            # Otherwise pass the variable in over the pipeline anyway, as it's
            # the same thing.
            $null = @(if ($PSCmdlet.GetVariableValue("_") -and $PSCmdlet.GetVariableValue("_") -isnot [string]) {
                    $PSCmdlet.GetVariableValue("_")
                } else {
                    $PSCmdlet.GetVariableValue($configuration.InputName)
                }) | Start-RSJob @jobArguments
            #endregion
        }
    }

    end {
    }
}