Author, load, modify and save PacKit application fragments and the
.packit metadata folder — the per-application XML the
PacKit app consumes. Built for CI/CD: a third
party instruments an application with a .packit folder, and someone later
loads it in PacKit.
The module produces output that is byte-identical to what PacKit itself writes, so a load → modify → save round-trip is lossless and diffs stay clean.
- Windows PowerShell 5.1 (Desktop) or PowerShell 7+ (Core).
- No external modules or binaries are required at runtime — it is pure PowerShell + .NET. (Building/testing the repo additionally uses Pester 5 and PSScriptAnalyzer.)
From the PowerShell Gallery:
Install-Module -Name PacKit -Scope CurrentUser
Import-Module PacKitOr vendor the self-contained PacKit folder into your repo / pipeline and import
it by manifest path:
Import-Module .\PacKit\PacKit.psd1(You can also drop the PacKit folder onto a $env:PSModulePath entry and
Import-Module PacKit. A published release zip — PacKit-<version>.zip —
extracts to exactly that layout.)
Import-Module .\PacKit\PacKit.psd1
# 1. Author the application
$app = New-PacKitApplicationFragment -Name 'Acme Reader' -Vendor 'Acme Corporation' `
-Description 'Reads PDFs' -OperatingSysArchitecture 'x64'
# 2. Add the installer (Type is derived from the file extension -> 'msi')
Add-PacKitPackage -Fragment $app -Path 'app.msi' -InstallCmdLine '/qn' -Version '1.0.0'
# 3. Detection + Intune assignment (optional)
Set-PacKitDetectionRule -Fragment $app -Type 'MSI' -MsiValue '{PRODUCT-CODE}'
Add-PacKitAssignment -Fragment $app -MsEntraGroupId '{2C3D4E5F-6071-8293-A4B5-C6D7E8F90011}' `
-AssignmentType 'Required' -InclusionType 'Include'
# 4. Validate, create the .packit folder, and save
if (-not (Test-PacKitApplicationFragment -Fragment $app)) { throw 'invalid fragment' }
Initialize-PacKitFolder -SourceFolder 'C:\src\Acme'
Export-PacKitApplicationFragment -Fragment $app -SourceFolder 'C:\src\Acme'
# -> C:\src\Acme\.packit\{APP-ID}.xmlLoad it back, change it, and re-save:
$app = Import-PacKitApplicationFragment -SourceFolder 'C:\src\Acme'
Set-PacKitApplication -Fragment $app -Description 'Reads PDFs and forms'
Export-PacKitApplicationFragment -Fragment $app -SourceFolder 'C:\src\Acme'A complete, runnable example with self-verification is in
examples/Build-SampleApp.ps1:
powershell -NoProfile -File .\examples\Build-SampleApp.ps1 -OutDir C:\temp\packit-demoExport-PacKitApplicationFragment -SourceFolder <dir> writes:
<dir>\.packit\{APP-ID}.xml # the application fragment (UTF-8 no BOM, CRLF)
Initialize-PacKitFolder -SourceFolder <dir> additionally creates PacKit's
managed resource subfolders:
<dir>\.packit\
icons\ detection-scripts\ psadt\ intunewin\ mecm\ downloads\ temp\
The fragment is a <FRAGMENT Version="23.8"> document containing one <ITEM>
(the application), with Packages and IntuneAssignments child collections.
Path-bearing fields (icon, detection script, package path) are stored relative
to the .packit folder; pass -RelativizePaths to Export to rewrite
absolute inputs that way automatically.
| Cmdlet | Purpose |
|---|---|
New-PacKitApplicationFragment |
Create a new in-memory fragment (generates a braced-UPPERCASE AppId). |
Import-PacKitApplicationFragment |
Load a fragment from -SourceFolder (.packit\<AppId>.xml, or the first *.xml) or -LiteralPath. |
Export-PacKitApplicationFragment |
Save a fragment to -SourceFolder (.packit\<AppId>.xml) or -LiteralPath. Supports -WhatIf, -RelativizePaths, -PassThru. |
Set-PacKitApplication |
Update application-level fields (Name, Vendor, Description, IconPath, OS, …). |
Set-PacKitDetectionRule |
Set the detection-rule fields (MSI/File/Registry/Script). |
Add-PacKitPackage |
Add an installer package; Type is derived from the path extension. |
Set-PacKitPackage |
Update an existing package by -PackageId. |
Remove-PacKitPackage |
Remove a package by -PackageId. |
Add-PacKitWinGetScanResult |
Attach a WinGet catalog match to a package. |
Add-PacKitAssignment |
Add an Intune (Entra group) assignment. |
Remove-PacKitAssignment |
Remove an assignment by -MsEntraGroupId. |
Initialize-PacKitFolder |
Create .packit + the managed subfolders (idempotent). |
Test-PacKitApplicationFragment |
Validate a fragment (-Detailed for Errors/Warnings). Fails invalid GUIDs, an empty name, or any XML-invalid character (control chars / lone surrogates) in a text field. |
Get-Help <cmdlet> -Full has parameter details and examples for each.
PacKit.ApplicationFragment
AppId Name Vendor Description IconPath WinGetAppScannedAt Unseen
OperatingSys OperatingSysArchitecture
DetectionRule -> PacKit.DetectionRule (Format, Type, MsiValue, ScriptPath, File*/Reg* …)
Packages -> PacKit.Package[] (PackageId, Path, Type, Version, *CmdLine, Intune* …)
WinGetScanResults -> PacKit.WinGetScanResult[] (CatalogPackageId, MatchScore, Vendor)
IntuneAssignments -> PacKit.Assignment[] (MsEntraGroupId, AssignmentType, InclusionType)
The cmdlets are non-interactive and pipeline-friendly. Export honours
-WhatIf; Test-PacKitApplicationFragment returns a boolean (and a -Detailed
report) for gating a build:
$ErrorActionPreference = 'Stop'
Import-Module PacKit # or: Import-Module .\PacKit\PacKit.psd1
$app = New-PacKitApplicationFragment -Name $env:APP_NAME -Vendor $env:APP_VENDOR
Add-PacKitPackage -Fragment $app -Path $env:INSTALLER_PATH -InstallCmdLine $env:INSTALL_ARGS -Version $env:APP_VERSION
$report = Test-PacKitApplicationFragment -Fragment $app -Detailed
if (-not $report.IsValid) { $report.Errors | Write-Error; exit 1 }
Export-PacKitApplicationFragment -Fragment $app -SourceFolder $env:APP_SOURCE -RelativizePathsEverything goes through build.ps1 (the same entry point CI uses):
# One-time: install build dependencies (Pester 5 + PSScriptAnalyzer, CurrentUser):
.\build.ps1 -Bootstrap -Task Lint
# Lint + test + package (the default):
.\build.ps1 # == -Task All
.\build.ps1 -Task Lint
.\build.ps1 -Task Test
.\build.ps1 -Task Package| Task | Does |
|---|---|
Clean |
Remove output\. |
Lint |
Run PSScriptAnalyzer with PSScriptAnalyzerSettings.psd1. |
Test |
Run the Pester 5 suite; writes output\testResults.xml (NUnit). |
Package |
Stage the runtime module to output\PacKit\ and zip it to output\PacKit-<version>.zip. |
Publish |
Publish-Module the staged module to the PowerShell Gallery (needs an API key). |
All |
Clean + Lint + Test + Package (default). |
A quick test-only run (forces Pester 5, returns the failed count as the exit
code) is also available via Invoke-Tests.ps1:
powershell -NoProfile -File .\Invoke-Tests.ps1
powershell -NoProfile -File .\Invoke-Tests.ps1 -TestPath .\tests\PacKit.Emitter.Tests.ps1tests\fixtures\golden-fragment.xml and golden-empty.xml are hand-built from
the PacKit on-disk contract; the emitter is verified byte-for-byte against them.
CI runs lint + test on every push/PR (Windows PowerShell 5.1 and PowerShell 7) via GitHub Actions and GitLab CI.
To cut a release:
- Bump
ModuleVersioninPacKit/PacKit.psd1and updateCHANGELOG.md. - Tag the commit and push the tag:
git tag v1.0.0 git push origin v1.0.0
- The release pipeline (
.github/workflows/release.yml/ thepublishjob in.gitlab-ci.yml) builds, tests, publishes to the PowerShell Gallery, and attachesPacKit-<version>.zipto the release.
The publish step needs a PowerShell Gallery API key supplied as the
PSGALLERY_API_KEY secret (GitHub) or a protected CI/CD variable (GitLab). To
publish manually:
.\build.ps1 -Task Publish -NuGetApiKey <your-psgallery-api-key>
# or: $env:PSGALLERY_API_KEY = '<key>'; .\build.ps1 -Task Publishpackit-powershell/
PacKit/ # the module (shipped)
PacKit.psd1 # manifest (RootModule, exports, PS 5.1 + Core)
PacKit.psm1 # loader (dot-sources Private + Public)
Public/ # the 13 exported cmdlets (one per file)
Private/ # schema, emitter, parser, escaping, GUID, path helpers
tests/ # Pester 5 suites + golden fixtures
examples/Build-SampleApp.ps1 # runnable, self-verifying sample
build.ps1 # build / lint / test / package / publish
Invoke-Tests.ps1 # quick Pester-5 test runner
PSScriptAnalyzerSettings.psd1 # lint configuration
.github/workflows/ # GitHub Actions (ci.yml, release.yml)
.gitlab-ci.yml # GitLab CI (lint / test / publish)
README.md CHANGELOG.md LICENSE.txt .gitignore
Copyright (c) Caphyon LTD. Use is governed by the PacKit EULA —
see LICENSE.txt and https://www.getpackit.com/eula/.