diff --git a/PITCHME.md b/PITCHME.md index 023e193..8cc6124 100644 --- a/PITCHME.md +++ b/PITCHME.md @@ -1,22 +1,20 @@ # PSGraph -PSGraph is a PowerShell module that allows you to script the generation of graphs using the GraphViz engine. It makes it easy to produce data driven visualizations. +PSGraph is a PowerShell module that lets you script the generation of graphs using the GraphViz engine. It makes it easy to produce data-driven visualizations straight from PowerShell objects. -![basic graph](https://kevinmarquette.github.io/img/basic.png) +![basic graph](images/firstGraph.png) --- -### Install GraphViz from the Chocolatey repo - - Register-PackageSource -Name Chocolatey -ProviderName Chocolatey -Location http://chocolatey.org/api/v2/ - Find-Package graphviz | Install-Package -ForceBootstrap - -### Install PSGraph from the Powershell Gallery +### Install PSGraph from the PowerShell Gallery Find-Module PSGraph | Install-Module + Import-Module PSGraph -### Import Module +### Install GraphViz - Import-Module PSGraph + # Chocolatey on Windows (nuget.org fallback for non-admin installs), + # Homebrew on macOS, your distro's package manager on Linux + Install-GraphViz --- @@ -40,79 +38,144 @@ Then we can render the graph as an image. Edge -From middle -To end } | Export-PSGraph -ShowGraph - -![firstGraph](http://psgraph.readthedocs.io/en/latest/images/firstGraph.png) +![firstGraph](images/firstGraph.png) --- ### Data driven graphs -The real fun starts when they are data driven +The real fun starts when they are data driven — every example below pulls its shape from real PowerShell objects, not hand-typed node names. --- -### Example: Server farm data +### Example: Server farm topology -Imagine you wanted to diagram a server farm. +Describe how tiers of servers relate to each other. + + $WebServer = 1..2 | ForEach-Object {"Web_$_"} + $APIServer = 1..2 | ForEach-Object {"API_$_"} + $DatabaseServer = 1..2 | ForEach-Object {"DB_$_"} + + graph servers { + node @{shape='box'} + edge LoadBalancer -To $WebServer + edge $WebServer -To $APIServer + edge $APIServer -To AvailabilityGroup + edge AvailabilityGroup -To $DatabaseServer + } | Export-PSGraph -ShowGraph -I'm generating example servers here: +![servers](images/pitchme-serverfarm.png) - # Server counts - $WebServerCount = 2 - $APIServerCount = 2 - $DatabaseServerCount = 2 +--- - # Server lists - $WebServer = 1..$WebServerCount | % {"Web_$_"} - $APIServer = 1..$APIServerCount | % {"API_$_"} - $DatabaseServer = 1..$DatabaseServerCount | % {"DB_$_"} +### Example: Database schema + +`Record`/`Row`/`Cells` build GraphViz's HTML-like table nodes — a natural fit for entity-relationship diagrams. `Cells -PortProperty` names a row so `Edge` can point straight at it. + + $customers = @( + [pscustomobject]@{ Column='Id'; Type='int PK' } + [pscustomobject]@{ Column='Name'; Type='nvarchar' } + [pscustomobject]@{ Column='Email'; Type='nvarchar' } + ) + $orders = @( + [pscustomobject]@{ Column='Id'; Type='int PK' } + [pscustomobject]@{ Column='CustomerId'; Type='int FK' } + [pscustomobject]@{ Column='Total'; Type='money' } + ) + + graph schema { + Record Customers -Rows ($customers | Cells -PortProperty Column) + Record Orders -Rows ($orders | Cells -PortProperty Column) + Edge 'Orders:CustomerId' -To 'Customers:Id' + } | Export-PSGraph -ShowGraph -But you could source these from AD or your CMDB +![schema](images/pitchme-schema.png) --- -### Example: Server farm graph +### Example: Live process tree -Then describe how those lists of servers are related +Graph what's actually running right now, color-coded by memory use via `New-NodeAttributeSet`. - graph servers { - node -Default @{shape='box'} - edge LoadBalancer -To $WebServer - edge $WebServer -To $APIServer - edge $APIServer -To AvailabilityGroup - edge AvailabilityGroup -To $DatabaseServer + $all = Get-Process + $procs = $all | Where-Object { + $_.Id -ne 0 -and $_.Parent -and ($all.Id -contains $_.Parent.Id) + } + + graph processTree @{rankdir='LR'} { + $procs | ForEach-Object { + $color = if ($_.WorkingSet64 -gt 200MB) {'orangered'} + elseif ($_.WorkingSet64 -gt 50MB) {'gold'} + else {'palegreen'} + $attrs = New-NodeAttributeSet -Style filled -FillColor $color + $attrs.label = $_.ProcessName + node $_.Id $attrs + } + edge $procs -FromScript {$_.Parent.Id} -ToScript {$_.Id} } | Export-PSGraph -ShowGraph +![process tree](images/pitchme-processtree.png) + --- -### Example: Server farm graph image +### Example: Windows service dependencies + +`Get-Service` already exposes each service's dependency graph — PSGraph just draws it. + + $services = Get-Service | Where-Object RequiredServices + + graph serviceDeps @{rankdir='LR'} { + node @{shape='box'} + $services | ForEach-Object { + edge $_.Name -To $_.RequiredServices.Name + } + } | Export-PSGraph -ShowGraph -![servers](https://kevinmarquette.github.io/img/servers.png) +![service dependencies](images/pitchme-servicedeps.png) --- -### Example: Project structures +### Example: PowerShell module dependencies -![files structure](http://psgraph.readthedocs.io/en/latest/images/filesSmall.png) +Dogfooding: walk installed modules' own `RequiredModules` and graph them. + + $modules = Get-Module -ListAvailable | Where-Object RequiredModules + + graph moduleDeps @{rankdir='LR'} { + node @{shape='box'} + $modules | ForEach-Object { + edge $_.Name -To $_.RequiredModules.Name + } + } | Export-PSGraph -ShowGraph + +![module dependencies](images/pitchme-moduledeps.png) --- -### Example: Parent and child processes +### Example: Export to any format in one line -![related processes](http://psgraph.readthedocs.io/en/latest/images/processSmall.png) +`Export-PSGraph` ships a format-specific alias for every supported output — `svgGraph`, `pngGraph`, `pdfGraph`, `dotGraph`, and more. + + $dot = graph g { edge hello world } + + $dot | svgGraph -Destination out.svg + $dot | pngGraph -Destination out.png + $dot | pdfGraph -Destination out.pdf + +![formats](images/pitchme-formats.png) --- -### Example: Network connections +### More examples -![network connections](http://psgraph.readthedocs.io/en/latest/images/networkConnection.png) +* [Project structure](images/filesSmall.png) — a folder tree walked with `Get-ChildItem` +* [GraphViz gallery recreations](https://github.com/themodulecollective/PSGraph/blob/main/docs/Example-Gallery.md) — clusters, entity-relation diagrams, finite automata +* Full command reference and more scripted examples: [psgraph.readthedocs.io](http://psgraph.readthedocs.io) --- ### What will you graph? -For more information - -* [psgraph.readthedocs.io](http://psgraph.readthedocs.io) -* [github.com/kevinmarquette/psgraph](https://github.com/kevinmarquette/psgraph) -* [kevinmarquette.github.io](https://kevinmarquette.github.io) \ No newline at end of file +* [psgraph.readthedocs.io](http://psgraph.readthedocs.io) — full documentation +* [github.com/themodulecollective/PSGraph](https://github.com/themodulecollective/PSGraph) — source, issues, and this fork's changelog +* `Get-Help about_PSGraph` — conceptual overview, right from your PowerShell prompt diff --git a/PITCHME.yaml b/PITCHME.yaml deleted file mode 100644 index 2d75733..0000000 --- a/PITCHME.yaml +++ /dev/null @@ -1 +0,0 @@ -theme : black \ No newline at end of file diff --git a/PSGraph/en-US/about_PSGraph.help.txt b/PSGraph/en-US/about_PSGraph.help.txt new file mode 100644 index 0000000..4667ed7 --- /dev/null +++ b/PSGraph/en-US/about_PSGraph.help.txt @@ -0,0 +1,133 @@ +TOPIC + about_PSGraph + +SHORT DESCRIPTION + PSGraph is a small DSL (Domain Specific Language) for generating + GraphViz graphs from PowerShell. It turns PowerShell objects and + collections into GraphViz's DOT text, then optionally renders that + text into an image. + +LONG DESCRIPTION + PSGraph does not parse anything of its own. `Graph { ... }` is + ordinary PowerShell: the `{ ... }` is a scriptblock literal, and + `Graph` runs it directly with `& $ScriptBlock`. Every command called + inside that block - `Node`, `Edge`, `Rank`, `SubGraph`, and the rest - + is just an ordinary function call that happens to emit a line of DOT + text as its pipeline output. `Graph` collects that output, wraps it + in `digraph { ... }`, and returns the whole thing as a string array. + + Because indentation and cluster nesting have to be shared between + calls that have no direct reference to one another, PSGraph tracks + them with a small amount of module-scoped state (current indent + depth, the active subgraph list) that gets set when a `Graph` block + opens and cleared when it closes. You do not need to manage this + yourself; it only matters if you are reading the source. + + The result of a `Graph` block is plain text in the DOT language, so + it can be inspected, saved, or piped straight into `Export-PSGraph` + to render an image with GraphViz. + + COMMANDS + + Graph (alias DiGraph) + The top-level container. Opens a graph, runs the scriptblock + that defines its contents, and closes it. + + Node + Declares one or more nodes and their attributes. + + Edge + Declares an edge (or a chain, or a cross-product of edges) + between nodes. + + SubGraph + A graph nested inside another graph, for clustering related + nodes together. + + Rank + Places the given nodes at the same level in the layout. + + Inline + Passes raw DOT text through untouched, for GraphViz syntax + PSGraph's DSL does not model directly. + + Record, Row, Cells, Entity + Build GraphViz's HTML-like table nodes. `Record` is the table; + `Row` is one hand-built row; `Cells` converts a whole collection + of pipeline objects into rows at once; `Entity` converts a + single object into a `Record` automatically. + + New-NodeAttributeSet (alias NodeAttributes) + New-EdgeAttributeSet (alias EdgeAttributes) + Build a case-correct GraphViz attribute hashtable from + parameters, with tab completion for shape, color, and font + values - GraphViz attribute names and values are case-sensitive, + and these catch mistakes before they reach GraphViz. + + Export-PSGraph (aliases pngGraph, svgGraph, pdfGraph, dotGraph, ...) + Shells out to GraphViz's `dot` executable to render DOT source + into an image. Each supported output format also has its own + alias, e.g. `$dot | svgGraph -Destination out.svg`. + + Show-PSGraph + Shorthand for `Export-PSGraph -ShowGraph`. + + Set-NodeFormatScript + Sets a scriptblock used to reformat every node/edge ID PSGraph + emits, for cases where your source data's names need cleanup + before they become DOT identifiers. + + Install-GraphViz + Installs the native GraphViz binaries PSGraph shells out to + (Chocolatey on Windows, with a nuget.org fallback for non-admin + installs via -Scope CurrentUser; Homebrew on macOS). + + Run `Get-Help -Full` for any of these, e.g. + `Get-Help Record -Full`, for parameters and examples. + + A NOTE ON THE 'Node' COMMAND NAME + + The bare `Node` command name can collide with Node.js-related + tooling or autoload behavior present on some systems. This has been + discussed upstream for years with no consensus reached, and is an + intentionally deferred design question, not an oversight: renaming + or aliasing `Node` would be a breaking change to the DSL and will + only happen as part of a deliberate major-version change with its + own migration notes. + +EXAMPLES + # A minimal graph, captured to a variable + $dot = Graph { + Edge hello world + } + + # The same graph, rendered and shown immediately + Graph { + Edge hello world + } | Export-PSGraph -ShowGraph + + # Data-driven: build nodes and edges from real objects + $processes = Get-Process | Select-Object -First 10 + Graph @{rankdir = 'LR'} { + node @{shape = 'box'} + node $processes -NodeScript {$_.Id} -Attributes @{label = {$_.ProcessName}} + edge $processes -FromScript {$_.Parent.Id} -ToScript {$_.Id} + } | Show-PSGraph + +SEE ALSO + Full command reference and more examples: + http://psgraph.readthedocs.io + + Source, issues, and this fork's changelog: + https://github.com/themodulecollective/PSGraph + + GraphViz's own documentation, for DOT-language and attribute details + PSGraph does not wrap in a dedicated parameter: + https://graphviz.org/documentation/ + +KEYWORDS + PSGraph + GraphViz + DOT + Graph + DSL diff --git a/docs/Command-Cells.md b/docs/Command-Cells.md new file mode 100644 index 0000000..3fa958c --- /dev/null +++ b/docs/Command-Cells.md @@ -0,0 +1,38 @@ +# Cells + +`Cells` converts pipeline objects into GraphViz HTML-like table rows (`...`), one row per object. It exists to pair with `Record`: pipe any collection straight into a table node without hand-building `Row` calls for each property. + + Get-Process | Select-Object -First 3 Name, Id | + Cells | Record Processes | Show-PSGraph + +By default the first object's property names become a bold header row, and every property after that becomes one `` per row. + +## Cells [-Properties [string[]]] [-ExcludeProperty [string[]]] + +Filter which properties become columns, and in what order. Both accept wildcards. + + Get-Process | Cells -Properties Name, Id, CPU + + Get-Process | Cells -ExcludeProperty Handle*, WS + +## Cells [-PortProperty [string]] + +Names one column's `` with a `PORT` attribute, so `Edge` can target that specific cell instead of the whole record. + + Get-Process | Select-Object -First 3 Name, Id | + Cells -PortProperty Id | Record Processes -Name Procs + + Graph { + Record Procs -Rows (Get-Process | Select-Object -First 3 Name, Id | Cells -PortProperty Id) + Node Other + Edge Other -To Procs:1234 + } + +## Cells [-Align [LEFT|CENTER|RIGHT]] [-HtmlEncode] [-NoHeader] + +* `-Align` — text alignment applied to every ``. Defaults to `LEFT`. +* `-HtmlEncode` — HTML-encodes each cell's value, for data that may contain `<>&`. +* `-NoHeader` — skips the header row that's otherwise built from the first object's property names. + + Get-Process | Select-Object -First 5 Name, Id, CPU | + Cells -Align CENTER -NoHeader | Record ProcessList diff --git a/docs/Command-Entity.md b/docs/Command-Entity.md index 7349b71..2bf51d4 100644 --- a/docs/Command-Entity.md +++ b/docs/Command-Entity.md @@ -13,7 +13,7 @@ The `Entity` command takes an object and maps it into a `Record`. This turned ou Entity $object } | Show-PSGraph -![An entity showing a PSCustomObject](/img/entitytypename.png) +![An entity showing a PSCustomObject](images/entitytypename.png) ## Entity [object] -Show [enum] @@ -29,7 +29,7 @@ Here is the same object showing the values. Entity $object -Name 'Person' -Show Value } | Show-PSGraph -![An entity showing the object values](/img/entityvalue.png) +![An entity showing the object values](images/entityvalue.png) The entity will automatically name each row with the property name. This will allow you to draw edges directly to them. I have a more complex example at the end of this article that shows this in action. diff --git a/docs/Command-Record.md b/docs/Command-Record.md index 20db454..7aaafb3 100644 --- a/docs/Command-Record.md +++ b/docs/Command-Record.md @@ -19,7 +19,7 @@ This is the most common way to define a record with a list of values. This will produce a node that looks like this: -![single node record object](/img/record.png) +![single node record object](images/record.png) Under the covers, this is a node object. The command takes care of all the attributes and HTML label formating for you. Because this is a `Node`, you can created edges to it like you would any other node. diff --git a/docs/Command-Row.md b/docs/Command-Row.md index c9361a0..75affc1 100644 --- a/docs/Command-Row.md +++ b/docs/Command-Row.md @@ -44,7 +44,7 @@ You can name a row like you name a node. By giving a row a name, we can target i Edge Table1:Row3 -to Table2:Row2 } | Show-PSGraph -![Two nodes with cross edges to rows](/img/recordedge.png) +![Two nodes with cross edges to rows](images/recordedge.png) If the label is a simple word with no spaces or symbols, the row will use that as the default row name. If you start injecting custom HTML into your row, then there will not be a default row name. diff --git a/docs/graphviz.md b/docs/graphviz.md index e69de29..f3fd1cd 100644 --- a/docs/graphviz.md +++ b/docs/graphviz.md @@ -0,0 +1,30 @@ +# GraphViz Documentation + +PSGraph is a thin PowerShell layer over [GraphViz](http://graphviz.org/) — every command in this module ultimately emits text in GraphViz's DOT language, and `Export-PSGraph`/`Show-PSGraph` shell out to GraphViz's `dot` executable to render that text into an image. When you need a feature this module doesn't wrap in a dedicated command, GraphViz's own documentation is the source of truth. + +## Useful upstream references + +* [Graphviz.org](http://graphviz.org/) — project home. +* [DOT language reference](https://graphviz.org/doc/info/lang.html) — the text format PSGraph generates. +* [Node, Edge, and Graph attributes](https://graphviz.org/doc/info/attrs.html) — every attribute name/value `Node`, `Edge`, `Graph`, and `SubGraph` accept in their `-Attributes` hashtables. +* [Node shapes](https://graphviz.org/doc/info/shapes.html) — including the HTML-like table labels `Record`/`Row`/`Cells`/`Entity` build for you. +* [Layout engines](https://graphviz.org/docs/layouts/) — `Export-PSGraph -LayoutEngine` accepts `dot`, `neato`, `circo`, `fdp`, `sfdp`, `twopi`, and a few legacy aliases (`SpringModelSmall`, etc.) kept for backward compatibility. +* [Command line / graph gallery](https://graphviz.org/gallery/) — the source for the recreated examples in `Example-Gallery.md`. + +## Falling back to raw DOT + +Any attribute or construct GraphViz supports but PSGraph doesn't have a dedicated parameter for can still be set through a plain hashtable, since `-Attributes` accepts arbitrary key/value pairs: + + graph g { + node A @{ shape = 'box'; peripheries = 3 } + } + +For syntax PSGraph's DSL doesn't model at all (a construct with no hashtable-attribute equivalent), use `Inline` to pass raw DOT text straight through: + + graph g { + inline 'rankdir=LR' + } + +## Installing the GraphViz binaries + +PSGraph's `Install-GraphViz` command installs the native `dot` binary GraphViz itself ships (Chocolatey on Windows with a nuget.org fallback for non-admin installs via `-Scope CurrentUser`, Homebrew on macOS). See [Command-Install-GraphViz.md](Command-Install-GraphViz.md) for details, or install GraphViz yourself through your platform's package manager (e.g. `apt-get install graphviz` on Debian/Ubuntu) if you'd rather not use PSGraph's installer. diff --git a/docs/images/entitytypename.png b/docs/images/entitytypename.png new file mode 100644 index 0000000..1d9bc2b Binary files /dev/null and b/docs/images/entitytypename.png differ diff --git a/docs/images/entityvalue.png b/docs/images/entityvalue.png new file mode 100644 index 0000000..6dc99c3 Binary files /dev/null and b/docs/images/entityvalue.png differ diff --git a/docs/images/pitchme-formats.png b/docs/images/pitchme-formats.png new file mode 100644 index 0000000..8546385 Binary files /dev/null and b/docs/images/pitchme-formats.png differ diff --git a/docs/images/pitchme-moduledeps.png b/docs/images/pitchme-moduledeps.png new file mode 100644 index 0000000..13af400 Binary files /dev/null and b/docs/images/pitchme-moduledeps.png differ diff --git a/docs/images/pitchme-processtree.png b/docs/images/pitchme-processtree.png new file mode 100644 index 0000000..df38e13 Binary files /dev/null and b/docs/images/pitchme-processtree.png differ diff --git a/docs/images/pitchme-schema.png b/docs/images/pitchme-schema.png new file mode 100644 index 0000000..31fd068 Binary files /dev/null and b/docs/images/pitchme-schema.png differ diff --git a/docs/images/pitchme-serverfarm.png b/docs/images/pitchme-serverfarm.png new file mode 100644 index 0000000..b38c50f Binary files /dev/null and b/docs/images/pitchme-serverfarm.png differ diff --git a/docs/images/pitchme-servicedeps.png b/docs/images/pitchme-servicedeps.png new file mode 100644 index 0000000..269a69a Binary files /dev/null and b/docs/images/pitchme-servicedeps.png differ diff --git a/docs/images/record.png b/docs/images/record.png new file mode 100644 index 0000000..5b19d30 Binary files /dev/null and b/docs/images/record.png differ diff --git a/docs/images/recordedge.png b/docs/images/recordedge.png new file mode 100644 index 0000000..511977e Binary files /dev/null and b/docs/images/recordedge.png differ diff --git a/docs/index.md b/docs/index.md index f5459c8..eb794f6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ # PSGraph Docs -PSDeploy uses ReadTheDocs to host our documentation. This allows us to keep our docs in the repository, without the various limitations that come with the built in GitHub repo wiki. +PSGraph uses ReadTheDocs to host our documentation. This allows us to keep our docs in the repository, without the various limitations that come with the built in GitHub repo wiki. diff --git a/mkdocs.yml b/mkdocs.yml index 0d20b8d..cb0cbcc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,6 +15,7 @@ pages: - Rank: Command-Rank.md - Record: Command-Record.md - Row: Command-Row.md + - Cells: Command-Cells.md - Entity: Command-Entity.md - New-NodeAttributeSet: Command-New-NodeAttributeSet.md - New-EdgeAttributeSet: Command-New-EdgeAttributeSet.md diff --git a/readme.md b/readme.md index eeb3e06..78536a4 100644 --- a/readme.md +++ b/readme.md @@ -5,11 +5,33 @@ PSGraph is a helper module implemented as a DSL (Domain Specific Language) for generating GraphViz graphs. The goal is to make it easier to generate graphs using Powershell. The DSL adds these commands that are explained below. +**Basics** + * [graph](http://psgraph.readthedocs.io/en/latest/Command-Graph/) * [edge](http://psgraph.readthedocs.io/en/latest/Command-Edge/) * [node](http://psgraph.readthedocs.io/en/latest/Command-Node/) * [subgraph](http://psgraph.readthedocs.io/en/latest/Command-SubGraph/) * [rank](http://psgraph.readthedocs.io/en/latest/Command-Rank/) +* [inline](http://psgraph.readthedocs.io/en/latest/Command-Inline/) + +**Table-style nodes** + +* [record](http://psgraph.readthedocs.io/en/latest/Command-Record/) +* [row](http://psgraph.readthedocs.io/en/latest/Command-Row/) +* [cells](http://psgraph.readthedocs.io/en/latest/Command-Cells/) +* [entity](http://psgraph.readthedocs.io/en/latest/Command-Entity/) + +**Attribute sets** + +* [New-NodeAttributeSet](http://psgraph.readthedocs.io/en/latest/Command-New-NodeAttributeSet/) (alias `NodeAttributes`) +* [New-EdgeAttributeSet](http://psgraph.readthedocs.io/en/latest/Command-New-EdgeAttributeSet/) (alias `EdgeAttributes`) +* [Set-NodeFormatScript](http://psgraph.readthedocs.io/en/latest/Command-Set-NodeFormatScript/) + +**Rendering** + +* [Export-PSGraph](http://psgraph.readthedocs.io/en/latest/Command-Export-PSGraph/) — also available as format-specific aliases (`svgGraph`, `pngGraph`, `pdfGraph`, `dotGraph`, ...) +* [Show-PSGraph](http://psgraph.readthedocs.io/en/latest/Command-Show-PSGraph/) +* [Install-GraphViz](http://psgraph.readthedocs.io/en/latest/Command-Install-GraphViz/) ## What is GraphViz? @@ -169,6 +191,8 @@ PSGraph supports Windows PowerShell 5.1+ and PowerShell 7+, on Windows, Linux, a # distro's package manager, e.g. `apt-get install graphviz`) Install-GraphViz +Once imported, `Get-Help about_PSGraph` gives a conceptual overview of the DSL from inside PowerShell, and `Get-Help -Full` (e.g. `Get-Help Record -Full`) covers any individual command in more depth than this readme. + # Generating a graph image I am still working out the workflow for this, but for now just do this.