PSGraph is a PowerShell-first wrapper around the ⚡ QuickGraph / QuikGraph ecosystem.
It lets you build, query and visualise graphs directly from the pipeline without dropping down to C# or external tools. Think LINQ for graphs – but in PowerShell.
Visualization note:
PSGraphnow keeps graph construction, algorithms, Graphviz/DOT, GraphML, and DSM logic. Visual rendering has moved to the siblingPSGraphViewproject.PSGraphno longer serves as a compatibility entry point for Vega, MSAGL, or DSM view rendering.
QuickGraph/QuikGraph gives .NET a battle-tested set of graph data-structures and algorithms (DFS, BFS, Dijkstra, min-cut, spanning-tree, etc.)
PSGraph layers PowerShell ergonomics, discoverability and visualisation on top so that:
- You can pipe any object collection into a graph, specify what constitutes vertices and edges, and explore the relationships immediately.
- Results stay as native objects – ideal for further CLI processing, reporting or automation.
The original goal was to analyse dependencies in IaC workloads, but the module has proven handy for network flows, security events, configuration drift
| What you get | |
|---|---|
| Idiomatic cmdlets | New-Graph, Add-Vertex, Add-Edge, Get-GraphPath, New-DSM, … |
| Ready-made algorithms | All algorithms exposed by QuikGraph are one cmdlet away. |
| Visualization split | Keep Graphviz / GraphML export in PSGraph; use PSGraphView for Vega / MSAGL / DSM rendering. |
| Pipeline-friendly | Import from GraphML, CSV, JSON, Matrix Market; export to Graphviz DOT and GraphML. |
| Test-driven | Over 100 Pester tests ensure every cmdlet does what it says. |
| Cross-platform | Runs anywhere PowerShell 7+ does (Windows, Linux, macOS). |
PSGraph has two validation contours:
- Source-built validation runs .NET and Pester tests from the repository build output.
- Gallery-installed validation installs
PSQuickGraphfromPSGalleryon Linux, Windows, and macOS, imports the installed module, runs smoke checks, and then runs the Pester suite against the installed package.
The gallery-installed contour is defined in .github/workflows/gallery-installed-e2e.yml and is intended to catch packaging, import, and cross-platform issues that source-built tests can miss.
Install-Module -Name PSQuickGraph -Scope CurrentUserCurrent ownership is intentional:
PSGraphowns graph objects, graph algorithms, DSM algorithms, Graphviz DOT export, GraphML interchange, and textual DSM export.PSGraphViewowns the visual renderer implementations and visualization-facing cmdlets such asExport-GraphViewandExport-DSMView.Export-GraphandExport-DSMinPSGraphare no longer the path for visual rendering.
Practical rule of thumb:
- Use
Import-Graph/Export-Graph -Format GraphMLwhen you need a neutral interchange format. - Use
Export-Graph -Format Graphvizwhen you need textual DOT output that stays fully insidePSGraph. - Use
PSGraphViewcmdlets for Vega / MSAGL / DSM visual export.
An example of Cartesian layouts for a node-link diagram of hierarchical data.
These rendering examples assume the sibling PSGraphView module is installed.
$data = (Invoke-WebRequest -Uri https://raw.githubusercontent.com/vega/vega-datasets/refs/heads/main/data/flare.json).Content | ConvertFrom-Json
$index = [object[]]::new($data.count + 1)
$data | % { $index[$_.id] = $_ }
$g = New-Graph
$data | Group-Object -Property parent | % {
$currentGroup = $_
if ([string]::IsNullOrEmpty($currentGroup.name)) {
$currentGroup.Group | % {
Add-Vertex -Vertex $_.name -Graph $g
}
}
else {
$parentLabel = $index[$currentGroup.name].name
$currentGroup.Group | % {
Add-Edge -From $parentLabel -To $_.name -Graph $g
}
}
}
$tempDir = [System.IO.Path]::GetTempPath()
$outFile = Join-Path $tempDir 'x.tree.html'
Export-GraphView -Graph $g -Renderer VegaTreeLayout -As Html -Path $outFileThis rendering path now lives in PSGraphView.
Same graph but using Force Directed layout
$tempDir = [System.IO.Path]::GetTempPath()
$outFile = Join-Path $tempDir 'x.force.html'
Export-GraphView -Graph $g -Renderer VegaForceDirected -As Html -Path $outFileThis rendering path also lives in PSGraphView.
PSGraph includes experimental DSM clustering capabilities with two algorithms:
- Classic (simulated annealing heuristic)
- GraphBased (SCC condensation + topological ordering)
You can tune algorithms via a single -AlgorithmConfig parameter that accepts:
- A strongly typed record (
[PSGraph.DesignStructureMatrix.DsmSimulatedAnnealingConfig]) - A hashtable / ordered dictionary
- A
PSCustomObject
$dsm = New-DSM -Graph (New-Graph | % { Add-Vertex -Graph $_ -Vertex 'A' }) # minimal placeholder
# Build a sample dependency graph
$g = New-Graph
'A','B','C','D' | ForEach-Object { Add-Vertex -Graph $g -Vertex $_ | Out-Null }
Add-Edge -From A -To B -Graph $g | Out-Null
Add-Edge -From B -To C -Graph $g | Out-Null
Add-Edge -From C -To A -Graph $g | Out-Null # cycle
Add-Edge -From C -To D -Graph $g | Out-Null
$dsm = New-DSM -Graph $g
# Run classic clustering (simulated annealing) with a tuned config
$cfg = @{ Times = 2; StableLimit = 2; MaxRepeat = 200; PowCc = 1 }
$result = Start-DSMClustering -Dsm $dsm -ClusteringAlgorithm Classic -AlgorithmConfig $cfg -Detailed
$result.Passes # number of passes performed
$result.BestCost # best coordination cost achieved
$result.CostHistory # cost trajectory$saCfg = [PSGraph.DesignStructureMatrix.DsmSimulatedAnnealingConfig]::new(
PowCc = 1,
PowBid = 0,
PowDep = 0,
Times = 3,
StableLimit = 2,
MaxRepeat = 500,
InitialTemperature = $null,
CoolingRate = 0.92,
MinTemperature = 0.001
)
$result = Start-DSMClustering -Dsm $dsm -ClusteringAlgorithm Classic -AlgorithmConfig $saCfg -Detailed$graphResult = Start-DSMClustering -Dsm $dsm -ClusteringAlgorithm GraphBased -Detailed
$graphResult.CostHistory # single value: cross-SCC edge countTip: For hashtable / PSCustomObject configs, keys are matched case-insensitively to record constructor parameters. Missing values fall back to defaults.
These fields tune the Classic (simulated annealing) DSM clustering. Use a strongly typed DsmSimulatedAnnealingConfig, or supply them via hashtable / PSCustomObject.
| Parameter | Role / Effect | Raise To | Lower To |
|---|---|---|---|
PowCc |
Exponent on cluster size in intra / extra cluster cost (penalises large clusters). | Split oversized clusters more aggressively. | Allow larger clusters. |
PowBid |
Exponent on cluster size in bid denominator ( (inOut^PowDep)/(size^PowBid) ). | Bias toward smaller clusters. | Reduce size pressure. |
PowDep |
Exponent amplifying interaction strength (inOut) in bid numerator. | Emphasise strong coupling. | Downplay link intensity. |
Times |
Move attempts per pass = Times * N. |
More exploration (slower). | Faster passes, less search. |
StableLimit |
Passes w/out improvement before considered stable. | Avoid premature convergence. | Stop earlier. |
MaxRepeat |
Hard cap on passes. | Permit longer searches. | Force early cutoff. |
InitialTemperature |
Starting T; null ⇒ auto-scale to initial cost (adaptive). | Accept more uphill moves early. | Greedier start. |
CoolingRate |
Per-pass decay (T *= CoolingRate). |
Maintain exploration longer. | Freeze faster. |
MinTemperature |
Convergence threshold on T. | Extend late stochastic phase. | Terminate sooner. |
EpochLength |
Moves per annealing epoch; 0 ⇒ auto (Times * N). |
More sampling before each cooling step. | More frequent cooling. |
CoolingSchedule |
Temperature schedule: Geometric, Linear, Logarithmic. |
Control cooling shape. | Use default Geometric for stability. |
InitialAcceptanceProbability |
Target probability for auto-T0 calibration. |
Hotter initial search. | Colder initial search. |
TemperatureCalibrationMoves |
Number of sampled moves for auto-T0. |
Smoother/robust T0 estimate. |
Faster startup. |
RandomSeed |
Optional deterministic seed. | Reproducible runs/tests. | More randomness across runs. |
Heuristics:
- Quick coarse result:
Times=1,CoolingRate=0.90, lowerMaxRepeat. - Higher quality dense graphs:
Times=5+,CoolingRate=0.97..0.99, higherStableLimit. - Discourage giant clusters: raise
PowCc/PowBid. - Emphasise connectivity: raise
PowDep.
Example tuned config:
$saCfg = [pscustomobject]@{
PowCc = 1; PowBid = 1; PowDep = 1;
Times = 5; StableLimit = 3; MaxRepeat = 800;
InitialTemperature = $null; # auto-scale to initial cost
CoolingRate = 0.985; MinTemperature = 0.0005
}
$result = Start-DSMClustering -Dsm $dsm -ClusteringAlgorithm Classic -AlgorithmConfig $saCfg -DetailedLeaving InitialTemperature as $null makes runs scale-aware across different matrix sizes; set a numeric value for strict comparability.
When Start-DSMClustering -Detailed is used with Classic, the extended result now also includes:
StopReason(TemperatureDepleted,StableLimitReached,MaxRepeatReached,EmptyGraph)AcceptedMoves,RejectedMoves,AcceptedWorseMovesAcceptanceRate
For quickly determining whether any path exists between two vertices (without retrieving the actual edge sequence) use Test-GraphPath which leverages strongly connected components and a condensed DAG search under the hood.
$g = New-Graph
'A','B','C','D','E' | ForEach-Object { Add-Vertex -Graph $g -Vertex $_ }
Add-Edge -From A -To B -Graph $g | Out-Null
Add-Edge -From B -To C -Graph $g | Out-Null
Add-Edge -From C -To D -Graph $g | Out-Null
Add-Edge -From A -To E -Graph $g | Out-Null
Test-GraphPath -Graph $g -From A -To D # True
Test-GraphPath -Graph $g -From D -To A # False (directional)
Test-GraphPath -Graph $g -From A -To A # True (trivial)Use Get-GraphPath when you need the actual path (edges) rather than just a boolean.
Jump straight to focused, copy‑paste friendly examples for each major task. All examples assume the module is imported and use concise variable names.
New-Graph– create an empty bidirectional graph (docs/New-Graph.md)New-AdjacencyGraph– create an adjacency-list backed graph (docs/New-AdjacencyGraph.md)Add-Vertex– add (or dedupe) vertices (docs/Add-Vertex.md)Add-Edge– add directed edges with optional tag (docs/Add-Edge.md)Import-Graph– load GraphML, CSV, JSON, or Matrix Market into a new graph; GraphML remains the neutral interchange format (docs/Import-Graph.md)Export-Graph– Graphviz / GraphML export (docs/Export-Graph.md)
Get-GraphPath– shortest path (Dijkstra) between two vertices (docs/Get-GraphPath.md)Test-GraphPath– fast reachability boolean (docs/Test-GraphPath.md)Get-InEdge/Get-OutEdge– incoming / outgoing edge enumeration (docs/Get-InEdge.md,docs/Get-OutEdge.md)Get-GraphTopologicalSort– DAG dependency ordering (docs/Get-GraphTopologicalSort.md)Get-GraphDistanceVector– root-based distance levels (docs/Get-GraphDistanceVector.md)
New-DSM– wrap a graph as a DSM (docs/New-DSM.md)Start-DSMClustering– cluster / partition with SA or graph-based algorithms (docs/Start-DSMClustering.md)Start-DSMSequencing– reorder to expose sources / cycles / sinks (docs/Start-DSMSequencing.md)Export-DSM– text export (docs/Export-DSM.md)
- Build → Query: New-Graph → Add-Vertex / Add-Edge → Get-GraphPath / Get-InEdge
- DAG → Execution Order: New-Graph → Add-Edge → Get-GraphTopologicalSort
- Import → Analyse → Export: Import-Graph → Get-GraphDistanceVector → Export-Graph
- Graph → DSM Insight: New-Graph → Add-* → New-DSM → Start-DSMClustering / Start-DSMSequencing → Export-DSM
Tip: Each linked doc contains multiple scenarios; skim the first example for the minimal pattern, then look for advanced sections (weights, tagging, configs).