Commands/PipeScript/Search-PipeScript.ps1
function Search-PipeScript { <# .Synopsis Searches PowerShell and PipeScript ScriptBlocks .Description Searches the contents of PowerShell and PipeScript ScriptBlocks, files, and text. Search-PipeScript can search using an -ASTCondition -or -ASTType or with a -RegularExpression. .Example Search-PipeScript -ScriptBlock { $a $b $c "text" } -AstType Variable .LINK Update-PipeScript #> [OutputType('Search.PipeScript.Result')] [Alias('Search-ScriptBlock')] param( # The input to search. # This can be a `[string]`, `[ScriptBlock]`, `[IO.FileInfo]`, or `[Ast]`. [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('ScriptBlock','Text')] [ValidateScript({ $validTypeList = [System.String],[System.Management.Automation.ScriptBlock],[System.IO.FileInfo],[System.Management.Automation.Language.Ast] $thisType = $_.GetType() $IsTypeOk = $(@( foreach ($validType in $validTypeList) { if ($_ -as $validType) { $true;break } })) if (-not $isTypeOk) { throw "Unexpected type '$(@($thisType)[0])'. Must be 'string','scriptblock','System.IO.FileInfo','System.Management.Automation.Language.Ast'." } return $true })] $InputObject, # The AST Condition. # These conditions will apply when the input is a `[ScriptBlock]`, `[Ast]`, or PowerShell file. [Parameter(ValueFromPipelineByPropertyName)] [Alias('ASTDelegate')] [ScriptBlock[]] $AstCondition, # A shortname for the abstract syntax tree types. [Parameter(ValueFromPipelineByPropertyName)] [ValidateScript({ $validTypeList = [System.String],[System.Text.RegularExpressions.Regex],[System.String[]],[System.Text.RegularExpressions.Regex[]] $thisType = $_.GetType() $IsTypeOk = $(@( foreach ($validType in $validTypeList) { if ($_ -as $validType) { $true;break } })) if (-not $isTypeOk) { throw "Unexpected type '$(@($thisType)[0])'. Must be 'string','regex','string[]','regex[]'." } return $true })] $AstType, # One or more regular expressions to match. [Parameter(ValueFromPipelineByPropertyName)] [Alias('RegEx','Regexes','RegularExpressions')] [ValidateScript({ $validTypeList = [System.String],[System.Text.RegularExpressions.Regex],[System.String[]],[System.Text.RegularExpressions.Regex[]] $thisType = $_.GetType() $IsTypeOk = $(@( foreach ($validType in $validTypeList) { if ($_ -as $validType) { $true;break } })) if (-not $isTypeOk) { throw "Unexpected type '$(@($thisType)[0])'. Must be 'string','regex','string[]','regex[]'." } return $true })] $RegularExpression, # If set, will search nested script blocks. [Alias('SearchNestedScriptBlock')] [switch] $Recurse ) process { $ScriptBlock = $null $Text = $null # If the input was a file if ($InputObject -is [IO.FileInfo]) { $inputCommand = # get the resolved command $ExecutionContext.SessionState.InvokeCommand.GetCommand( $InputObject.Fullname, 'ExternalScript,Application') # If the command was an external script if ($inputCommand -is [Management.Automation.ExternalScriptInfo]) { # Use it's ScriptBlock $ScriptBlock = $inputCommand.ScriptBlock } # If the command was an application, and it looks like PipeScript elseif ( $inputCommand -is [Management.Automation.ApplicationInfo] -and $inputCommand.Source -match '\.ps$' ) { # Load the file text $text = [IO.File]::ReadAllText($inputCommand.Source) # and create a script block. $scriptBlock = [ScriptBlock]::Create($text) } # Otherwise else { # Read the file contents as text. $text = [IO.File]::ReadAllText($inputCommand.Source) } } $ast = $null # If the inputObject was a [ScriptBlock] if ($InputObject -is [scriptblock]) { $scriptBlock = $InputObject # set $ScriptBlock $ast = $scriptBlock.Ast # and set $ast to the $ScriptBlock`s AST. # Reset $text to the ScriptBlock contents. $Text = "$scriptBlock" } elseif ($InputObject -is [Management.Automation.Language.Ast]) { # If the input was an [AST] # set the AST. $ast = $InputObject $text = $ast.Extent.ToString() } # If the InputObject is a string if ($InputObject -is [string]) { $Text = $InputObject # set $Text. } # If we have a ScriptBlock if ($ast) { # If we have an ASTType to find if ($AstType) { foreach ($astTypeName in $AstType) { # See if it's a real type $realAstType = foreach ($potentialType in $AstType, "Management.Automation.Language.$AstType", "Management.Automation.Language.${AstType}AST" ) { if ($potentialType -as [type]) { $potentialType -as [type]; break } } # If it was a real type, but in the wrong namespace if ($realAstType -and $realAstType.Namespace -eq 'Management.Automation.Language') { Write-Error "'$astType' is not an AST type" # error and continue. continue } # Set the search condition $condition = if ($realAstType) { # If there was a real type, search for it. [ScriptBlock]::Create('param($ast) $ast -is [' + $realAstType.FullName + ']') } elseif ($astType -is [Regex]) { [scriptblock]::Create(( 'param($ast) $ast.GetType().Name -match ([Regex]::New(' + $astType.ToString().Replace("'", "''") + "','" + $astType.Options + "','" + $(if ($AstType.MatchTimeout -lt 0) { '00:00:05' } else { $AstType.MatchTimeout.ToString() }) + '))' )) } elseif ($astType -as [regex]) { [ScriptBlock]::Create('param($ast) $ast.GetType().Name -match "'+ $astType +'"') } else { [ScriptBlock]::Create('param($ast) $ast.GetType().Name -like "*' + $astType +'*"') } # Add this condition to the list of conditions. $AstCondition += $condition } } # If we have any AST conditions if ($AstCondition) { foreach ($condition in $AstCondition) { # Find all of the results. $Ast.FindAll($condition, ($Recurse -as [bool])) | & { process { $in = $this = $_ [PSCustomObject][Ordered]@{ 'InputObject' = $inputObject 'Result' = & { $_ } 'Expression' = $condition 'ScriptBlock' = & { $ScriptBlock } 'PSTypeName' = "Search.PipeScript.Result" } } } } } } if ($text) { if ($RegularExpression) { foreach ($regex in $RegularExpression) { $realRegex = if ($regex -is [regex]) { $regex } else { [Regex]::new($regex) } $realRegex.Matches($text) | & { process { $in = $this = $_ [PSCustomObject][Ordered]@{ 'InputObject' = $InputObject 'Result' = & { $_ } 'Expression' = $realRegex 'RegularExpression' = & { $realRegex } 'PSTypeName' = "Search.PipeScript.Result" } } } } } } } } |