|
| 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 | + |
| 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 | + |
| 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 | + |
| 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