Skip to content

Commit ec6d097

Browse files
eosforHeyItsGilbert
authored andcommitted
Add article: Analyze Dependencies with PSQuickGraph and PSGraphView
1 parent 8050b5a commit ec6d097

4 files changed

Lines changed: 373 additions & 0 deletions

File tree

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
---
2+
title: "Analyze Dependencies with PSQuickGraph and PSGraphView"
3+
description: "Build a dependency graph from PowerShell objects, trace paths and blast radius, calculate a safe deployment order, and render the same model as diagrams and a design structure matrix."
4+
author: Andrey Vernigora
5+
authors:
6+
- Andrey Vernigora
7+
date: 2026-08-22T00:00:00+00:00
8+
categories:
9+
- Graph
10+
tags:
11+
- powershell
12+
- psquickgraph
13+
- psgraphview
14+
- dependency-graphs
15+
- graphviz
16+
---
17+
18+
PowerShell is excellent at collecting objects. The harder question often comes
19+
one step later: how are those objects related?
20+
21+
A table can tell us that an Orders API uses a database, a message broker, and a
22+
vault. It is much less useful when we need to answer questions such as:
23+
24+
- What will be affected if Azure Service Bus is unavailable?
25+
- Why does the customer-facing application depend on Key Vault?
26+
- In what order should the platform be deployed or migrated?
27+
- Where are the cycles and tightly coupled groups when the model grows?
28+
29+
Those are graph questions. [PSGraph](https://github.com/eosfor/PSGraph) provides
30+
the graph model and algorithms through the `PSQuickGraph` PowerShell module. The
31+
sibling [PSGraphView](https://github.com/eosfor/PSGraphView) module renders those
32+
models as Graphviz, Vega, MSAGL, and design structure matrix views.
33+
34+
This article builds one small platform model and uses it for several jobs. The
35+
point is not the fictional architecture. The point is that the same native
36+
PowerShell objects can support automation, analysis, and documentation without
37+
maintaining three separate models.
38+
39+
## Two modules with different jobs
40+
41+
The naming is worth explaining before installing anything:
42+
43+
| Name | Responsibility |
44+
| --- | --- |
45+
| `PSGraph` | The project and GitHub repository. |
46+
| `PSQuickGraph` | The installable module for graph objects, algorithms, GraphML, Graphviz/DOT export, and DSM analysis. |
47+
| `PSGraphView` | The installable visualization module for Graphviz, Vega, MSAGL, and DSM output. |
48+
49+
`PSQuickGraph` is not the Microsoft Graph API, and it is not the older Graphviz
50+
DSL module named `PSGraph`. Its focus is an object graph that can be queried and
51+
passed through PowerShell pipelines.
52+
53+
The examples below use the current prerelease pair because the renderer split is
54+
new. Pinning the versions makes the article reproducible:
55+
56+
```powershell
57+
Install-PSResource `
58+
-Name PSQuickGraph `
59+
-Version 2.6.0-beta1 `
60+
-Prerelease `
61+
-Scope CurrentUser
62+
63+
Install-PSResource `
64+
-Name PSGraphView `
65+
-Version 0.2.0-beta1 `
66+
-Prerelease `
67+
-Scope CurrentUser
68+
69+
Import-Module PSQuickGraph -RequiredVersion 2.6.0
70+
Import-Module PSGraphView -RequiredVersion 0.2.0
71+
```
72+
73+
If you only need graph construction and algorithms, `PSGraphView` is optional.
74+
75+
## Model a platform with ordinary objects
76+
77+
The sample inventory contains applications, services, workers, shared platform
78+
services, databases, and observability. There is no required vertex class in the
79+
calling code; each item is a normal `PSCustomObject`:
80+
81+
```powershell
82+
$services = @(
83+
[pscustomobject]@{ Name = 'Customer Portal'; Kind = 'Application'; Team = 'Experience' }
84+
[pscustomobject]@{ Name = 'Admin Portal'; Kind = 'Application'; Team = 'Operations' }
85+
[pscustomobject]@{ Name = 'Orders API'; Kind = 'Service'; Team = 'Orders' }
86+
[pscustomobject]@{ Name = 'Inventory API'; Kind = 'Service'; Team = 'Inventory' }
87+
[pscustomobject]@{ Name = 'Billing Worker'; Kind = 'Worker'; Team = 'Billing' }
88+
[pscustomobject]@{ Name = 'Notification Worker'; Kind = 'Worker'; Team = 'Experience' }
89+
[pscustomobject]@{ Name = 'Azure Service Bus'; Kind = 'Platform'; Team = 'Platform' }
90+
[pscustomobject]@{ Name = 'Orders DB'; Kind = 'Data'; Team = 'Orders' }
91+
[pscustomobject]@{ Name = 'Inventory DB'; Kind = 'Data'; Team = 'Inventory' }
92+
[pscustomobject]@{ Name = 'Billing DB'; Kind = 'Data'; Team = 'Billing' }
93+
[pscustomobject]@{ Name = 'Key Vault'; Kind = 'Platform'; Team = 'Platform' }
94+
[pscustomobject]@{ Name = 'Application Insights'; Kind = 'Observability'; Team = 'Platform' }
95+
)
96+
```
97+
98+
Dependencies are data too. In this model an edge points from a consumer to its
99+
dependency: `Orders API -> Orders DB` means that the API depends on the database.
100+
101+
```powershell
102+
$dependencies = @(
103+
[pscustomobject]@{ From = 'Customer Portal'; To = 'Orders API'; Reason = 'HTTPS' }
104+
[pscustomobject]@{ From = 'Customer Portal'; To = 'Inventory API'; Reason = 'HTTPS' }
105+
[pscustomobject]@{ From = 'Admin Portal'; To = 'Orders API'; Reason = 'HTTPS' }
106+
[pscustomobject]@{ From = 'Admin Portal'; To = 'Inventory API'; Reason = 'HTTPS' }
107+
[pscustomobject]@{ From = 'Orders API'; To = 'Orders DB'; Reason = 'SQL' }
108+
[pscustomobject]@{ From = 'Orders API'; To = 'Inventory API'; Reason = 'HTTPS' }
109+
[pscustomobject]@{ From = 'Orders API'; To = 'Azure Service Bus'; Reason = 'AMQP' }
110+
[pscustomobject]@{ From = 'Orders API'; To = 'Key Vault'; Reason = 'Secrets' }
111+
[pscustomobject]@{ From = 'Inventory API'; To = 'Inventory DB'; Reason = 'SQL' }
112+
[pscustomobject]@{ From = 'Inventory API'; To = 'Key Vault'; Reason = 'Secrets' }
113+
[pscustomobject]@{ From = 'Billing Worker'; To = 'Azure Service Bus'; Reason = 'AMQP' }
114+
[pscustomobject]@{ From = 'Billing Worker'; To = 'Billing DB'; Reason = 'SQL' }
115+
[pscustomobject]@{ From = 'Billing Worker'; To = 'Key Vault'; Reason = 'Secrets' }
116+
[pscustomobject]@{ From = 'Notification Worker'; To = 'Azure Service Bus'; Reason = 'AMQP' }
117+
[pscustomobject]@{ From = 'Notification Worker'; To = 'Key Vault'; Reason = 'Secrets' }
118+
[pscustomobject]@{ From = 'Customer Portal'; To = 'Application Insights'; Reason = 'Telemetry' }
119+
[pscustomobject]@{ From = 'Admin Portal'; To = 'Application Insights'; Reason = 'Telemetry' }
120+
[pscustomobject]@{ From = 'Orders API'; To = 'Application Insights'; Reason = 'Telemetry' }
121+
[pscustomobject]@{ From = 'Inventory API'; To = 'Application Insights'; Reason = 'Telemetry' }
122+
[pscustomobject]@{ From = 'Billing Worker'; To = 'Application Insights'; Reason = 'Telemetry' }
123+
[pscustomobject]@{ From = 'Notification Worker'; To = 'Application Insights'; Reason = 'Telemetry' }
124+
)
125+
```
126+
127+
Build the graph in two passes. Explicitly adding vertices preserves isolated
128+
services; adding only edges would omit objects that currently have no
129+
relationships.
130+
131+
```powershell
132+
$serviceByName = @{}
133+
$graph = New-Graph
134+
135+
foreach ($service in $services) {
136+
$serviceByName[$service.Name] = $service
137+
$vertex = Add-Vertex -Graph $graph -Vertex $service -PassThru
138+
$vertex.Metadata |
139+
Add-Member -NotePropertyName Team -NotePropertyValue $service.Team
140+
}
141+
142+
foreach ($dependency in $dependencies) {
143+
Add-Edge `
144+
-Graph $graph `
145+
-From $serviceByName[$dependency.From] `
146+
-To $serviceByName[$dependency.To] `
147+
-Tag $dependency.Reason |
148+
Out-Null
149+
}
150+
```
151+
152+
The sample graph contains 12 vertices and 21 directed edges. The wrapper vertex
153+
retains the original object in `OriginalObject`, so analysis results can return
154+
to normal PowerShell processing at any time.
155+
156+
## First view: the dependency map
157+
158+
`PSQuickGraph` exports the model as DOT; `PSGraphView` asks Graphviz to lay it out
159+
and return SVG. Keeping these steps separate is useful: algorithms can run on a
160+
server that never renders an image, while a documentation build can apply its
161+
own visual style.
162+
163+
```powershell
164+
$dot = Export-Graph `
165+
-Graph $graph `
166+
-Format Graphviz `
167+
-GraphScript { @{ rankdir = 'LR'; bgcolor = 'white' } } `
168+
-VertexScript {
169+
@{
170+
shape = 'Record'
171+
style = 'filled'
172+
label = "{{ {0} | {1} }}" -f $_.Name, $_.Kind
173+
}
174+
}
175+
176+
Export-GraphvizView `
177+
-InputObject $dot `
178+
-Renderer Dot `
179+
-As Svg `
180+
-OutputPath ./platform-dependencies.svg
181+
```
182+
183+
![A consumer-to-dependency graph of the sample platform. Each record shows the component name and kind.](/images/articles/psgraphview-platform-dependencies.svg)
184+
185+
A drawing is already useful for a design review, but the graph becomes more
186+
valuable when it answers operational questions.
187+
188+
## Immediate dependencies and dependents
189+
190+
Because the edge direction is explicit, outgoing and incoming edges answer two
191+
different questions:
192+
193+
```powershell
194+
$orders = $graph.Vertices | Where-Object Label -EQ 'Orders API'
195+
196+
# What does Orders API require?
197+
Get-OutEdge -Graph $graph -Vertex $orders |
198+
ForEach-Object { $_.Target.OriginalObject }
199+
200+
# What calls Orders API directly?
201+
Get-InEdge -Graph $graph -Vertex $orders |
202+
ForEach-Object { $_.Source.OriginalObject }
203+
```
204+
205+
The result is still the inventory object, not display text. It can be grouped by
206+
team, joined with ownership data, exported to CSV, or used to open incidents.
207+
208+
## Explain a dependency with a path
209+
210+
Knowing that two components are connected is not always enough. `Get-GraphPath`
211+
returns the edge sequence that explains the relationship:
212+
213+
```powershell
214+
$from = $graph.Vertices | Where-Object Label -EQ 'Customer Portal'
215+
$to = $graph.Vertices | Where-Object Label -EQ 'Key Vault'
216+
217+
$path = Get-GraphPath -Graph $graph -From $from -To $to
218+
219+
@($path.Source.Label) + $path[-1].Target.Label
220+
```
221+
222+
For this model the result is:
223+
224+
```text
225+
Customer Portal -> Orders API -> Key Vault
226+
```
227+
228+
This is useful in change reviews and incident response because it provides an
229+
explanation, not just a Boolean answer.
230+
231+
## Calculate blast radius
232+
233+
Suppose Azure Service Bus is unavailable. Every vertex that can reach it through
234+
consumer-to-dependency edges is transitively affected:
235+
236+
```powershell
237+
$serviceBus = $graph.Vertices |
238+
Where-Object Label -EQ 'Azure Service Bus'
239+
240+
$affected = $graph.Vertices |
241+
Where-Object Label -NE $serviceBus.Label |
242+
Where-Object {
243+
Test-GraphPath -Graph $graph -From $_ -To $serviceBus
244+
} |
245+
Sort-Object Label
246+
247+
$affected.OriginalObject
248+
```
249+
250+
![Azure Service Bus and every direct or transitive consumer are marked with a bold border and a warning marker.](/images/articles/psgraphview-service-bus-impact.svg)
251+
252+
The result includes both direct consumers and applications affected indirectly:
253+
254+
```text
255+
Admin Portal
256+
Billing Worker
257+
Customer Portal
258+
Notification Worker
259+
Orders API
260+
```
261+
262+
`Get-InEdge` finds immediate consumers. `Test-GraphPath` also finds portals that
263+
depend on Service Bus indirectly through Orders API.
264+
265+
## Derive a deployment order
266+
267+
The same graph can become an execution plan. With consumer-to-dependency edges,
268+
reversing the topological order puts dependencies before their consumers:
269+
270+
```powershell
271+
$deploymentOrder = Get-GraphTopologicalSort `
272+
-Graph $graph `
273+
-Reverse
274+
275+
$deploymentOrder |
276+
Select-Object -ExpandProperty OriginalObject |
277+
Select-Object Name, Kind, Team
278+
```
279+
280+
The result starts with shared dependencies and ends with applications:
281+
282+
```text
283+
Azure Service Bus
284+
Orders DB
285+
Key Vault
286+
Inventory DB
287+
Billing DB
288+
Application Insights
289+
Inventory API
290+
Billing Worker
291+
Orders API
292+
Notification Worker
293+
Customer Portal
294+
Admin Portal
295+
```
296+
297+
Topological sorting is appropriate only for a directed acyclic graph. If two
298+
services depend on each other, the sort fails rather than inventing a safe
299+
order. That failure is useful evidence: the cycle needs an explicit migration
300+
strategy or an architectural change.
301+
302+
## When a node-link diagram becomes too dense
303+
304+
Arrows work well for a dozen components. They become a hairball for a hundred. A
305+
design structure matrix (DSM) represents the same edges as cells: the row is the
306+
consumer and the column is its dependency.
307+
308+
`PSQuickGraph` creates and sequences the matrix; `PSGraphView` renders it:
309+
310+
```powershell
311+
$dsm = New-DSM -Graph $graph
312+
$sequencedDsm = Start-DSMSequencing `
313+
-Dsm $dsm `
314+
-LoopDetectionMethod Condensation
315+
316+
Export-DSMView `
317+
-SequencedDsm $sequencedDsm `
318+
-Renderer DsmVegaMatrix `
319+
-As Json `
320+
-Path ./dependency-matrix.json
321+
```
322+
323+
![A sequenced design structure matrix of the same platform. Filled cells map consumers to dependencies without crossing edges.](/images/articles/psgraphview-dependency-matrix.svg)
324+
325+
The Vega renderer produces an interactive matrix whose row and column labels can
326+
be highlighted on hover. The static image above uses the same Vega specification
327+
for the article page. For larger models, `Start-DSMClustering` can group strongly
328+
related components before rendering. That makes the matrix useful for finding
329+
candidate service boundaries, not merely documenting the current state.
330+
331+
## Other scenarios for the same pattern
332+
333+
Only the data collection step changes between domains. The graph workflow
334+
remains: collect objects, create stable vertices, add directed relationships,
335+
ask questions, then choose a view.
336+
337+
- **Security events:** connect processes, users, hosts, files, and network
338+
destinations to reconstruct a suspicious chain.
339+
- **Network policy:** turn accepted and rejected firewall flows into host and
340+
port relationships.
341+
- **Infrastructure as code:** model semantic Bicep dependencies and validate
342+
cross-resource relationships, as shown in
343+
[Validate Azure Resource Relationships with PSRule and PowerShell Graphs](/articles/2026-07-24-validate-azure-resource-relationships-with-psrule-and-powershell-graphs/).
344+
- **Web diagnostics:** connect pages to scripts, APIs, and third-party origins
345+
discovered through Chrome DevTools Protocol.
346+
- **Execution graphs:** use topological order to evaluate a computation graph,
347+
as shown in
348+
[Explore Micrograd with Verso and PowerShell](/articles/2026-07-24-explore-micrograd-with-verso-and-powershell/).
349+
350+
These examples look different on screen, but the useful questions are the same:
351+
what depends on this, how did we get there, what order is valid, and where is the
352+
system too tightly coupled?
353+
354+
## Takeaways
355+
356+
`PSQuickGraph` is most useful when a diagram is not the final product. The graph
357+
can drive impact reports, validation, deployment ordering, and incident
358+
analysis. `PSGraphView` then turns that same tested model into the representation
359+
that fits the audience: a familiar node-link diagram, an interactive view, or a
360+
dense DSM.
361+
362+
The practical pattern is small:
363+
364+
1. Keep vertices as domain objects with stable names or IDs.
365+
2. Decide and document the edge direction.
366+
3. Use algorithms before reaching for visualization.
367+
4. Render from the same model instead of maintaining diagrams by hand.
368+
369+
Once relationships become first-class data, PowerShell can do much more than
370+
draw boxes and arrows.

0 commit comments

Comments
 (0)