Format-Object.ps1
function Format-Object { <# .SYNOPSIS Formats an Object .DESCRIPTION Formats any object, using any number of Format-Object extensions. .EXAMPLE "red" | Format-Object -ForegroundColor "red" .EXAMPLE 1..10 | Format-Object -NumberedList .LINK Get-EZOutExtension .LINK Format-RichText .LINK Format-Markdown .LINK Format-YAML .LINK Format-Heatmap #> param( [Parameter(ValueFromPipeline)] [PSObject] $InputObject ) dynamicParam { $myCmdName = $MyInvocation.MyCommand $dynamicParams = Get-EZOutExtension -DynamicParameter -NoMandatoryDynamicParameter -ParameterSetName "__AllParameterSets" -CommandName $myCmdName $null = $dynamicParams.Remove("InputObject") $dynamicParams } begin { # First, we make a copy of our parameters $paramCopy = @{} + $PSBoundParameters $paramCopy.Remove('InputObject') # let's initialize an empty variable to keep any extension validation errors. $extensionValidationErrors = $null # Then we create a hashtable containing the parameters to Get-EZOutExtension: $ezOutExtensionParams = @{ CommandName = $MyInvocation.MyCommand # We want extensions for this command CouldRun = $true Parameter = $paramCopy ValidateInput = $host # that are valid, given the $host. } # If -Verbose is -Debug is set, we will want to populate extensionValidationErrors if ($VerbosePreference -ne 'silentlyContinue' -or $DebugPreference -ne 'silentlyContinue') { $ezOutExtensionParams.ErrorAction = 'SilentlyContinue' # We do not want to display errors $ezOutExtensionParams.ErrorVariable = 'extensionValidationErrors' # we want to redirect them into $extensionValidationErrors. $ezOutExtensionParams.AllValid = $true # and we want to see that all of the validation attributes are correct. } else { $ezOutExtensionParams.ErrorAction = 'Ignore' } # Now we get a list of extensions. $formatObjectExtensions = @(Get-EZOutExtension @ezOutExtensionParams | Where-Object { $_.ExtensionParameter.Count}) # If any of them had errors, and we want to see the -Verbose channel if ($extensionValidationErrors -and $VerbosePreference -ne 'silentlyContinue') { foreach ($validationError in $extensionValidationErrors) { Write-Verbose "$validationError" # write the validation errors to verbose. # It should be noted that there will almost always be validation errors, # since most extensions will not apply to a given $GitCommand } } # Next we want to create a collection of SteppablePipelines. # These allow us to run the begin/process/end blocks of each Extension. $steppablePipelines = [Collections.ArrayList]::new(@(if ($formatObjectExtensions) { foreach ($ext in $formatObjectExtensions) { $extParams = $ext.ExtensionParameter $extCmd = $ext.ExtensionCommand $scriptCmd = {& $extCmd @extParams} $scriptCmd.GetSteppablePipeline() } })) # Next we need to start any steppable pipelines. # Each extension can break, continue in it's begin block to indicate it should not be processed. $spi = 0 $spiToRemove = @() $beginIsRunning = $false # Walk over each steppable pipeline. :NextExtension foreach ($steppable in $steppablePipelines) { if ($beginIsRunning) { # If beginIsRunning is set, then the last steppable pipeline continued $spiToRemove+=$steppablePipelines[$spi] # so mark it to be removed. } $beginIsRunning = $true # Note that beginIsRunning=$false, try { $steppable.Begin($true) # then try to run begin } catch { $PSCmdlet.WriteError($_) # Write any exceptions as errors } $beginIsRunning = $false # Note that beginIsRunning=$false $spi++ # and increment the index. } # If this is still true, an extenion used 'break', which signals to stop processing of it any subsequent pipelines. if ($beginIsRunning) { $spiToRemove += @(for (; $spi -lt $steppablePipelines.Count; $spi++) { $steppablePipelines[$spi] }) } # Remove all of the steppable pipelines that signaled they no longer wish to run. foreach ($tr in $spiToRemove) { $steppablePipelines.Remove($tr) } } process { $myInv = $MyInvocation if (-not $steppablePipelines) { # If we do not have any steppable pipelines, output the input object unchanged. $InputObject } else { # If we have steppable pipelines, then we have to do a similar operation as we did for begin. $spi = 0 $spiToRemove = @() $processIsRunning = $false # We have to walk thru each steppable pipeline, :NextExtension foreach ($steppable in $steppablePipelines) { if ($processIsRunning) { # if $ProcessIsRunning, the pipeline was skipped with continue. $spiToRemove+=$steppablePipelines[$spi] # and we should add it to the list of pipelines to remove } $processIsRunning = $true # Set $processIsRunning, try { # if ($paramCopy.ContainsKey('InputObject')) { # If InputObject was passed positionally # $steppable.Process() # run process, using no pipelined input. # } else { # otherwise if ($formatObjectExtensions[$spi]) { $steppable.Process([PSObject]$InputObject) # attempt to run process, using $InputObject as the pipelined input. } # } } catch { $err = $_ $PSCmdlet.WriteError($_) # (catch any exceptions and write them as errors). } $processIsRunning = $false # Set $processIsRunning to $false for the next step. } if ($processIsRunning) { # If $ProcessIsRunning was true, the extension used break # which should signal cancellation of all subsequent extensions. $spiToRemove += @(for (; $spi -lt $steppablePipelines.Count; $spi++) { $steppablePipelines[$spi] }) $InputObject # We will also output the inputObject in this case. } # Remove any steppable pipelines we need to remove. foreach ($tr in $spiToRemove) { $steppablePipelines.Remove($tr) } } } end { foreach ($sp in $steppablePipelines) { $sp.End() } } } |