Last active
October 16, 2025 14:26
-
-
Save JPRuskin/4c3b2d1442b42b7a79e03cf9066d5cb4 to your computer and use it in GitHub Desktop.
A class and function to output a basic dependency graph for installed Chocolatey packages.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.Synopsis | |
Contains a class and function to output dependencies for one or all packages on a given system. | |
.Description | |
Contains a PackageDependencyChain class that will find dependencies for locally installed, or CCR available, packages. | |
This can be used manually, or a tree can be shown via the Get-ChocolateyDependencies function. | |
.Example | |
.\Get-ChocolateyDependencies.ps1 | |
# Should just output a not-very-pretty dependency tree for all installed packages. | |
.Example | |
. .\Get-ChocolateyDependencies.ps1 # Dot-sourcing or pasting the file into a terminal | |
Get-ChocolateyDependencies -Id vmwareworkstation | |
# Displays a tree for a given package. | |
.Example | |
. .\Get-ChocolateyDependencies.ps1 # Dot-sourcing or pasting the file into a terminal | |
[PackageDependencyChain]::new("vcredist2015").OutDependencies() | |
# Lists a deduped list of total dependencies a given package has. | |
#> | |
class PackageDependencyChain { | |
[string]$Id | |
#[Nuget.Versioning.VersionRange]$Version # Ignore this for now | |
[PackageDependencyChain[]]$Dependencies | |
PackageDependencyChain ($Id) { | |
$NuspecPath = "$env:ChocolateyInstall\lib\$Id\$Id.nuspec" | |
if (Test-Path $NuspecPath) { | |
[xml]$Nuspec = Get-Content $NuspecPath | |
$Namespace = [Xml.XmlNamespaceManager]::new($Nuspec.NameTable) | |
$Namespace.AddNamespace("nuspec", $Nuspec.package.xmlns) | |
$this.Id = $Nuspec.package.metadata.id | |
$this.Dependencies = $Nuspec.SelectNodes("//nuspec:dependency", $Namespace).id | |
} else { | |
$QueryString = "Id eq '$Id' and IsLatestVersion" | |
$QueryUrl = 'https://community.chocolatey.org/api/v2/Packages()?$filter={0}' -f [uri]::EscapeUriString($queryString) | |
[xml]$Nuspec = Invoke-WebRequest $QueryUrl -UseBasicParsing # Not actually a nuspec | |
$this.Id = $Nuspec.feed.entry.title.'#text' | |
$this.Dependencies = $Nuspec.feed.entry.properties.Dependencies.ForEach{ $_.Split(":")[0] } | |
} | |
} | |
[string] DisplayTree([int]$Depth = 0, [switch]$Last) { | |
$LastItemChar, $MoreItemsChar = [char]0x2514, [char]0x251C | |
$BranchType = if ($Last) { | |
$LastItemChar | |
} else { | |
$MoreItemsChar | |
} | |
$StringOutput = if ($Depth) { | |
(" " * 4 * ($Depth - 1)) + $BranchType + "$([char]0x2500)$([char]0x2500) " + $this.Id + "`n" | |
} else { | |
$this.Id + "`n" | |
} | |
foreach ($Dependency in $this.Dependencies) { | |
$StringOutput += $Dependency.DisplayTree($Depth + 1, $(($this.Dependencies.Count - 1) -eq $this.Dependencies.IndexOf($Dependency))) | |
} | |
# Fill in gaps between wrapped nested rows | |
# $StringOutput = $StringOutput.Split("`n") | |
# foreach ($Line in $StringOutput) { | |
# if (($WrapIndex = $Line.IndexOf($MoreItemsChar)) -ge 0) { | |
# foreach ($LineNumber in ($StringOutput.IndexOf($Line)+1)..$StringOutput.Count) { | |
# Write-Verbose "Checking line '$LineNumber': '$($StringOutput[$LineNumber])'" -Verbose | |
# if (($StringOutput[$LineNumber].ToCharArray())[$WrapIndex] -in @($LastItemChar, $MoreItemsChar)) { | |
# Write-Verbose "Found closing char on '$_'" -Verbose | |
# break | |
# } | |
# $StringOutput[$LineNumber] = -join([char[]]($StringOutput[$LineNumber])[0..($WrapIndex-1)] + '|' + ([char[]]$StringOutput[$LineNumber])[($WrapIndex+1)..$StringOutput[$LineNumber].Length]) | |
# } | |
# } | |
# } | |
return $StringOutput -join "`n" | |
} | |
[string[]] OutDependencies() { | |
[string[]]$Packages = foreach ($Dependency in $this.Dependencies) { | |
$Dependency.OutDependencies() | |
} | |
return $Packages + $this.Id | Select-Object -Unique | |
} | |
} | |
function Get-ChocolateyDependencies { | |
<# | |
.Synopsis | |
Shows a tree of Chocolatey packages and their dependencies | |
#> | |
[CmdletBinding()] | |
param( | |
# If specified, only tests a specific package | |
[ArgumentCompleter({ | |
param($1, $2, $WordToMatch, $4, $5) | |
Get-ChildItem $env:ChocolateyInstall\lib | ? BaseName -Like "$WordToMatch*" | % BaseName | |
if ($WordToMatch) { | |
Invoke-RestMethod -UseBasicParsing -Uri "https://community.chocolatey.org/json/JsonApi?invoke&action=GetSuggestions&SearchTerm=$WordToMatch" | ForEach-Object { | |
$_.PackageId | |
} | |
} | |
} | |
)] | |
[string]$Id, | |
# If passed, doesn't list packages that are dependencies on the top layer | |
[switch]$SkipTopLevelDependencies | |
) | |
process { | |
if ($Id) { | |
([PackageDependencyChain]$Id).DisplayTree(0, $false) | |
} else { | |
$Packages = [PackageDependencyChain[]](Get-ChildItem $env:ChocolateyInstall\lib | % BaseName) | |
if ($SkipTopLevelDependencies) { | |
$Packages.Where{ $_.Id -notin $_.Dependencies.Id }.DisplayTree(0, $false) # May need to tune this for nesting | |
} else { | |
$Packages.DisplayTree(0, $false) | |
} | |
} | |
} | |
} | |
if ($MyInvocation.InvocationName -ne ".") { | |
Get-ChocolateyDependencies | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment