RVR.ScriptAnalyzerRules.psm1
#Requires -Version 3.0 Function Measure-StoppingAfterThrow { <# .SYNOPSIS A throw command should be followed by a return or exit statement. .DESCRIPTION A throw command should be followed by a return or exit statement. Please see https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/be-careful-with-throw-statements-part-1 for more information. .EXAMPLE Measure-StoppingAfterThrow -ScriptBlockAst $ScriptBlockAst .INPUTS [System.Management.Automation.Language.ScriptBlockAst] .OUTPUTS [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]] .NOTES These articles describe the problem: https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/be-careful-with-throw-statements-part-1 https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/be-careful-with-throw-statements-part-2 You can also have a look at the Examplescript.ps1 included in this module. Found this article to be quite helpfull in explaining the PS Abstract Syntax Tree https://blog.ipswitch.com/how-to-take-advantage-of-powershells-abstract-syntax-tree This article helped me setting up my first PS Script analyzer rule. https://mathieubuisson.github.io/create-custom-rule-psscriptanalyzer/ Thanks to all! #> [CmdletBinding()] [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [System.Management.Automation.Language.ScriptBlockAst] $ScriptBlockAst ) Process { <# Dirty workaround to ignore the child AST blocks. # Why this woraround? #> if($ScriptBlockAst.Parent){ # I'm not interested in the childs for analysis return } [System.Management.Automation.Language.Ast[]]$throws = $ScriptBlockAst.FindAll({$args[0] -is [System.Management.Automation.Language.ThrowStatementAst]}, $true) # Use this array to put the results in. If any found. $Results = @() foreach($throw in $throws){ $exit = $throw.parent.Find({$args[0] -is [System.Management.Automation.Language.ExitStatementAst]}, $true) $return = $throw.parent.Find({$args[0] -is [System.Management.Automation.Language.ReturnStatementAst]}, $true) #if exit exists and startoffset is greater than than throws end ofset (meaning the exit becomes after the throw) if($exit -and ($throw.extent.EndOffset -lt $exit.Extent.StartOffset) ){ # "Found exit after throw. Test passed with flying collors!" } elseif($return -and ($throw.extent.EndOffset -lt $return.Extent.StartOffset)) { # again, looking good } else { $Results += New-Object ` -Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" ` -ArgumentList "$((Get-Help $MyInvocation.MyCommand.Name).Description.Text)",$throw.Extent,$PSCmdlet.MyInvocation.InvocationName,Warning,$Null } } $Results } } # Export-ModuleMember -Function Measure-* |