Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JustinGrote/0bcb1a2960eae77aca2d6c5058b944b1 to your computer and use it in GitHub Desktop.
Save JustinGrote/0bcb1a2960eae77aca2d6c5058b944b1 to your computer and use it in GitHub Desktop.
Warn if Format command (Format-Table, etc.) are used in the middle of a pipeline.
using namespace System.Management.Automation.Language
using namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
function IncorrectUseOfFormatCmdletsInMiddleOfPipeline {
<#
.SYNOPSIS
Do not place format-table in the middle of a pipeline
.DESCRIPTION
Detects when Format commands are commonly misused in the middle of a pipeline, causing unexpected results with display objects that dont have the same properties as the original piped object.
.NOTES
None
#>
[CmdletBinding()]
[OutputType([DiagnosticRecord[]])]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.Language.ScriptBlockAst]
$ScriptBlockAst
)
Process {
trap {
$PSCmdlet.ThrowTerminatingError($PSItem)
}
#TODO: Detect additional aliases automatically within the same script?
$formatBadList = @(
'Format-Table'
'ft'
'Format-List'
'fl'
'Format-Wide'
'fw'
)
#Predicate that finds format commands not at the end of a pipeline
$findFormatTable = {
param ([Ast]$Ast)
#We use a "disqualifying" approach to keep this performant.
if ($Ast -isnot [CommandAst]) {return}
[CommandAst]$commandAst = $Ast
if ($commandAst.GetCommandName() -notin $formatBadList) {return}
if ($commandAst.Parent -isnot [PipelineAst]) {return}
[PipelineAst]$parent = $commandAst.Parent
#If the command is not the last element of the pipeline, we have a match
return $parent.PipelineElements[-1] -ne $commandAst
}
[DiagnosticRecord[]]$result = $ScriptBlockAst.FindAll($findFormatTable, $false) | ForEach-Object {
#Emit a warning record for each format table found
[DiagnosticRecord]@{
Message = "$($PSItem.GetCommandName()) should not be used in the middle of a pipeline. It outputs special display objects that are generally incompatible with further pipeline use. Consider using Select-Object instead"
Extent = $PSItem.Extent
RuleName = $PSCmdlet.MyInvocation.InvocationName
Severity = 'Warning'
}
}
return $result
}
}
Export-ModuleMember IncorrectUseOfFormatCmdletsInMiddleOfPipeline
@{
CustomRulePath = '.\IncorrectUseOfFormatCmdletsInMiddleOfPipeline.psm1'
IncludeDefaultRules = $true
IncludeRules = @(
'IncorrectUseOfFormatCmdletsInMiddleOfPipeline'
)
}
# Should Warn
Get-Process | Format-Table | ForEach-Object { $_ }
# Should Warn
Get-Process
| Format-Table
| ForEach-Object { $_ }
# Should Warn
Format-Wide
| Get-Process
| ForEach-Object { $_ }
#OK
Get-Process | ForEach-Object { $_ } | Format-Table
#OK
Get-Process
| ForEach-Object { $_ }
| Format-Table
#OK
Get-Process
| ForEach-Object { $_ }
| Format-Wide
@fflaten
Copy link

fflaten commented Jul 18, 2022

Awesome work! 👍
Tip: You should change to $false in $ScriptBlockAst.FindAll($findFormatTable, $true) as $true will return duplicates. Nested scriptsblock (ex a function) will get it's own run through IncorrectUseOfFormatCmdletsInMiddleOfPipeline

@JustinGrote
Copy link
Author

Awesome work! 👍 Tip: You should change to $false in $ScriptBlockAst.FindAll($findFormatTable, $true) as $true will return duplicates. Nested scriptsblock (ex a function) will get it's own run through IncorrectUseOfFormatCmdletsInMiddleOfPipeline

Good tip! Wasn't sure if nested was required or not, thanks for confirming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment