From 1cdd3a61a2e6ba62d871b02aaf7529220cb674e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Rodr=C3=ADguez?= Date: Sun, 13 Sep 2026 21:29:07 +0100 Subject: [PATCH] feat!: publish as IntuneGraphKit, with a tag-driven Gallery release `IntuneGraph` on the PowerShell Gallery belongs to an unrelated module by another author - one that uploads, assigns and removes Intune apps - so the README's `Install-Module IntuneGraph` installed that instead of this read-only tool. A Gallery package name has to equal the module name, so the module is renamed IntuneGraphKit. Cmdlet names, the graph.json format, the MCP server name and the repository keep the IntuneGraph name. With the name settled, the rest of #5: - manifest: CompatiblePSEditions, PSEdition_* tags, IconUri, a Description matching the repository description, and a freshly generated GUID - build.ps1 -Task Publish, reading the key from PSGALLERY_API_KEY only - release.yml: a v* tag re-runs CI, checks the tag matches ModuleVersion, then publishes - CI covers Windows PowerShell 5.1, which CONTRIBUTING already claimed - a Pester check on the metadata the Gallery listing shows Refs #5 --- .github/workflows/ci.yml | 19 +++-- .github/workflows/release.yml | 44 ++++++++++ .gitignore | 4 +- CHANGELOG.md | 21 +++++ CONTRIBUTING.md | 33 ++++++-- README.md | 7 +- SECURITY.md | 4 +- build.ps1 | 22 ++++- docs/discoverability.md | 24 +++++- docs/fixtures.md | 2 +- docs/img/icon.png | Bin 0 -> 7303 bytes docs/mcp.md | 7 +- site/src/pages/index.astro | 4 +- .../Assets/template.html | 0 .../deviceManagement/assignmentFilters.json | 0 .../configurationPolicies.json | 0 .../deviceManagement/deviceHealthScripts.json | 0 .../deviceManagementScripts.json | 0 .../DemoData/manifest.json | 0 .../v1.0/deviceAppManagement/mobileApps.json | 0 .../deviceCompliancePolicies.json | 0 .../deviceConfigurations.json | 0 .../v1.0/deviceManagement/managedDevices.json | 0 .../DemoData/v1.0/groups.json | 0 .../members.json | 0 .../members.json | 0 .../members.json | 0 .../members.json | 0 .../members.json | 0 .../members.json | 0 .../members.json | 0 .../IntuneGraphKit.psd1} | 14 +++- .../IntuneGraphKit.psm1} | 0 .../Private/DataSource.ps1 | 0 .../Private/Emit.ps1 | 0 .../Private/Fetch.ps1 | 0 .../Private/GraphModel.ps1 | 0 .../Private/Mcp.ps1 | 0 .../Private/Normalize.ps1 | 0 .../Private/Query.ps1 | 0 .../Private/Util.ps1 | 0 .../Public/Connect-IntuneGraph.ps1 | 0 .../Public/Disconnect-IntuneGraph.ps1 | 0 .../Public/Export-IntuneGraph.ps1 | 0 .../Public/Find-IntuneOrphan.ps1 | 0 .../Public/Get-IntuneBlastRadius.ps1 | 0 .../Public/Get-IntuneGraphNode.ps1 | 0 .../Public/Get-IntuneTarget.ps1 | 0 .../Public/Import-IntuneGraph.ps1 | 0 .../Public/Show-IntuneGraph.ps1 | 0 .../Public/Start-IntuneGraphMcp.ps1 | 0 tests/IntuneGraph.Tests.ps1 | 20 ++++- tests/New-ContosoFixtures.ps1 | 2 +- tools/icon/New-IntuneGraphIcon.ps1 | 75 ++++++++++++++++++ tools/mcp/mcp-server.ps1 | 2 +- 55 files changed, 268 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 docs/img/icon.png rename src/{IntuneGraph => IntuneGraphKit}/Assets/template.html (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/beta/deviceManagement/assignmentFilters.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/beta/deviceManagement/configurationPolicies.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/beta/deviceManagement/deviceHealthScripts.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/beta/deviceManagement/deviceManagementScripts.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/manifest.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/deviceAppManagement/mobileApps.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/deviceManagement/deviceCompliancePolicies.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/deviceManagement/deviceConfigurations.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/deviceManagement/managedDevices.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000001/members.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000002/members.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000003/members.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000004/members.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000005/members.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000006/members.json (100%) rename src/{IntuneGraph => IntuneGraphKit}/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000007/members.json (100%) rename src/{IntuneGraph/IntuneGraph.psd1 => IntuneGraphKit/IntuneGraphKit.psd1} (67%) rename src/{IntuneGraph/IntuneGraph.psm1 => IntuneGraphKit/IntuneGraphKit.psm1} (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/DataSource.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/Emit.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/Fetch.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/GraphModel.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/Mcp.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/Normalize.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/Query.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Private/Util.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Connect-IntuneGraph.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Disconnect-IntuneGraph.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Export-IntuneGraph.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Find-IntuneOrphan.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Get-IntuneBlastRadius.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Get-IntuneGraphNode.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Get-IntuneTarget.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Import-IntuneGraph.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Show-IntuneGraph.ps1 (100%) rename src/{IntuneGraph => IntuneGraphKit}/Public/Start-IntuneGraphMcp.ps1 (100%) create mode 100644 tools/icon/New-IntuneGraphIcon.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27fb0ad..45e5843 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,36 +5,43 @@ on: branches: [ main ] pull_request: branches: [ main ] + # The release workflow calls this, so a tag is only ever published off a green run. + workflow_call: jobs: test: strategy: fail-fast: false matrix: - os: [ windows-latest, ubuntu-latest ] + include: + - { os: windows-latest, shell: pwsh } # PowerShell 7 + - { os: windows-latest, shell: powershell } # Windows PowerShell 5.1 - the manifest's floor + - { os: ubuntu-latest, shell: pwsh } runs-on: ${{ matrix.os }} + defaults: + run: + shell: ${{ matrix.shell }} steps: - uses: actions/checkout@v4 + # -SkipPublisherCheck: Windows ships Pester 3.4 signed with a different + # certificate, and without it the 5.x install refuses to replace it. - name: Install PowerShell modules - shell: pwsh run: | Set-PSRepository PSGallery -InstallationPolicy Trusted - Install-Module Pester -MinimumVersion 5.0.0 -Force -Scope CurrentUser + Install-Module Pester -MinimumVersion 5.0.0 -Force -Scope CurrentUser -SkipPublisherCheck Install-Module PSScriptAnalyzer -Force -Scope CurrentUser - name: PSScriptAnalyzer - shell: pwsh run: ./build.ps1 -Task Analyze - name: Pester (fixtures only - no tenant needed) - shell: pwsh run: ./build.ps1 -Task Test - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: - name: test-results-${{ matrix.os }} + name: test-results-${{ matrix.os }}-${{ matrix.shell }} path: testResults.xml if-no-files-found: ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..54d61a8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,44 @@ +name: Release + +# Pushing a v* tag publishes that version to the PowerShell Gallery - but only after +# the full CI matrix passes again on the tagged commit, and only if the tag matches the +# manifest. A Gallery version can be unlisted but its number can never be reused, so +# everything that can be checked is checked before the one step that cannot be undone. + +on: + push: + tags: [ 'v*' ] + +permissions: + contents: read + +concurrency: + group: release + cancel-in-progress: false + +jobs: + ci: + uses: ./.github/workflows/ci.yml + + publish: + needs: ci + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # The tag reaches the script through env, never interpolated into it. + - name: Check the tag matches ModuleVersion + shell: pwsh + env: + TAG: ${{ github.ref_name }} + run: | + $manifest = Import-PowerShellDataFile ./src/IntuneGraphKit/IntuneGraphKit.psd1 + if ($env:TAG -ne "v$($manifest.ModuleVersion)") { + throw "Tag '$env:TAG' does not match ModuleVersion $($manifest.ModuleVersion). Nothing was published." + } + + - name: Publish to the PowerShell Gallery + shell: pwsh + env: + PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} + run: ./build.ps1 -Task Publish diff --git a/.gitignore b/.gitignore index a4789e3..eca4b3d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,11 +13,11 @@ site/.astro/ out/ IntuneGraph-*/ *.html -!src/IntuneGraph/Assets/template.html +!src/IntuneGraphKit/Assets/template.html !site/public/**/*.html graph.json !tests/Fixtures/**/*.json -!src/IntuneGraph/DemoData/**/*.json +!src/IntuneGraphKit/DemoData/**/*.json # Test output TestResults/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f01490..e7529db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to IntuneGraph are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/); versions follow [SemVer](https://semver.org/). +## [Unreleased] + +### Changed +- **The module is now `IntuneGraphKit`**: `Install-Module IntuneGraphKit`, + `Import-Module IntuneGraphKit`. The name `IntuneGraph` on the PowerShell Gallery + belongs to an unrelated module by another author, and a Gallery package name has to + match the module name. Cmdlet names, the `graph.json` format, the MCP server name and + the project name are unchanged. If you import from a clone, the path is now + `src/IntuneGraphKit/IntuneGraphKit.psd1`. +- The README no longer tells visitors to run `Install-Module IntuneGraph`, which + installed that unrelated module instead of this one. +- The manifest `Description` is now the same text as the repository description, and + the module has a freshly generated `GUID` ahead of its first Gallery publish. + +### Added +- Release pipeline: pushing a `v*` tag re-runs the full CI matrix and, when it is green + and the tag matches `ModuleVersion`, publishes to the PowerShell Gallery + (`.github/workflows/release.yml`, `build.ps1 -Task Publish`). +- Gallery listing metadata: `CompatiblePSEditions` plus the `PSEdition_Desktop` and + `PSEdition_Core` tags, so the module appears under both edition filters, and an icon. + ## [0.2.0] - 2026-09-06 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 20b4849..2505ae2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ live tenant. Nothing in the test suite loads it. ## Repository layout ``` -src/IntuneGraph/ +src/IntuneGraphKit/ ├── Public/ one file per exported cmdlet — this is the supported surface ├── Private/ internals: Fetch, Normalize, GraphModel, Query, Emit, Mcp, Util ├── DemoData/ the Contoso tenant shipped to users via -DemoData @@ -50,15 +50,17 @@ them aware of where the data came from, it belongs somewhere else. .\build.ps1 -Task Test # Pester only .\build.ps1 -Task Analyze # PSScriptAnalyzer only .\build.ps1 -Task Fixtures # regenerate the Contoso dataset +.\build.ps1 -Task Publish # release workflow only — see Releasing ``` -CI runs `Analyze` then `Test` on `windows-latest` and `ubuntu-latest` for every -push and pull request, so run both locally before you open one. +CI runs `Analyze` then `Test` for every push and pull request on Windows +PowerShell 5.1 and PowerShell 7 on `windows-latest`, and PowerShell 7 on +`ubuntu-latest`, so run both locally before you open one. ### PSScriptAnalyzer Settings live in [`PSScriptAnalyzerSettings.psd1`](PSScriptAnalyzerSettings.psd1) -and apply to `src/IntuneGraph` recursively. Severity is `Error` and `Warning`; +and apply to `src/IntuneGraphKit` recursively. Severity is `Error` and `Warning`; **errors fail the build, warnings do not.** Three rules are excluded, each with its reason written next to it — `PSAvoidUsingWriteHost` because the report cmdlets write formatted output to the host on purpose, @@ -78,7 +80,7 @@ demo data cannot drift apart. To add a scenario: 1. Edit the identity tables and workload definitions at the top of [`tests/New-ContosoFixtures.ps1`](tests/New-ContosoFixtures.ps1). 2. Run `.\build.ps1 -Task Fixtures`. It rewrites both `tests/Fixtures/contoso` - and `src/IntuneGraph/DemoData`. + and `src/IntuneGraphKit/DemoData`. 3. Update the expected counts in `tests/IntuneGraph.Tests.ps1` — they are asserted, and a scenario nobody counted is a scenario nobody tested. 4. Commit the regenerated JSON along with your change. @@ -125,6 +127,27 @@ A pull request wants: versions follow [SemVer](https://semver.org/), - documentation updated in the same pull request when behaviour changes. +## Releasing + +Releases go out from CI, never from a workstation. The module is published to the +PowerShell Gallery as **IntuneGraphKit** — `IntuneGraph` on the Gallery is an +unrelated module by another author, which is why the two names differ (see +[docs/discoverability.md](docs/discoverability.md#the-gallery-name-intunegraphkit)). + +1. Move the *Unreleased* entries in `CHANGELOG.md` under the new version and date. +2. Set `ModuleVersion` in `src/IntuneGraphKit/IntuneGraphKit.psd1` to that version. +3. Merge to `main`, then tag the merge commit and push the tag — + `git tag v0.3.0`, then `git push origin v0.3.0`. + +The Release workflow re-runs the whole CI matrix on the tagged commit, checks that the +tag matches `ModuleVersion`, and only then publishes. A Gallery version can be unlisted +but its number can never be reused, so that check is the last point at which a mistake +costs nothing. + +The API key exists only as the `PSGALLERY_API_KEY` repository secret. +`build.ps1 -Task Publish` reads it from the environment and refuses to run without +it; there is deliberately no parameter for it. + ## Where to start Issues labelled diff --git a/README.md b/README.md index 61ecff0..6e8eb90 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ **Turn your Microsoft Intune tenant into an interactive relationship graph.** See what actually applies to a device and *why*, preview the blast radius before you touch a group, and find the orphaned junk, all from a local, read-only snapshot. +[![PowerShell Gallery](https://img.shields.io/powershellgallery/v/IntuneGraphKit?label=PSGallery)](https://www.powershellgallery.com/packages/IntuneGraphKit) [![PowerShell](https://img.shields.io/badge/PowerShell-5.1%20%7C%207%2B-5391FE)](#requirements) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) [![Read-only](https://img.shields.io/badge/Graph%20scopes-read--only-brightgreen)](docs/permissions.md) @@ -19,11 +20,13 @@ https://juandresrodca.github.io/IntuneGraph/demo/ ```powershell -Install-Module IntuneGraph -Scope CurrentUser # (coming to PSGallery) -Import-Module IntuneGraph +Install-Module IntuneGraphKit -Scope CurrentUser +Import-Module IntuneGraphKit Export-IntuneGraph -DemoData -PassThru | Show-IntuneGraph -Open ``` +> The Gallery package is **IntuneGraphKit**. The `IntuneGraph` package on the Gallery is an unrelated module by another author, and unlike this one it writes to Intune — check the name before you install. + That builds the bundled **Contoso** demo tenant and opens the interactive graph in your browser — zero Graph auth, zero setup. The whole tool works offline against demo data, which means you can evaluate it (and contribute to it) without ever touching a real tenant. ![IntuneGraph interactive demo — the Contoso tenant as a relationship graph](docs/img/demo.gif) diff --git a/SECURITY.md b/SECURITY.md index 78daec3..a3e0b16 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,7 +14,7 @@ If private reporting is unavailable to you, email **juandresrodca@gmail.com** wi Please include: -- the module version (`(Get-Module IntuneGraph).Version`) and PowerShell version +- the module version (`(Get-Module IntuneGraphKit).Version`) and PowerShell version (`$PSVersionTable.PSVersion`), - what you did, what happened, and what you expected, - a minimal reproduction — **against `-DemoData` wherever possible**, never against a @@ -93,7 +93,7 @@ export the way you would treat any tenant extract: To reproduce almost anything, `-DemoData` is enough: ```powershell -Import-Module IntuneGraph +Import-Module IntuneGraphKit Export-IntuneGraph -DemoData -PassThru | Show-IntuneGraph ``` diff --git a/build.ps1 b/build.ps1 index de70c79..c6fb466 100644 --- a/build.ps1 +++ b/build.ps1 @@ -5,16 +5,17 @@ .\build.ps1 -Task Test .\build.ps1 -Task Analyze .\build.ps1 -Task Fixtures + .\build.ps1 -Task Publish # release workflow only; reads $env:PSGALLERY_API_KEY #> [CmdletBinding()] param( - [ValidateSet('Test', 'Analyze', 'Fixtures', 'All')] + [ValidateSet('Test', 'Analyze', 'Fixtures', 'Publish', 'All')] [string]$Task = 'All' ) $ErrorActionPreference = 'Stop' $root = $PSScriptRoot -$moduleDir = Join-Path $root 'src\IntuneGraph' +$moduleDir = Join-Path $root 'src\IntuneGraphKit' function Invoke-Fixtures { Write-Host '== Regenerating Contoso fixtures ==' -ForegroundColor Cyan @@ -48,9 +49,26 @@ function Invoke-Test { Invoke-Pester -Configuration $conf } +function Invoke-Publish { + Write-Host '== Publish to the PowerShell Gallery ==' -ForegroundColor Cyan + # The key is read from the environment only - there is deliberately no parameter + # for it - so it never lands in a script, a default value or shell history. The + # release workflow sets it from the PSGALLERY_API_KEY repository secret. + if ([string]::IsNullOrWhiteSpace($env:PSGALLERY_API_KEY)) { + throw 'PSGALLERY_API_KEY is not set. Releases publish from .github/workflows/release.yml - see CONTRIBUTING.md.' + } + if (-not (Get-Command Publish-PSResource -ErrorAction SilentlyContinue)) { + throw 'Publish-PSResource not found. It ships with PowerShell 7.4+ (Microsoft.PowerShell.PSResourceGet).' + } + $manifest = Test-ModuleManifest -Path (Join-Path $moduleDir 'IntuneGraphKit.psd1') + Write-Host "Publishing $($manifest.Name) $($manifest.Version)" + Publish-PSResource -Path $moduleDir -Repository PSGallery -ApiKey $env:PSGALLERY_API_KEY +} + switch ($Task) { 'Fixtures' { Invoke-Fixtures } 'Analyze' { Invoke-Analyze } 'Test' { Invoke-Test } + 'Publish' { Invoke-Publish } 'All' { Invoke-Analyze; Invoke-Test } } diff --git a/docs/discoverability.md b/docs/discoverability.md index dec4a78..846a341 100644 --- a/docs/discoverability.md +++ b/docs/discoverability.md @@ -14,7 +14,7 @@ maintenance task — should change this file in the same commit, with the reason |---|---|---| | **GitHub search and topic pages** | Someone types `intune assignments` into GitHub, or lands on `github.com/topics/intune`. The topics decide whether this repo is in that result set at all. | Repository settings → Topics | | **The live demo** | `https://juandresrodca.github.io/IntuneGraph/demo/`, published by `.github/workflows/pages.yml`. Set as the repository homepage so it appears as a link in search results and in the sidebar. | Repository settings → Website | -| **PowerShell Gallery** | The intended install route, `Install-Module IntuneGraph`. Gallery search reads the `Tags` in `src/IntuneGraph/IntuneGraph.psd1`, which are a separate list from GitHub topics and should stay in step with them. | `IntuneGraph.psd1` | +| **PowerShell Gallery** | The install route, `Install-Module IntuneGraphKit` — see [*The Gallery name*](#the-gallery-name-intunegraphkit) for why it is not `IntuneGraph`. Gallery search reads the `Tags` in `src/IntuneGraphKit/IntuneGraphKit.psd1`, which are a separate list from GitHub topics and should stay in step with them. | `IntuneGraphKit.psd1` | The README's first screen is the fourth surface, and the only one that converts a visitor into a user. It leads with the demo command that needs no tenant, because the @@ -22,6 +22,24 @@ cost of trying this tool is the thing most likely to stop someone. --- +## The Gallery name: IntuneGraphKit + +The project is IntuneGraph; the module on the Gallery is **IntuneGraphKit**. That was +not a choice between two names. `IntuneGraph` on the PowerShell Gallery was already +taken — by an unrelated module from another author that uploads, assigns and removes +Intune apps — and a Gallery package name has to equal the module name. + +The difference matters in one direction. Anyone who types the project name into +`Install-Module` gets the other module: one with write cmdlets, installed on the +strength of a README whose whole pitch is *read-only*. So the README's install block +carries a one-line note naming the other package, and that note stays for as long as +the other package exists. + +Everything a user does not install kept the project name: the repository, the site, +the cmdlet names, the MCP server name (`intunegraph`) and the report title. + +--- + ## Topics GitHub allows twenty. The rule for adding one: **a real practitioner would type it into @@ -122,6 +140,10 @@ visitor who has already clicked: the three questions the tool answers, then the objections an admin raises before installing anything that touches their tenant (*does it write?* and *do I need a tenant to try it?*), answered before they are asked. +The manifest `Description` in `src/IntuneGraphKit/IntuneGraphKit.psd1` carries the same +text, so the Gallery listing and the repository make the same pitch. Change both in the +same commit. + --- ## Sibling repositories diff --git a/docs/fixtures.md b/docs/fixtures.md index 935601b..4b337de 100644 --- a/docs/fixtures.md +++ b/docs/fixtures.md @@ -31,7 +31,7 @@ A missing fixture is a hard error (silent empties would hide bugs). ## The Contoso dataset -`tests/Fixtures/contoso` (mirrored to `src/IntuneGraph/DemoData`) is designed so +`tests/Fixtures/contoso` (mirrored to `src/IntuneGraphKit/DemoData`) is designed so **each hygiene check fires exactly once** and every query scenario is covered: - **Nesting:** `SG-AllStaff` ⊃ `SG-Finance`, `SG-IT` → tests the `Via` path. diff --git a/docs/img/icon.png b/docs/img/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c0fcfd2fc5a99e67fd1ec0856bd4dc37a2da3e7f GIT binary patch literal 7303 zcmY+Jc|4Te`}prMj3Hx}ov~yoYO+TdJ7upBVkA4^DZ9*KNg{)iA`x1xWh>iwtdS)N z2_w6Zh8UVL^Syh%pXc-a{r;HOeXjdl*IBM}pX;3WHMi_-EfAbyoB#kItSn6(0RRFP zApjf(KHUF$?)yi&=4fFIR1Qk4f�bk&O`m)MOr_d$EBsN0{ZsYXHFA@#hEW34MMA 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 literal 0 HcmV?d00001 diff --git a/docs/mcp.md b/docs/mcp.md index eafa126..4213c69 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -100,15 +100,16 @@ claude mcp add intunegraph -- powershell.exe -NoLogo -NoProfile -NonInteractive } ``` -### If the module is installed from PSGallery +### If the module is installed from the PowerShell Gallery -You can skip the launcher script and start the server from the installed module: +You can skip the launcher script and start the server from the installed module +(the Gallery package is `IntuneGraphKit`): ```json { "command": "pwsh", "args": ["-NoLogo", "-NoProfile", "-NonInteractive", "-InputFormat", "Text", - "-Command", "Import-Module IntuneGraph; Start-IntuneGraphMcp -Path 'D:/snapshots/contoso/graph.json'"] + "-Command", "Import-Module IntuneGraphKit; Start-IntuneGraphMcp -Path 'D:/snapshots/contoso/graph.json'"] } ``` diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index c294923..bb8ac74 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -208,8 +208,8 @@ const mcpChat = `> why does Win11 Security Baseline apply to DEV-FIN-01?
1 · Zero-setup demo
-
Install-Module IntuneGraph -Scope CurrentUser
-Import-Module IntuneGraph
+          
Install-Module IntuneGraphKit -Scope CurrentUser
+Import-Module IntuneGraphKit
 Export-IntuneGraph -DemoData -PassThru | Show-IntuneGraph -Open
diff --git a/src/IntuneGraph/Assets/template.html b/src/IntuneGraphKit/Assets/template.html similarity index 100% rename from src/IntuneGraph/Assets/template.html rename to src/IntuneGraphKit/Assets/template.html diff --git a/src/IntuneGraph/DemoData/beta/deviceManagement/assignmentFilters.json b/src/IntuneGraphKit/DemoData/beta/deviceManagement/assignmentFilters.json similarity index 100% rename from src/IntuneGraph/DemoData/beta/deviceManagement/assignmentFilters.json rename to src/IntuneGraphKit/DemoData/beta/deviceManagement/assignmentFilters.json diff --git a/src/IntuneGraph/DemoData/beta/deviceManagement/configurationPolicies.json b/src/IntuneGraphKit/DemoData/beta/deviceManagement/configurationPolicies.json similarity index 100% rename from src/IntuneGraph/DemoData/beta/deviceManagement/configurationPolicies.json rename to src/IntuneGraphKit/DemoData/beta/deviceManagement/configurationPolicies.json diff --git a/src/IntuneGraph/DemoData/beta/deviceManagement/deviceHealthScripts.json b/src/IntuneGraphKit/DemoData/beta/deviceManagement/deviceHealthScripts.json similarity index 100% rename from src/IntuneGraph/DemoData/beta/deviceManagement/deviceHealthScripts.json rename to src/IntuneGraphKit/DemoData/beta/deviceManagement/deviceHealthScripts.json diff --git a/src/IntuneGraph/DemoData/beta/deviceManagement/deviceManagementScripts.json b/src/IntuneGraphKit/DemoData/beta/deviceManagement/deviceManagementScripts.json similarity index 100% rename from src/IntuneGraph/DemoData/beta/deviceManagement/deviceManagementScripts.json rename to src/IntuneGraphKit/DemoData/beta/deviceManagement/deviceManagementScripts.json diff --git a/src/IntuneGraph/DemoData/manifest.json b/src/IntuneGraphKit/DemoData/manifest.json similarity index 100% rename from src/IntuneGraph/DemoData/manifest.json rename to src/IntuneGraphKit/DemoData/manifest.json diff --git a/src/IntuneGraph/DemoData/v1.0/deviceAppManagement/mobileApps.json b/src/IntuneGraphKit/DemoData/v1.0/deviceAppManagement/mobileApps.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/deviceAppManagement/mobileApps.json rename to src/IntuneGraphKit/DemoData/v1.0/deviceAppManagement/mobileApps.json diff --git a/src/IntuneGraph/DemoData/v1.0/deviceManagement/deviceCompliancePolicies.json b/src/IntuneGraphKit/DemoData/v1.0/deviceManagement/deviceCompliancePolicies.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/deviceManagement/deviceCompliancePolicies.json rename to src/IntuneGraphKit/DemoData/v1.0/deviceManagement/deviceCompliancePolicies.json diff --git a/src/IntuneGraph/DemoData/v1.0/deviceManagement/deviceConfigurations.json b/src/IntuneGraphKit/DemoData/v1.0/deviceManagement/deviceConfigurations.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/deviceManagement/deviceConfigurations.json rename to src/IntuneGraphKit/DemoData/v1.0/deviceManagement/deviceConfigurations.json diff --git a/src/IntuneGraph/DemoData/v1.0/deviceManagement/managedDevices.json b/src/IntuneGraphKit/DemoData/v1.0/deviceManagement/managedDevices.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/deviceManagement/managedDevices.json rename to src/IntuneGraphKit/DemoData/v1.0/deviceManagement/managedDevices.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups.json b/src/IntuneGraphKit/DemoData/v1.0/groups.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups.json rename to src/IntuneGraphKit/DemoData/v1.0/groups.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000001/members.json b/src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000001/members.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000001/members.json rename to src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000001/members.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000002/members.json b/src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000002/members.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000002/members.json rename to src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000002/members.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000003/members.json b/src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000003/members.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000003/members.json rename to src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000003/members.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000004/members.json b/src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000004/members.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000004/members.json rename to src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000004/members.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000005/members.json b/src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000005/members.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000005/members.json rename to src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000005/members.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000006/members.json b/src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000006/members.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000006/members.json rename to src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000006/members.json diff --git a/src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000007/members.json b/src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000007/members.json similarity index 100% rename from src/IntuneGraph/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000007/members.json rename to src/IntuneGraphKit/DemoData/v1.0/groups/00000000-0000-0000-0000-000000000007/members.json diff --git a/src/IntuneGraph/IntuneGraph.psd1 b/src/IntuneGraphKit/IntuneGraphKit.psd1 similarity index 67% rename from src/IntuneGraph/IntuneGraph.psd1 rename to src/IntuneGraphKit/IntuneGraphKit.psd1 index a428a12..eb3f295 100644 --- a/src/IntuneGraph/IntuneGraph.psd1 +++ b/src/IntuneGraphKit/IntuneGraphKit.psd1 @@ -1,16 +1,20 @@ @{ - RootModule = 'IntuneGraph.psm1' + RootModule = 'IntuneGraphKit.psm1' ModuleVersion = '0.2.0' - GUID = 'b7e4b0a2-4c9e-4a1f-9c3d-1a2b3c4d5e6f' + GUID = '2fd9d683-0ede-4a50-a4b5-60a39b1a93be' Author = 'Juan Andres Rodriguez' CompanyName = 'IntuneGraph' Copyright = '(c) 2026 Juan Andres Rodriguez. MIT License.' - Description = 'Turn your Microsoft Intune tenant into an interactive, queryable relationship graph. Target resolution, blast-radius impact preview, and assignment hygiene checks. Read-only, offline-capable, zero write scopes.' + # Same text as the GitHub repository description - change both together. + Description = 'Turn your Microsoft Intune tenant into an interactive relationship graph. See what applies to a device or user and why, preview the blast radius before you touch a group, and find orphaned or broken assignments. Read-only Graph, offline-capable demo, self-contained HTML viewer. Zero write scopes.' # 5.1 floor broadens adoption (many Intune admins are still on Windows PowerShell). # PowerShell 7+ is recommended but not required. PowerShellVersion = '5.1' + # Drives the Gallery's edition filter together with the PSEdition_* tags below. + CompatiblePSEditions = @('Desktop', 'Core') + # Microsoft.Graph.Authentication is loaded lazily by Connect-IntuneGraph so that # demo/fixture mode works with zero dependencies installed. It is intentionally # NOT a hard RequiredModules entry. @@ -35,9 +39,11 @@ PrivateData = @{ PSData = @{ Tags = @('Intune', 'MicrosoftGraph', 'MEM', 'Endpoint', 'MDM', 'Graph', 'Visualization', - 'MCP', 'ModelContextProtocol', 'AI', 'Copilot', 'Windows', 'macOS', 'Linux') + 'MCP', 'ModelContextProtocol', 'AI', 'Copilot', 'Windows', 'macOS', 'Linux', + 'PSEdition_Desktop', 'PSEdition_Core') LicenseUri = 'https://github.com/juandresrodca/IntuneGraph/blob/main/LICENSE' ProjectUri = 'https://github.com/juandresrodca/IntuneGraph' + IconUri = 'https://raw.githubusercontent.com/juandresrodca/IntuneGraph/main/docs/img/icon.png' ReleaseNotes = 'https://github.com/juandresrodca/IntuneGraph/blob/main/CHANGELOG.md' } } diff --git a/src/IntuneGraph/IntuneGraph.psm1 b/src/IntuneGraphKit/IntuneGraphKit.psm1 similarity index 100% rename from src/IntuneGraph/IntuneGraph.psm1 rename to src/IntuneGraphKit/IntuneGraphKit.psm1 diff --git a/src/IntuneGraph/Private/DataSource.ps1 b/src/IntuneGraphKit/Private/DataSource.ps1 similarity index 100% rename from src/IntuneGraph/Private/DataSource.ps1 rename to src/IntuneGraphKit/Private/DataSource.ps1 diff --git a/src/IntuneGraph/Private/Emit.ps1 b/src/IntuneGraphKit/Private/Emit.ps1 similarity index 100% rename from src/IntuneGraph/Private/Emit.ps1 rename to src/IntuneGraphKit/Private/Emit.ps1 diff --git a/src/IntuneGraph/Private/Fetch.ps1 b/src/IntuneGraphKit/Private/Fetch.ps1 similarity index 100% rename from src/IntuneGraph/Private/Fetch.ps1 rename to src/IntuneGraphKit/Private/Fetch.ps1 diff --git a/src/IntuneGraph/Private/GraphModel.ps1 b/src/IntuneGraphKit/Private/GraphModel.ps1 similarity index 100% rename from src/IntuneGraph/Private/GraphModel.ps1 rename to src/IntuneGraphKit/Private/GraphModel.ps1 diff --git a/src/IntuneGraph/Private/Mcp.ps1 b/src/IntuneGraphKit/Private/Mcp.ps1 similarity index 100% rename from src/IntuneGraph/Private/Mcp.ps1 rename to src/IntuneGraphKit/Private/Mcp.ps1 diff --git a/src/IntuneGraph/Private/Normalize.ps1 b/src/IntuneGraphKit/Private/Normalize.ps1 similarity index 100% rename from src/IntuneGraph/Private/Normalize.ps1 rename to src/IntuneGraphKit/Private/Normalize.ps1 diff --git a/src/IntuneGraph/Private/Query.ps1 b/src/IntuneGraphKit/Private/Query.ps1 similarity index 100% rename from src/IntuneGraph/Private/Query.ps1 rename to src/IntuneGraphKit/Private/Query.ps1 diff --git a/src/IntuneGraph/Private/Util.ps1 b/src/IntuneGraphKit/Private/Util.ps1 similarity index 100% rename from src/IntuneGraph/Private/Util.ps1 rename to src/IntuneGraphKit/Private/Util.ps1 diff --git a/src/IntuneGraph/Public/Connect-IntuneGraph.ps1 b/src/IntuneGraphKit/Public/Connect-IntuneGraph.ps1 similarity index 100% rename from src/IntuneGraph/Public/Connect-IntuneGraph.ps1 rename to src/IntuneGraphKit/Public/Connect-IntuneGraph.ps1 diff --git a/src/IntuneGraph/Public/Disconnect-IntuneGraph.ps1 b/src/IntuneGraphKit/Public/Disconnect-IntuneGraph.ps1 similarity index 100% rename from src/IntuneGraph/Public/Disconnect-IntuneGraph.ps1 rename to src/IntuneGraphKit/Public/Disconnect-IntuneGraph.ps1 diff --git a/src/IntuneGraph/Public/Export-IntuneGraph.ps1 b/src/IntuneGraphKit/Public/Export-IntuneGraph.ps1 similarity index 100% rename from src/IntuneGraph/Public/Export-IntuneGraph.ps1 rename to src/IntuneGraphKit/Public/Export-IntuneGraph.ps1 diff --git a/src/IntuneGraph/Public/Find-IntuneOrphan.ps1 b/src/IntuneGraphKit/Public/Find-IntuneOrphan.ps1 similarity index 100% rename from src/IntuneGraph/Public/Find-IntuneOrphan.ps1 rename to src/IntuneGraphKit/Public/Find-IntuneOrphan.ps1 diff --git a/src/IntuneGraph/Public/Get-IntuneBlastRadius.ps1 b/src/IntuneGraphKit/Public/Get-IntuneBlastRadius.ps1 similarity index 100% rename from src/IntuneGraph/Public/Get-IntuneBlastRadius.ps1 rename to src/IntuneGraphKit/Public/Get-IntuneBlastRadius.ps1 diff --git a/src/IntuneGraph/Public/Get-IntuneGraphNode.ps1 b/src/IntuneGraphKit/Public/Get-IntuneGraphNode.ps1 similarity index 100% rename from src/IntuneGraph/Public/Get-IntuneGraphNode.ps1 rename to src/IntuneGraphKit/Public/Get-IntuneGraphNode.ps1 diff --git a/src/IntuneGraph/Public/Get-IntuneTarget.ps1 b/src/IntuneGraphKit/Public/Get-IntuneTarget.ps1 similarity index 100% rename from src/IntuneGraph/Public/Get-IntuneTarget.ps1 rename to src/IntuneGraphKit/Public/Get-IntuneTarget.ps1 diff --git a/src/IntuneGraph/Public/Import-IntuneGraph.ps1 b/src/IntuneGraphKit/Public/Import-IntuneGraph.ps1 similarity index 100% rename from src/IntuneGraph/Public/Import-IntuneGraph.ps1 rename to src/IntuneGraphKit/Public/Import-IntuneGraph.ps1 diff --git a/src/IntuneGraph/Public/Show-IntuneGraph.ps1 b/src/IntuneGraphKit/Public/Show-IntuneGraph.ps1 similarity index 100% rename from src/IntuneGraph/Public/Show-IntuneGraph.ps1 rename to src/IntuneGraphKit/Public/Show-IntuneGraph.ps1 diff --git a/src/IntuneGraph/Public/Start-IntuneGraphMcp.ps1 b/src/IntuneGraphKit/Public/Start-IntuneGraphMcp.ps1 similarity index 100% rename from src/IntuneGraph/Public/Start-IntuneGraphMcp.ps1 rename to src/IntuneGraphKit/Public/Start-IntuneGraphMcp.ps1 diff --git a/tests/IntuneGraph.Tests.ps1 b/tests/IntuneGraph.Tests.ps1 index 614e4d2..79ab66b 100644 --- a/tests/IntuneGraph.Tests.ps1 +++ b/tests/IntuneGraph.Tests.ps1 @@ -3,7 +3,7 @@ #requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } BeforeAll { - $script:ModulePath = Join-Path $PSScriptRoot '..\src\IntuneGraph\IntuneGraph.psd1' + $script:ModulePath = Join-Path $PSScriptRoot '..\src\IntuneGraphKit\IntuneGraphKit.psd1' Import-Module $script:ModulePath -Force $script:FixtureRoot = Join-Path $PSScriptRoot 'Fixtures\contoso' $script:GraphPath = Join-Path $TestDrive 'graph.json' @@ -16,7 +16,7 @@ BeforeAll { $script:McpSend = { param($Graph, [string[]]$Lines) $payload = ($Lines -join "`n") - $raw = & (Get-Module IntuneGraph) { + $raw = & (Get-Module IntuneGraphKit) { param($g, $text) $sw = New-Object System.IO.StringWriter Invoke-IgMcpLoop -Reader (New-Object System.IO.StringReader($text)) -Writer $sw -State (New-IgMcpState -Graph $g -Path $null) @@ -44,9 +44,21 @@ Describe 'Module' { $expected = 'Connect-IntuneGraph', 'Disconnect-IntuneGraph', 'Export-IntuneGraph', 'Import-IntuneGraph', 'Get-IntuneGraphNode', 'Get-IntuneTarget', 'Get-IntuneBlastRadius', 'Find-IntuneOrphan', 'Show-IntuneGraph', 'Start-IntuneGraphMcp' - $actual = (Get-Command -Module IntuneGraph -CommandType Function).Name + $actual = (Get-Command -Module IntuneGraphKit -CommandType Function).Name ($expected | Sort-Object) | Should -Be ($actual | Sort-Object) } + It 'carries the metadata the Gallery listing needs' { + # A published version's listing can never be corrected in place; only a new + # version fixes it. Catch a missing field here instead. + $m = Import-PowerShellDataFile $script:ModulePath + $m.CompatiblePSEditions | Should -Contain 'Desktop' + $m.CompatiblePSEditions | Should -Contain 'Core' + $m.PrivateData.PSData.Tags | Should -Contain 'PSEdition_Desktop' + $m.PrivateData.PSData.Tags | Should -Contain 'PSEdition_Core' + foreach ($uri in 'ProjectUri', 'LicenseUri', 'IconUri', 'ReleaseNotes') { + $m.PrivateData.PSData[$uri] | Should -Match '^https://' -Because "$uri is shown on the listing" + } + } } Describe 'Export / build' { @@ -304,7 +316,7 @@ Describe 'MCP transport (feature 4)' { $out.Raw | Should -Match '\\n' # the rendered table's newlines, escaped } It 'serves every tool with the session disconnected, so no live tenant call is reachable' { - (InModuleScope IntuneGraph { $script:IgSession.Mode }) | Should -Be 'None' + (InModuleScope IntuneGraphKit { $script:IgSession.Mode }) | Should -Be 'None' foreach ($tool in 'intune_summary', 'intune_orphans', 'intune_node') { $out = & $script:McpSend $script:G @(& $script:McpToolRequest $tool @{}) $out.Responses[0].result.isError | Should -BeFalse diff --git a/tests/New-ContosoFixtures.ps1 b/tests/New-ContosoFixtures.ps1 index ef61625..51c73e3 100644 --- a/tests/New-ContosoFixtures.ps1 +++ b/tests/New-ContosoFixtures.ps1 @@ -11,7 +11,7 @@ param( [string[]]$TargetRoot = @( (Join-Path $PSScriptRoot 'Fixtures\contoso'), - (Join-Path $PSScriptRoot '..\src\IntuneGraph\DemoData') + (Join-Path $PSScriptRoot '..\src\IntuneGraphKit\DemoData') ) ) diff --git a/tools/icon/New-IntuneGraphIcon.ps1 b/tools/icon/New-IntuneGraphIcon.ps1 new file mode 100644 index 0000000..0bebd25 --- /dev/null +++ b/tools/icon/New-IntuneGraphIcon.ps1 @@ -0,0 +1,75 @@ +<# + Draws the module icon the PowerShell Gallery listing shows (the manifest's IconUri). + Run: .\tools\icon\New-IntuneGraphIcon.ps1 (writes docs\img\icon.png) + + Windows only: it uses System.Drawing, which ships with Windows PowerShell, so the + icon needs no package install. Colours are the node palette from the HTML viewer, + and the two bright edges are the "via" path - device -> group -> policy - which is + the one thing this tool shows that a flat list does not. +#> +[CmdletBinding()] +param( + [string]$OutputPath, + [int]$Size = 256 +) +$ErrorActionPreference = 'Stop' +# Resolved here rather than as a parameter default: Windows PowerShell leaves +# $PSScriptRoot empty in parameter defaults when the script is started with -File. +if (-not $OutputPath) { $OutputPath = Join-Path $PSScriptRoot '..\..\docs\img\icon.png' } +Add-Type -AssemblyName System.Drawing + +function New-IgColor([string]$Hex) { [System.Drawing.ColorTranslator]::FromHtml($Hex) } + +$s = $Size / 256.0 # drawn on a 256 grid, scaled to $Size +$bmp = New-Object System.Drawing.Bitmap $Size, $Size +$g = [System.Drawing.Graphics]::FromImage($bmp) +$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias +$g.Clear([System.Drawing.Color]::Transparent) + +# Rounded-square tile, inset so the border stroke is not clipped. +$inset = 3 * $s; $d = 2 * 52 * $s; $far = $Size - $inset - $d +$tile = New-Object System.Drawing.Drawing2D.GraphicsPath +$tile.AddArc($inset, $inset, $d, $d, 180, 90) +$tile.AddArc($far, $inset, $d, $d, 270, 90) +$tile.AddArc($far, $far, $d, $d, 0, 90) +$tile.AddArc($inset, $far, $d, $d, 90, 90) +$tile.CloseFigure() +$bg = New-IgColor '#0d1320' +$g.FillPath((New-Object System.Drawing.SolidBrush $bg), $tile) +$g.DrawPath((New-Object System.Drawing.Pen (New-IgColor '#27395a'), (4 * $s)), $tile) + +# x, y, radius, colour - group hub in the middle, one of each workload kind around it. +$nodes = [ordered]@{ + group = @(128, 128, 30, '#9fef00') + config = @(194, 64, 19, '#ffb454') + device = @(196, 192, 19, '#58a6ff') + user = @( 60, 192, 19, '#c792ea') + app = @( 62, 64, 19, '#ff7b72') +} +$edges = @( + @('app', 'group', '#34496e'), + @('user', 'group', '#34496e'), + @('device', 'group', '#9fef00'), + @('group', 'config', '#9fef00') +) + +foreach ($e in $edges) { + $a = $nodes[$e[0]]; $b = $nodes[$e[1]] + $pen = New-Object System.Drawing.Pen (New-IgColor $e[2]), (8 * $s) + $pen.StartCap = [System.Drawing.Drawing2D.LineCap]::Round + $pen.EndCap = [System.Drawing.Drawing2D.LineCap]::Round + $g.DrawLine($pen, $a[0] * $s, $a[1] * $s, $b[0] * $s, $b[1] * $s) + $pen.Dispose() +} + +# Each node sits in a ring of tile colour so edges stop short of it. +foreach ($n in $nodes.Values) { + $x = $n[0] * $s; $y = $n[1] * $s; $r = $n[2] * $s; $ring = 7 * $s + $g.FillEllipse((New-Object System.Drawing.SolidBrush $bg), ($x - $r - $ring), ($y - $r - $ring), (2 * ($r + $ring)), (2 * ($r + $ring))) + $g.FillEllipse((New-Object System.Drawing.SolidBrush (New-IgColor $n[3])), ($x - $r), ($y - $r), (2 * $r), (2 * $r)) +} + +$OutputPath = [System.IO.Path]::GetFullPath($OutputPath) +$bmp.Save($OutputPath, [System.Drawing.Imaging.ImageFormat]::Png) +$g.Dispose(); $bmp.Dispose() +Write-Host "Wrote $OutputPath ($Size x $Size)" diff --git a/tools/mcp/mcp-server.ps1 b/tools/mcp/mcp-server.ps1 index 2493710..2078d39 100644 --- a/tools/mcp/mcp-server.ps1 +++ b/tools/mcp/mcp-server.ps1 @@ -9,5 +9,5 @@ param( [switch]$LogRequests ) $ErrorActionPreference = 'Stop' -Import-Module (Join-Path $PSScriptRoot '..\..\src\IntuneGraph\IntuneGraph.psd1') -Force +Import-Module (Join-Path $PSScriptRoot '..\..\src\IntuneGraphKit\IntuneGraphKit.psd1') -Force Start-IntuneGraphMcp -Path $Path -LogRequests:$LogRequests