Public/Find-FunctionCall.ps1
function Find-FunctionCall { <# .SYNOPSIS For a given function, find what functions it calls. .DESCRIPTION For the purposes of working out dependencies, it may be good to know what a function depends on at the function scale. This command takes a function and builds a tree of functions called by that function. .PARAMETER Name Provide the name of a function to analyse. .PARAMETER Function Provide a function object as input. This will be the output of Get-Command. .PARAMETER Depth Maximum level of nesting to analyse. If this depth is exceeded, a warning will be emitted. .PARAMETER ResolveAlias Specifies to resolve aliases to the aliased command. .INPUTS [System.Management.Automation.FunctionInfo] .OUTPUTS [FunctionCallInfo] This command outputs an object similar to System.Management.Automation.FunctionInfo. Note that this is not a child class of FunctionInfo. .EXAMPLE 'Install-Module' | Get-Command | Find-FunctionCall CommandType Name Version Source ----------- ---- ------- ------ Function Install-Module 2.2.5 PowerShellGet Cmdlet Get-Member 7.0.0.0 Microsoft.PowerShell.Utility Function Get-ProviderName 2.2.5 PowerShellGet Cmdlet Get-Member 7.0.0.0 Microsoft.PowerShell.Utility Function Get-PSRepository 2.2.5 PowerShellGet Cmdlet ForEach-Object 7.2.5.500 Microsoft.PowerShell.Core Function New-ModuleSourceFromPackageSource 2.2.5 PowerShellGet Cmdlet ForEach-Object 7.2.5.500 Microsoft.PowerShell.Core Cmdlet New-Object 7.0.0.0 Microsoft.PowerShell.Utility Cmdlet Write-Output 7.0.0.0 Microsoft.PowerShell.Utility Cmdlet Get-PackageSource 1.4.7 PackageManagement Function Install-NuGetClientBinaries 2.2.5 PowerShellGet Cmdlet Get-Command 7.2.5.500 Microsoft.PowerShell.Core Function Get-ParametersHashtable 2.2.5 PowerShellGet Cmdlet Get-Command 7.2.5.500 Microsoft.PowerShell.Core Cmdlet Where-Object 7.2.5.500 Microsoft.PowerShell.Core For the 'Install-Module' command from the PowerShellGet module, determine the call tree. #> [OutputType([FunctionCallInfo[]])] [CmdletBinding(DefaultParameterSetName = 'FromFunction', PositionalBinding = $false)] param ( [Parameter(ParameterSetName = 'ByName', Mandatory, ValueFromPipeline, Position = 0)] [string]$Name, [Parameter(ParameterSetName = 'FromFunction', Mandatory, ValueFromPipeline, Position = 0)] [Management.Automation.FunctionInfo]$Function, [int]$Depth = 4, [switch]$ResolveAlias, [Parameter(DontShow, ParameterSetName = 'Recursing', Mandatory, ValueFromPipeline)] [IFunctionCallInfo]$CallingFunction, [Parameter(DontShow, ParameterSetName = 'Recursing')] [int]$_CallDepth = 0, [Parameter(DontShow, ParameterSetName = 'Recursing')] [Collections.Generic.HashSet[Management.Automation.FunctionInfo]]$_SeenFunctions = [Collections.Generic.HashSet[Management.Automation.FunctionInfo]]::new() ) process { if ($_CallDepth -ge $Depth) { Write-Warning "Resulting output is truncated as call tree has exceeded the set depth of $Depth." return } if ($PSCmdlet.ParameterSetName -eq 'ByName') { $Function = Get-Command $Name -CommandType Function -ErrorAction Stop } if ($PSCmdlet.ParameterSetName -eq 'Recursing') { $Function = $CallingFunction.Command } else { $CallingFunction = [FunctionCallInfo]$Function } # Returns false if already in set if (-not $_SeenFunctions.Add($Function)) { return } if (-not $_CallDepth) { $CallingFunction } $_CallDepth++ $Def = "function $($Function.Name) {$($Function.Definition)}" $Tokens = @() [void][Management.Automation.Language.Parser]::ParseInput($Def, [ref]$Tokens, [ref]$null) $CommandTokens = $Tokens | Where-Object {$_.TokenFlags -band 'CommandName'} $CalledCommandNames = $CommandTokens.Text | Sort-Object -Unique if (-not $CalledCommandNames) { return } $Resolver = { param ([string[]]$CommandNames, [string]$ModuleName, [switch]$ResolveAlias) foreach ($CommandName in $CommandNames) { try { $ResolvedCommand = Get-Command $CommandName -ErrorAction Stop if ($ResolveAlias -and $ResolvedCommand.CommandType -eq 'Alias') { [FunctionCallInfo]$ResolvedCommand.ResolvedCommand } else { [FunctionCallInfo]$ResolvedCommand } } catch [Management.Automation.CommandNotFoundException] { [UnresolvedFunctionCallInfo]$CommandName $_.ErrorDetails = "Command resolution failed for command '$CommandName'$(if ($ModuleName) {" in module '$ModuleName'"})." Write-Error -ErrorRecord $_ } catch { Write-Error -ErrorRecord $_ } } } [IFunctionCallInfo[]]$CalledCommands = if ($Function.Module) { $Function.Module.Invoke($Resolver, @($CalledCommandNames, $Function.Module.Name, $ResolveAlias)) } else { & $Resolver $CalledCommandNames '' $ResolveAlias } if (-not $CalledCommands) { return } $CalledCommands | ForEach-Object { $_.Depth = $_CallDepth $_.CalledBy = $CallingFunction # Recurse [IFunctionCallInfo[]]$CallsOfCalls = $_ | Where-Object CommandType -eq 'Function' | Find-FunctionCall -Depth $Depth -ResolveAlias:$ResolveAlias -_CallDepth $_CallDepth -_SeenFunctions $_SeenFunctions | Where-Object Name $_ | Write-Output if ($CallsOfCalls) { $_.Calls.AddRange($CallsOfCalls) $CallsOfCalls | Write-Output } } } } |