Public/Find-PowerShellSymbol.ps1
function Find-PowerShellSymbol { <# .SYNOPSIS Search PowerShell files for function, parameter, and variable definitions/usages using AST. .DESCRIPTION Accepts file paths (with wildcards/recursion) or pipeline input (from dir/Get-ChildItem), searches for a (partial) symbol name, and returns an array of PSObjects with file, line number, type, name, and line content. .PARAMETER SymbolName The (partial) symbol name to search for (case-insensitive). .PARAMETER Path One or more file paths (wildcards/recursion supported). .PARAMETER FunctionsOnly Search only for functions (definitions and usages). .PARAMETER VarsOnly Search only for variables (usages). .PARAMETER ParamsOnly Search only for parameters (definitions/usages). .EXAMPLE Find-PowerShellSymbol -SymbolName 'Get-Data' -Path .\*.ps1 -FunctionsOnly Find-PowerShellSymbol -SymbolName 'foo' -Path .\*.ps1 -VarsOnly dir .\src -Recurse -Filter *.ps1 | Find-PowerShellSymbol -SymbolName 'bar' -ParamsOnly #> [CmdletBinding()] param( [Parameter(Position = 0)] [string]$SymbolName, [Parameter(Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('FullName')] [string[]]$Path, [Parameter()] [switch]$FunctionsOnly, [Parameter()] [switch]$VarsOnly, [Parameter()] [switch]$ParamsOnly, [Parameter()] [string]$DataType ) begin { $results = @() $symbolPattern = if ($SymbolName) { [regex]::Escape($SymbolName) } else { ".*" } $searchFunctions = -not $VarsOnly -and -not $ParamsOnly -or $FunctionsOnly $searchVars = -not $FunctionsOnly -and -not $ParamsOnly -or $VarsOnly $searchParams = -not $FunctionsOnly -and -not $VarsOnly -or $ParamsOnly } process { $files = @() if ($Path) { foreach ($p in $Path) { if (Test-Path $p -PathType Leaf) { $files += $p } elseif (Test-Path $p -PathType Container) { $files += Get-ChildItem -Path $p -Recurse -Filter *.ps1 -File | ForEach-Object { $_.FullName } } else { $files += Get-ChildItem -Path $p -Recurse -Filter *.ps1 -File -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName } } } } elseif ($PSItem -and $PSItem.PSIsContainer -eq $false -and $PSItem.FullName) { $files += $PSItem.FullName } $files = $files | Sort-Object -Unique foreach ($file in $files) { if (-not ($file -match '\.ps1$')) { continue } if (-not (Test-Path $file)) { continue } try { $content = Get-Content $file -Raw -ErrorAction Stop } catch { continue } $lines = $content -split "`r?`n" $ast = [System.Management.Automation.Language.Parser]::ParseInput($content, [ref]$null, [ref]$null) if ($searchFunctions) { # Function Definitions $defs = $ast.FindAll({ param($node) $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and ($SymbolName -eq $null -or $node.Name -match $symbolPattern) }, $true) foreach ($def in $defs) { $lineNum = $def.Extent.StartLineNumber $line = $lines[$lineNum - 1] $results += [PSCustomObject][ordered]@{ LineNumber = $lineNum Type = 'FunctionDefinition' Name = $def.Name Line = $line File = $file } } # Function Usages $calls = $ast.FindAll({ param($node) $node -is [System.Management.Automation.Language.CommandAst] -and $node.CommandElements.Count -gt 0 -and ($SymbolName -eq $null -or $node.CommandElements[0].Value -match $symbolPattern) }, $true) foreach ($call in $calls) { $lineNum = $call.Extent.StartLineNumber $line = $lines[$lineNum - 1] $results += [PSCustomObject][Ordered]@{ LineNumber = $lineNum Type = 'FunctionUsage' Name = $call.CommandElements[0].Value Line = $line File = $file } } } if ($searchParams) { # Parameter Definitions/Usages $params = $ast.FindAll({ param($node) $node -is [System.Management.Automation.Language.ParameterAst] -and ($SymbolName -eq $null -or $node.Name.VariablePath.UserPath -match $symbolPattern) }, $true) foreach ($param in $params) { $typeName = $null if ($param.StaticType -and $param.StaticType.Name) { $typeName = $param.StaticType.Name } elseif ($param.Attributes) { $typeAttr = $param.Attributes | Where-Object { $_.TypeName -and $_.TypeName.Name } if ($typeAttr) { $typeName = $typeAttr[0].TypeName.Name } } if ($DataType -and $typeName) { if ($typeName -notlike "*$DataType*") { continue } } elseif ($DataType) { continue } $lineNum = $param.Extent.StartLineNumber $line = $lines[$lineNum - 1] $results += [PSCustomObject][Ordered]@{ LineNumber = $lineNum Type = 'Parameter' Name = $param.Name.VariablePath.UserPath DataType = $typeName Line = $line File = $file } } } if ($searchVars) { # Variable Usages $vars = $ast.FindAll({ param($node) $node -is [System.Management.Automation.Language.VariableExpressionAst] -and ($SymbolName -eq $null -or $node.VariablePath.UserPath -match $symbolPattern) }, $true) foreach ($var in $vars) { $typeName = $null if ($var.StaticType -and $var.StaticType.Name) { $typeName = $var.StaticType.Name } if ($DataType -and $typeName) { if ($typeName -notlike "*$DataType*") { continue } } elseif ($DataType) { continue } $lineNum = $var.Extent.StartLineNumber $line = $lines[$lineNum - 1] $results += [PSCustomObject][Ordered]@{ LineNumber = $lineNum Type = 'Variable' Name = $var.VariablePath.UserPath DataType = $typeName Line = $line File = $file } } } } } end { $results } } |