#requires -Module Reflection [CmdletBinding()] param( # The path to a script or name of a command $Command, # If you want to include private functions from a module, make sure it's imported, and pass the ModuleInfo here [System.Management.Automation.PSModuleInfo]$ModuleScope = $( Get-Module $Command -ListAvailable -ErrorAction SilentlyContinue | Get-Module -ErrorAction SilentlyContinue ), [switch]$Recurse, [switch]$Modules ) function Resolve-Command { [CmdletBinding()] param( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)] [string]$Name ) process { Write-Debug "ENTER: Resolve-Command $Name" while (($command = Get-Command $Name -EA SilentlyContinue).CommandType -eq "Alias") { $Name = $command.Definition } $command Write-Debug "EXIT: Resolve-Command $Name" } } $FindDependencies = { [CmdletBinding()] param($Command) Write-Progress "Searching Dependencies in $Command" Write-Debug "FindDependencies $Command" function Resolve-Command { [CmdletBinding()] param( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)] [string]$Name ) process { Write-Progress "Resolving Command $Name" Write-Debug " ENTER: Resolve-Command $Name" while (($command = Get-Command $Name -EA SilentlyContinue).CommandType -eq "Alias") { $Name = $command.Definition } $command Write-Debug " EXIT: Resolve-Command $Name" } } function Get-ParseResults { param( # The script or file path to parse [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("Path", "PSPath")] $Script ) process { Write-Progress "Parsing $Script" Write-Debug " ENTER: Get-ParseResults $Script" $ParseErrors = $null $Tokens = $null if ($Script | Test-Path -ErrorAction SilentlyContinue) { Write-Debug " Parse $Script as Path" $AST = [System.Management.Automation.Language.Parser]::ParseFile(($Script | Convert-Path), [ref]$Tokens, [ref]$ParseErrors) } elseif ($Script -is [System.Management.Automation.FunctionInfo]) { Write-Debug " Parse $Script as Function" $String = "function $($Script.Name) { $($Script.Definition) }" $AST = [System.Management.Automation.Language.Parser]::ParseInput($String, [ref]$Tokens, [ref]$ParseErrors) } else { Write-Debug " Parse $Script as String" $AST = [System.Management.Automation.Language.Parser]::ParseInput([String]$Script, [ref]$Tokens, [ref]$ParseErrors) } Write-Debug " EXIT: Get-ParseResults $Script" [PSCustomObject]@{ PSTypeName = "System.Management.Automation.Language.ParseResults" ParseErrors = $ParseErrors Tokens = $Tokens AST = $AST } } } Resolve-Command $Command | Get-ParseResults | & { process { Write-Progress "Searching for commands in ScriptBlock" ([System.Management.Automation.Language.Ast]$_.AST).FindAll( { param($Ast) Where-Object -Input $Ast -FilterScript { $_ -is "System.Management.Automation.Language.CommandAst" } }, $true) } } | # Errors will appear for commands you don't have available Resolve-Command -Name { $_.CommandElements[0].Value } -ErrorAction SilentlyContinue -ErrorVariable +global:MissingCommands } $Commands = @( Resolve-Command $Command ) $ScannedCommands = @() $global:MissingCommands = @() do { foreach ($Command in $Commands | Where-Object { $_ -notin $ScannedCommands -and $_ -isnot [System.Management.Automation.CmdletInfo] }) { $Commands += @( if ($Command.Module) { Write-Progress "Parsing $Command in module $($Command.Module)" Write-Debug "Parsing $Command in module $($Command.Module)" & $Command.Module $FindDependencies $Command } else { Write-Debug "Parsing $Command" & $FindDependencies $Command } ) $ScannedCommands += $Command } } while ($Recurse -and ($Commands | Where-Object { $_ -notin $ScannedCommands -and $_ -isnot [System.Management.Automation.CmdletInfo] })) if ($Modules) { $Commands | Sort-Object Source, Name -Unique | Group-Object Source | Select-Object @{N = "Module"; e = { $_.Name } }, @{N = "Used Commands"; E = { $_.Group } } } else { $Commands | Select-Object -Unique } if ($MissingCommands) { Write-Warning "Missing source for some (probably internal) commands: $(($MissingCommands | Select-Object -Expand TargetObject -Unique) -join ', ')" Write-Verbose ($MissingCommands | Sort-Object -Unique | Out-String) $global:MissingCommands = $MissingCommands }