Skip to content

Repository files navigation

PacKit PowerShell module

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.


Requirements

  • 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.)

Install

From the PowerShell Gallery:

Install-Module-Name PacKit -Scope CurrentUser
Import-Module PacKit

Or 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.)


Quick start — instrument an application

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 saveif (-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}.xml

Load 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-demo

What gets written

Export-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.


Commands

CmdletPurpose
New-PacKitApplicationFragmentCreate a new in-memory fragment (generates a braced-UPPERCASE AppId).
Import-PacKitApplicationFragmentLoad a fragment from -SourceFolder (.packit\<AppId>.xml, or the first *.xml) or -LiteralPath.
Export-PacKitApplicationFragmentSave a fragment to -SourceFolder (.packit\<AppId>.xml) or -LiteralPath. Supports -WhatIf, -RelativizePaths, -PassThru.
Set-PacKitApplicationUpdate application-level fields (Name, Vendor, Description, IconPath, OS, …).
Set-PacKitDetectionRuleSet the detection-rule fields (MSI/File/Registry/Script).
Add-PacKitPackageAdd an installer package; Type is derived from the path extension.
Set-PacKitPackageUpdate an existing package by -PackageId.
Remove-PacKitPackageRemove a package by -PackageId.
Add-PacKitWinGetScanResultAttach a WinGet catalog match to a package.
Add-PacKitAssignmentAdd an Intune (Entra group) assignment.
Remove-PacKitAssignmentRemove an assignment by -MsEntraGroupId.
Initialize-PacKitFolderCreate .packit + the managed subfolders (idempotent).
Test-PacKitApplicationFragmentValidate 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.

Object model

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)

Using it in a pipeline

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_VENDORAdd-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; exit1 }
Export-PacKitApplicationFragment-Fragment $app-SourceFolder $env:APP_SOURCE-RelativizePaths

Build & test

Everything 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
TaskDoes
CleanRemove output\.
LintRun PSScriptAnalyzer with PSScriptAnalyzerSettings.psd1.
TestRun the Pester 5 suite; writes output\testResults.xml (NUnit).
PackageStage the runtime module to output\PacKit\ and zip it to output\PacKit-<version>.zip.
PublishPublish-Module the staged module to the PowerShell Gallery (needs an API key).
AllClean + 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.ps1

tests\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.


Releasing

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:

  1. Bump ModuleVersion in PacKit/PacKit.psd1 and update CHANGELOG.md.
  2. Tag the commit and push the tag:
    git tag v1.0.0
    git push origin v1.0.0
  3. The release pipeline (.github/workflows/release.yml / the publish job in .gitlab-ci.yml) builds, tests, publishes to the PowerShell Gallery, and attaches PacKit-<version>.zip to 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 Publish

Repository layout

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

License

Copyright (c) Caphyon LTD. Use is governed by the PacKit EULA — see LICENSE.txt and https://www.getpackit.com/eula/.

About

PacKit PowerShell Module

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages