Skip to content

Instantly share code, notes, and snippets.

@JPRuskin
Created August 24, 2018 14:21
Show Gist options
  • Select an option

  • Save JPRuskin/e9930261a234cbb2f0dfe3c5beaa3746 to your computer and use it in GitHub Desktop.

Select an option

Save JPRuskin/e9930261a234cbb2f0dfe3c5beaa3746 to your computer and use it in GitHub Desktop.
Function to get all commands called in a given script, scriptblock, or command, and display them along with the module they are contained in.
#function Get-ScriptRequirements {
<#
.SYNOPSIS
Gets all calls within a script or scriptblock and outputs name, module and count.
.DESCRIPTION
Uses AST to retrieve all calls in a valid script file, command, or
scriptblock, then outputs the amount of each along with the module
.EXAMPLE
Get-ScriptRequirements -ScriptPath .\testscript.ps1
.EXAMPLE
gci $moduleDir -recurse *.ps1 | Get-ScriptRequirements -ByModule
#>
[CmdletBinding(DefaultParameterSetName='File')]
param(
# Filepath for script to analyse
[Parameter(Mandatory, ValueFromPipelineByPropertyName,ParameterSetName='ScriptPath')]
[ValidateScript({Test-Path $_})]
[Alias('PSPath')]
[IO.FileInfo[]]$ScriptPath,
# Scriptblock to analyse
[Parameter(Mandatory, ParameterSetName='ScriptBlock')]
[ScriptBlock[]]$ScriptBlock,
# Command to analyse
[Parameter(Mandatory, ParameterSetName='CommandName')]
[string[]]$CommandName,
# Identifies private functions from modules passed here
[string[]]$ModuleName,
# Analyses requirements for called commands, as well
[Switch]$Recurse
)
begin {
# BUG: Microsoft / non-script modules cause issue here
if ($CommandName) {
if ($SourceModule = (Get-Command -Name $CommandName).Source) {
$ModuleName = $ModuleName + $SourceModule | Select -Unique
}
}
# Add Private functions from $ModuleName or the source Module
foreach ($Module in $ModuleName) {
if (-not (Get-Module $Module)) {
Import-Module $Module
}
$LoadedModule = Get-Module $Module
&($LoadedModule){Get-Command -Module $Module} | ? Name -notin (Get-Command -Module $Module).Name | %{
$PrivateFunctions += @{$_.Name = $_.Source}
}
Write-Debug "Found '$($PrivateFunctions.Count)' private functions total. Finished with '$($Module)'"
}
}
process {
foreach ($Item in (Get-Variable -Name $PSCmdlet.ParameterSetName).Value) {
try {
switch ($PSCmdlet.ParameterSetName) {
'ScriptPath' {
Write-Verbose "Analysing [$($Item.Name)]"
$Script = [System.Management.Automation.Language.Parser]::ParseFile($Item, [ref]$null, [ref]$null)
}
'ScriptBlock' {
Write-Verbose "Analysing [ScriptBlock]"
$Script = [System.Management.Automation.Language.Parser]::ParseInput($Item, [ref]$null, [ref]$null)
}
'CommandName' {
Write-Verbose "Analysing [$($Item)]"
if (($Command = Get-Command -Name $Item).CommandType -eq 'Alias') {
$Command = Get-Command -Name $Command.ResolvedCommandName
}
if ($Command.ScriptBlock) {
$Script = [System.Management.Automation.Language.Parser]::ParseInput($Command.ScriptBlock, [ref]$null, [ref]$null)
} else {
Write-Warning "$($Command.Name) seems to not be script-based."
return
}
}
}
} catch {
Write-Warning "Failed to parse $($PSCmdlet.ParameterSetName).`n$_"
}
try {
$Functions = $Script.FindAll({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $true)
Write-Verbose "Found $($Functions.Count) calls"
} catch {
Write-Error "Failed to find functions in $($PSCmdlet.ParameterSetName).`n$_"
}
$Functions.GetCommandName() | Group-Object -NoElement | Select Count,Name,@{
Name = 'Module'
Expression = {
try {
if ($_.Name -in $PrivateFunctions.Name) {
$PrivateFunctions."$($_.Name)"
} else {
$Command = Get-Command $_.Name
if ($Command.CommandType -eq 'Alias') {
$Command = Get-Command -Name $Command.ResolvedCommandName
}
$Command.Source
}
} catch {}
}
}
if ($Recurse) {
# Should add some logic to prevent this going "down the hole", so to speak
$Functions.GetCommandName() | Select -Unique | %{
Get-ScriptRequirements -CommandName $_
}
# Also, can we recurse scripts?
}
}
}
#}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment