psadt-deploy-skill 0.34.2 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +477 -454
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,454 +1,477 @@
|
|
|
1
|
-
<h1 align="center">PSADT v4 → Intune Deployment Skill</h1>
|
|
2
|
-
|
|
3
|
-
<p align="center">
|
|
4
|
-
<em>A Claude Code skill that drives the full lifecycle of a PowerShell App Deployment Toolkit (PSADT) v4.x Intune Win32 package — from first conversation to a tested, upload-ready <code>.intunewin</code>.</em>
|
|
5
|
-
</p>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<a href="https://github.com/pt1987/claude-code-psadt-skill/actions/workflows/tests.yml"><img src="https://github.com/pt1987/claude-code-psadt-skill/actions/workflows/tests.yml/badge.svg" alt="tests" /></a>
|
|
9
|
-
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License: MIT" /></a>
|
|
10
|
-
<img src="https://img.shields.io/badge/PSADT-v4.x-0a7bbb?style=flat-square" alt="PSADT v4.x" />
|
|
11
|
-
<img src="https://img.shields.io/badge/Platform-Windows-0078d6?style=flat-square&logo=windows&logoColor=white" alt="Windows" />
|
|
12
|
-
<img src="https://img.shields.io/badge/Claude%20Code-Skill-d97757?style=flat-square" alt="Claude Code Skill" />
|
|
13
|
-
</p>
|
|
14
|
-
|
|
15
|
-
<p align="center"><sub><a href="#quick-start">Quick start</a> · <a href="#how-it-works">How it works</a> · <a href="#features">Features</a> · <a href="#first-run-setup">Setup</a> · <a href="#security">Security</a> · <a href="#roadmap">Roadmap</a> · <a href="#changelog">Changelog</a></sub></p>
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## What is this?
|
|
20
|
-
|
|
21
|
-
A **Claude Code skill** (not a plugin): a reusable instruction package that teaches the agent how to build,
|
|
22
|
-
package, test, troubleshoot and deploy a **PSADT v4.x Intune Win32 app**. You describe the application; the
|
|
23
|
-
skill runs the workflow — intake, web research, scaffolding, all three deployment types
|
|
24
|
-
(Install / Uninstall / Repair), pre-flight checks, the SYSTEM test, packaging, the dossier, and the
|
|
25
|
-
optional Graph upload.
|
|
26
|
-
|
|
27
|
-
A skill is a folder with a `SKILL.md` (YAML frontmatter + Markdown instructions), here bundled with
|
|
28
|
-
`scripts/` and `references/`. It loads progressively: the agent sees only the name and description until a
|
|
29
|
-
task makes it relevant, then the full body loads on demand.
|
|
30
|
-
|
|
31
|
-
<img width="1024" height="254" alt="image" src="https://github.com/user-attachments/assets/7c7931ba-dcae-4476-a648-11115eceb3b5" />
|
|
32
|
-
|
|
33
|
-
## Quick start
|
|
34
|
-
|
|
35
|
-
```powershell
|
|
36
|
-
npx psadt-deploy-skill
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
That installs the skill into `~/.claude/skills/psadt-deploy` and runs the setup doctor, which provisions
|
|
40
|
-
everything it can and names the handful of values only you can supply (see
|
|
41
|
-
[First-run setup](#first-run-setup)). Then open Claude Code in any folder and say what you want:
|
|
42
|
-
|
|
43
|
-
> *"Create the Win32 Intune package for 7-Zip 24.09"* — or *"package Notepad++ for Intune"*
|
|
44
|
-
|
|
45
|
-
The skill asks at most **four decision gates** (scope · deployment semantics · SYSTEM-test consent ·
|
|
46
|
-
upload confirmation). Everything else it researches and states as an assumption instead of asking.
|
|
47
|
-
|
|
48
|
-
## How it works
|
|
49
|
-
|
|
50
|
-
Twelve phases, each owned by a script rather than by prose, so a step either passed or did not:
|
|
51
|
-
|
|
52
|
-
| Phase | What happens | Owner |
|
|
53
|
-
|---|---|---|
|
|
54
|
-
| **0** Setup | 13 prerequisite checks, GREEN/YELLOW/RED, `-Fix` provisions | `Initialize-PsadtSkill.ps1` |
|
|
55
|
-
| **1–2** Intake + research | blocker questions as clickable options; a local-evidence ladder (installed here? binary here? already written down?) answers what it can, and a research agent is dispatched only per question it leaves open | agent (gates 1–2) |
|
|
56
|
-
| **3** Scaffold | a generator writes launcher + detection + per-run log name + manifest; `New-ADTTemplate` only when none fits | `New-MsiPackage` · `New-BrowserExtensionPackage` · `New-WindowsFeaturePackage` · `New-DriverPackage` |
|
|
57
|
-
| **4** Customize | all three hooks filled from the research, helpers in the Extensions module | agent |
|
|
58
|
-
| **5** Pre-flight | 10 checks (encoding, AST parse, v3 cmdlets, structure, detection contract, manifest, log name, driver trust …) → GREEN/RED | `Invoke-PsadtPreflight.ps1` |
|
|
59
|
-
| **6** SYSTEM test | installs/uninstalls as **SYSTEM** like the IME does; **binding before any upload** | `Invoke-PsadtSystemTest.ps1` |
|
|
60
|
-
| **7** Package | one command → verified `.intunewin`, named after the app | `Invoke-PsadtPackage.ps1` |
|
|
61
|
-
| **8** Dossier | always, uploaded or not: bilingual self-contained HTML | `New-PsadtReport.ps1` |
|
|
62
|
-
| **9** Upload *(opt-in)* | dry run → confirm → `win32LobApp` via raw Graph | `Invoke-IntuneWin32Upload.ps1` |
|
|
63
|
-
| **10** Assignment *(opt-in)* | create/reuse Entra groups by naming scheme | `Invoke-IntuneAppAssignment.ps1` |
|
|
64
|
-
| **11–12** Test + rollout | DEV-VM cycles, test group, pilot → staged production | agent |
|
|
65
|
-
|
|
66
|
-
**Everything one app knows lives in `<pkg>\psadt-package.json`** — identity, the decisions taken at the
|
|
67
|
-
gates, the research findings, every phase's result and the artifacts produced. The generators write it,
|
|
68
|
-
every later phase reads and updates it, and pre-flight fails without it. That is what stops two packages of
|
|
69
|
-
the same app from disagreeing about their own version.
|
|
70
|
-
|
|
71
|
-
Depth lives in `references/` (phases 0–12 + appendices A–Q, one file per domain — see
|
|
72
|
-
`references/README.md`); `SKILL.md` stays the
|
|
73
|
-
control plane.
|
|
74
|
-
|
|
75
|
-
## Features
|
|
76
|
-
|
|
77
|
-
### Setup and prerequisites
|
|
78
|
-
|
|
79
|
-
- **Setup doctor** — one idempotent script checks PowerShell 7, Windows PowerShell 5.1, elevation, git,
|
|
80
|
-
PSAppDeployToolkit, the content-prep tool, `Invoke-CommandAs`, Pester, the config, a legacy config, the
|
|
81
|
-
skill tree, a pending update and the Intune credentials. Each line carries a concrete fix; `-Fix` applies
|
|
82
|
-
the ones that need no decision.
|
|
83
|
-
- **Self-healing prerequisites** — installs the PSAppDeployToolkit module from the PowerShell Gallery and
|
|
84
|
-
downloads `IntuneWinAppUtil.exe`, keeping both current against their official sources.
|
|
85
|
-
- **Config outside the skill folder** — `config.json`, `secret.dpapi` and `tools/` live in
|
|
86
|
-
`%LOCALAPPDATA%\psadt-deploy\` (override: `$env:PSADT_DEPLOY_HOME`), so a `git pull`, a re-clone or a
|
|
87
|
-
re-install can no longer take your setup with it. A pre-0.19 config keeps working and is migrated on
|
|
88
|
-
request, never deleted.
|
|
89
|
-
- **One-line install** — `npx psadt-deploy-skill` (Node 18+, zero dependencies) does clone-or-update plus
|
|
90
|
-
the doctor run in one step.
|
|
91
|
-
|
|
92
|
-
### Build and verify
|
|
93
|
-
|
|
94
|
-
- **Guided intake** — the blocker questions up front as clickable options, pre-filled with researched
|
|
95
|
-
defaults (app, latest version, installer type, package type).
|
|
96
|
-
- **Switch catalog before the web** — identifies the installer engine from the *binary* (byte signatures in
|
|
97
|
-
the PE overlay, resources and section table, not a filename guess) and serves that engine's documented
|
|
98
|
-
silent / uninstall / log / no-reboot switches from a catalog that ships with the skill. Offline, and it
|
|
99
|
-
reports every stage it checked including the misses. A candidate is still a claim until a run proves it.
|
|
100
|
-
- **Autonomous research** — checks the installed PSADT version against the latest release *and* whether
|
|
101
|
-
commands changed; researches silent install / uninstall / repair switches and known Intune pitfalls.
|
|
102
|
-
- **All three deployment types from the start** — Install, Uninstall *and* Repair, acid-tested, so
|
|
103
|
-
Company-Portal uninstalls actually work.
|
|
104
|
-
- **Pre-flight gate** — encoding/BOM, AST parse, launcher acid test, v3-cmdlet scan, hook structure,
|
|
105
|
-
detection-script contract, the package manifest, the per-run log name and driver trust. GREEN or RED,
|
|
106
|
-
with the failing file named.
|
|
107
|
-
- **Automated SYSTEM test loop** *(opt-in, binding before upload)* — installs, uninstalls and reinstalls as
|
|
108
|
-
the **SYSTEM** account via `Invoke-CommandAs`, mirroring the Intune Management Extension; reads the fresh
|
|
109
|
-
session log and the detection result, and hands back a structured verdict for the fix-and-retry loop.
|
|
110
|
-
Needs an elevated session; belongs on a VM with a snapshot.
|
|
111
|
-
- **Deterministic packaging** — `<outputRoot>\<Vendor>_<App>_<Version>_<Arch>\<same stem>.intunewin`,
|
|
112
|
-
verified after the fact (`Detection.xml`, `SetupFile`, size, SHA256), with the detection script and the
|
|
113
|
-
real logo beside it. It refuses an output folder inside the package, and never deletes a foreign
|
|
114
|
-
`.intunewin` it finds there.
|
|
115
|
-
- **One PSADT log per run** — `<Vendor>_<App>_<Version>_<Arch>_<Install|Uninstall|Repair>_<timestamp>.log`
|
|
116
|
-
instead of every run of every version appending to one unreadable file.
|
|
117
|
-
|
|
118
|
-
### Package types
|
|
119
|
-
|
|
120
|
-
The app's **native installer is always the default**. Everything else is opt-in and only on request:
|
|
121
|
-
|
|
122
|
-
- **MSI / EXE** — the ordinary case, via `New-MsiPackage.ps1` or a hand-filled scaffold.
|
|
123
|
-
- **WinGet** — the full `PSAppDeployToolkit.WinGet` lifecycle: the extension module self-heals into the
|
|
124
|
-
package, the Package ID is discovered with `Find-ADTWinGetPackage`, hooks use `*-ADTWinGet*`
|
|
125
|
-
(`-Scope Machine`). Never selected on its own initiative.
|
|
126
|
-
- **Browser extensions** — force-install via the Edge/Chrome/Firefox policy keys (including the Firefox
|
|
127
|
-
`REG_MULTI_SZ` trap), with selective removal on uninstall.
|
|
128
|
-
- **Windows features** — `Enable-WindowsOptionalFeature` and `Add-WindowsCapability`, offline source or a
|
|
129
|
-
temporary WSUS bypass that is restored afterwards, `EnablePending` handled honestly.
|
|
130
|
-
- **Third-party drivers** — the trust situation is classified *before* anything is built: Microsoft-signed
|
|
131
|
-
installs silently, vendor-signed needs the signer certificate owned in exactly one place, and a
|
|
132
|
-
vendor-signed **kernel** driver is refused because `TrustedPublisher` satisfies the PnP prompt but never
|
|
133
|
-
Code Integrity — it would install and then not load. Unsigned is refused outright, with three honest
|
|
134
|
-
options and no testsigning. Staging is per-INF `pnputil`; uninstall resolves `oemNN.inf` by original name
|
|
135
|
-
instead of a remembered index.
|
|
136
|
-
- **Script-only / remediation packages** — ESP-safe patterns for fix packages with no installer at all.
|
|
137
|
-
|
|
138
|
-
### Deliverables
|
|
139
|
-
|
|
140
|
-
- **HTML dossier — always generated**, uploaded or not. One self-contained file
|
|
141
|
-
(`Intune-Dossier.html`) built from a fixed template, never hand-assembled: the **Intune dossier** (App
|
|
142
|
-
Info, return-code map, detection rule, requirements, assignments, driver trust, and a ready-to-paste
|
|
143
|
-
**Markdown** description for the Company-Portal field) plus a **technical package report** (the three
|
|
144
|
-
hooks, PSADT cmdlets used, pre-flight and SYSTEM-test results, logo and `.intunewin` verification).
|
|
145
|
-
Bilingual with a DE/EN toggle, browser-translatable, logo embedded as a data URI.
|
|
146
|
-
- **Real logo only** — finds and downloads the actual application logo (vendor source or Wikimedia
|
|
147
|
-
Commons), verifies real pixel transparency *and* looks at the image. The PSADT default `AppIcon.png` is
|
|
148
|
-
blocked by hash.
|
|
149
|
-
- **Start Menu only** — creates Start Menu entries and removes stray desktop icons.
|
|
150
|
-
|
|
151
|
-
### Intune
|
|
152
|
-
|
|
153
|
-
- **Access as state, not as a 403** — `Test-PsadtIntuneAccess.ps1` answers before Phase 9 whether the app
|
|
154
|
-
can upload, assign groups or create policies, and for how long the credential lives. Verified / refused /
|
|
155
|
-
**unknown** are three different answers, and an offline check never overwrites what was verified before.
|
|
156
|
-
- **Direct upload via Microsoft Graph** *(opt-in)* — pushes the `.intunewin` as a `win32LobApp` (app +
|
|
157
|
-
logo), self-contained raw Graph, no third-party module. Identity comes from the manifest, so Intune shows
|
|
158
|
-
the same name and version as the artifact and the dossier. Read-only dry run → confirm → upload. Fills the
|
|
159
|
-
whole App-information tab, **never deletes an older version** (new versions coexist, with optional
|
|
160
|
-
supersedence wiring), never auto-assigns categories or notes.
|
|
161
|
-
- **One-time Entra bootstrap** — `New-PsadtEntraApp.ps1` signs in via **WAM**, creates the app, grants and
|
|
162
|
-
admin-consents the roles and stores the credential: a **certificate** (preferred — nothing secret at rest,
|
|
163
|
-
JWT client-assertion auth) or a DPAPI-encrypted client secret. Re-running it is normal: it finds the
|
|
164
|
-
recorded app, merges requested permissions instead of replacing them, and never prompts.
|
|
165
|
-
- **Opt-in group assignment** — creates/reuses Entra security groups by a configured naming scheme and
|
|
166
|
-
assigns Required / Available / Uninstall. Least-privilege (`Group.Create` + `GroupMember.Read.All`),
|
|
167
|
-
dry run → confirm, idempotent, and it never deletes a group or another app's assignment.
|
|
168
|
-
- **Certificate + firewall policies** — Custom OMA-URI profiles for `TrustedPublisher` / `TrustedPeople`
|
|
169
|
-
(the built-in template cannot reach those stores) and settings-catalog firewall-rule policies. Both
|
|
170
|
-
scripts are self-contained deliverables: they can be copied to a test client that has no skill installed.
|
|
171
|
-
|
|
172
|
-
### Operations
|
|
173
|
-
|
|
174
|
-
- **Troubleshooting** — decodes Intune error/HRESULT codes, maps symptoms to root causes, and triages the
|
|
175
|
-
right log (`AppWorkload.log`, the PSADT session log, `setupapi.dev.log` for drivers).
|
|
176
|
-
- **Self-update** — `scripts/Update-PsadtSkill.ps1` compares against GitHub, shows what changed, and
|
|
177
|
-
updates in place on your confirmation (`git pull --ff-only` for a clone, otherwise a branch-zip overwrite
|
|
178
|
-
of tracked files only). Machine-local state is never touched. Say *"psadt update"*.
|
|
179
|
-
- **503 Pester tests** over the helper scripts, including drift guards that fail when the docs and the code
|
|
180
|
-
disagree.
|
|
181
|
-
|
|
182
|
-
## Requirements
|
|
183
|
-
|
|
184
|
-
- Windows with PowerShell 5.1+ / PowerShell 7+
|
|
185
|
-
- For the `npx` installer only: **Node 18+** (the skill itself never needs Node)
|
|
186
|
-
- [PSAppDeployToolkit](https://psappdeploytoolkit.com/) v4.x *(installed/updated automatically from the
|
|
187
|
-
PowerShell Gallery if missing)*
|
|
188
|
-
- [Microsoft Win32 Content Prep Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)
|
|
189
|
-
*(provisioned automatically)*
|
|
190
|
-
- For the **SYSTEM test loop**: an **elevated** session; the
|
|
191
|
-
[`Invoke-CommandAs`](https://github.com/mkellerman/Invoke-CommandAs) module is installed automatically
|
|
192
|
-
- For the **direct Intune upload**: an Entra app with the Graph application role
|
|
193
|
-
`DeviceManagementApps.ReadWrite.All` (admin-consented) — created in one run by
|
|
194
|
-
`scripts/New-PsadtEntraApp.ps1` (WAM sign-in as Global Admin / Privileged Role Admin, device-code
|
|
195
|
-
fallback). Check what is actually in place with `scripts/Test-PsadtIntuneAccess.ps1`. Full permission
|
|
196
|
-
matrix and the manual portal route: `references/app-registration.md`.
|
|
197
|
-
- For **Pester tests**: Pester 5+ (`Install-Module Pester -MinimumVersion 5.0 -Scope CurrentUser`)
|
|
198
|
-
- **Optional (recommended): the [superpowers](https://github.com/obra/superpowers) plugin** — if installed,
|
|
199
|
-
the gated research fan-out and the reviewer gate use it. Not required: without it the skill falls back to the
|
|
200
|
-
native Agent tool and `/code-review`, and nothing in the workflow depends on the plugin.
|
|
201
|
-
|
|
202
|
-
## Installation
|
|
203
|
-
|
|
204
|
-
```powershell
|
|
205
|
-
npx psadt-deploy-skill
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Installs the **newest release** into `~/.claude/skills/psadt-deploy` and runs the setup doctor. Flags:
|
|
209
|
-
`--dir <path>` · `--project` (into `./.claude/skills`) · `--ref <tag|branch>` · `--no-setup`. Node 18+ and
|
|
210
|
-
Windows; the installer itself has zero dependencies and the package carries only `bin/` — the skill is
|
|
211
|
-
fetched from GitHub at install time.
|
|
212
|
-
|
|
213
|
-
### Which version you get
|
|
214
|
-
|
|
215
|
-
The default is the newest **release tag**, not `main`. This skill registers an Entra application with
|
|
216
|
-
admin consent and writes to an Intune tenant; installing whatever last landed on `main` is not a
|
|
217
|
-
defensible default for that.
|
|
218
|
-
|
|
219
|
-
```powershell
|
|
220
|
-
npx psadt-deploy-skill # newest release (default)
|
|
221
|
-
npx psadt-deploy-skill --ref v0.26.7 # pin an exact release
|
|
222
|
-
npx psadt-deploy-skill --ref main # the development branch, deliberately
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**For managed environments:** pin a tag, read the diff between it and the next one before moving, then
|
|
226
|
-
lift the pin. Releases are tagged `vX.Y.Z` and match the [Changelog](#changelog); tags exist from
|
|
227
|
-
**v0.24.0** onward — earlier versions predate the current history and cannot be tagged retroactively.
|
|
228
|
-
|
|
229
|
-
Re-running the installer updates an existing installation, and so does saying *"psadt update"* to Claude
|
|
230
|
-
Code. What counts as an update depends on what you installed: on a **pinned release** it is the next
|
|
231
|
-
release tag — unreleased work on `main` is deliberately invisible, because that is what pinning means. On
|
|
232
|
-
a **branch** installation it is the next commit, as before. Either way the update overwrites tracked
|
|
233
|
-
repository files only; `config.json`, `secret.dpapi` and `tools/` are never touched.
|
|
234
|
-
|
|
235
|
-
**Or clone it yourself** — the repo root *is* the skill folder:
|
|
236
|
-
|
|
237
|
-
```powershell
|
|
238
|
-
git clone https://github.com/pt1987/claude-code-psadt-skill.git "$env:USERPROFILE\.claude\skills\psadt-deploy"
|
|
239
|
-
pwsh "$env:USERPROFILE\.claude\skills\psadt-deploy\scripts\Initialize-PsadtSkill.ps1" -Fix
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
`npx skills add pt1987/claude-code-psadt-skill` works too, since `SKILL.md` sits in the repository root.
|
|
243
|
-
|
|
244
|
-
No git on the machine? The installer falls back to the GitHub tarball and Windows' own `tar.exe`, so the
|
|
245
|
-
one-liner still works — including with `--ref <tag>`, which is the combination a locked-down machine
|
|
246
|
-
actually needs.
|
|
247
|
-
|
|
248
|
-
The skill activates automatically when you ask Claude Code to build an Intune package, or when you work in
|
|
249
|
-
a folder containing `Invoke-AppDeployToolkit.ps1`.
|
|
250
|
-
|
|
251
|
-
### What is deliberately not in the skill frontmatter
|
|
252
|
-
|
|
253
|
-
`SKILL.md` declares `name`, `description` and `license`, and nothing else. The omissions are choices, not
|
|
254
|
-
oversights:
|
|
255
|
-
|
|
256
|
-
- **`paths`** would look like the right way to express "activates in a folder containing
|
|
257
|
-
`Invoke-AppDeployToolkit.ps1`". It is the opposite: the field *limits* activation to files matching the
|
|
258
|
-
globs. Setting it would switch the skill off for the most common request there is — packaging an app in
|
|
259
|
-
an empty folder, where `Invoke-AppDeployToolkit.ps1` does not exist yet because Phase 3 is what creates
|
|
260
|
-
it. The folder case is covered by the last sentence of the description instead.
|
|
261
|
-
- **`allowed-tools`** grants tools up front; it does not restrict them. For a skill that installs software
|
|
262
|
-
as SYSTEM and writes to a tenant, being asked per call is the point. See [`SECURITY.md`](SECURITY.md).
|
|
263
|
-
- **`metadata.version`** is ignored by Claude Code, and the version already lives in `CHANGELOG.md`,
|
|
264
|
-
`package.json` (kept in sync by a test) and on the website. A fourth place to forget on release day, for
|
|
265
|
-
no behaviour, is not worth it.
|
|
266
|
-
- **`shell`** only matters for `!` command injection in `SKILL.md`, which this skill does not use — and a
|
|
267
|
-
failing `!` command aborts the *entire* skill invocation, so an `Initialize-PsadtSkill` call wired up that
|
|
268
|
-
way would be a single point of failure for every packaging request.
|
|
269
|
-
- **`context: fork` / `agent`** would isolate the skill in a subagent. It orchestrates its own sub-agents
|
|
270
|
-
and needs the main context to hold the decision gates.
|
|
271
|
-
|
|
272
|
-
## First-run setup
|
|
273
|
-
|
|
274
|
-
`scripts/Initialize-PsadtSkill.ps1` (also reachable by saying *"psadt setup"* / *"psadt doctor"*) checks
|
|
275
|
-
every prerequisite in one pass and reports **GREEN / YELLOW / RED**. Every line comes with a concrete fix
|
|
276
|
-
hint, and `-Fix` applies the ones that need no decision (module installs, the tool download, the
|
|
277
|
-
`language.*` defaults, `paths.intuneWinAppUtil`, and migrating a pre-0.19 config). It is idempotent — run it
|
|
278
|
-
as often as you like.
|
|
279
|
-
|
|
280
|
-
Only four values genuinely need you; the doctor lists them in `.Missing` and takes them via `-Set`:
|
|
281
|
-
|
|
282
|
-
```powershell
|
|
283
|
-
pwsh scripts/Initialize-PsadtSkill.ps1 -Fix -Set @{
|
|
284
|
-
'paths.packageRoot' = 'D:\Pakete'; 'paths.outputRoot' = 'D:\Intune'
|
|
285
|
-
'author.person' = 'Pat Taubert'; 'author.company' = 'PHAT Consulting'
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
| Setting | Purpose |
|
|
290
|
-
|---|---|
|
|
291
|
-
| `paths.packageRoot` / `outputRoot` | Where packages are built and where artifacts are written |
|
|
292
|
-
| `paths.intuneWinAppUtil` | Content-prep tool location — filled by `-Fix` |
|
|
293
|
-
| `language.script` / `dossier` | Script language (EN) vs. dossier language (DE for the Company Portal) — filled by `-Fix` |
|
|
294
|
-
| `author.person` / `company` | Stamped into every package's `AppScriptAuthor` |
|
|
295
|
-
| `intune.*` *(optional)* | Direct upload: tenant/client, credential reference, verified roles — written by `New-PsadtEntraApp.ps1` |
|
|
296
|
-
| `intune.groups.*` *(optional)* | Opt-in group assignment (`enabled` / `create` / `membershipType` / `naming`) — guide Appendix M |
|
|
297
|
-
|
|
298
|
-
### Where the setup is stored
|
|
299
|
-
|
|
300
|
-
`config.json`, `secret.dpapi` and `tools/` live in the **config home** — `%LOCALAPPDATA%\psadt-deploy\`,
|
|
301
|
-
overridable with `$env:PSADT_DEPLOY_HOME` — **not** in the skill folder, so they survive a `git pull`, a
|
|
302
|
-
re-clone and a re-install. They are machine-local and never committed. A `config.json` from a pre-0.19
|
|
303
|
-
install (beside `scripts/`) keeps working read-only; the doctor flags it and `-Fix` migrates it, renaming
|
|
304
|
-
the originals to `*.migrated` rather than deleting anything.
|
|
305
|
-
|
|
306
|
-
> DPAPI is bound to the Windows user profile: a re-installed OS invalidates a stored client secret. The
|
|
307
|
-
> doctor and `Test-PsadtIntuneAccess.ps1` both say so, and the fix is one `New-PsadtEntraApp.ps1` run.
|
|
308
|
-
|
|
309
|
-
## Project structure
|
|
310
|
-
|
|
311
|
-
```
|
|
312
|
-
psadt-deploy/
|
|
313
|
-
├─ SKILL.md · README.md · CHANGELOG.md · SECURITY.md · LICENSE
|
|
314
|
-
├─ package.json · bin/install.mjs the npx installer (Node 18+, zero dependencies)
|
|
315
|
-
├─ scripts/
|
|
316
|
-
│ │ setup + config
|
|
317
|
-
│ ├─ Initialize-PsadtSkill.ps1 setup doctor (Phase 0, GREEN/YELLOW/RED, -Fix/-Set)
|
|
318
|
-
│ ├─ Get-PsadtConfig.ps1 config read + config-home resolver
|
|
319
|
-
│ ├─ Set-PsadtConfig.ps1 config write (deep merge, DPAPI secret, -Remove)
|
|
320
|
-
│ ├─ Get-PsadtModule.ps1 PSADT module (self-heal)
|
|
321
|
-
│ ├─ Get-IntuneWinAppUtil.ps1 content-prep tool (self-heal)
|
|
322
|
-
│ ├─ Get-WinGetModule.ps1 WinGet extension (opt-in)
|
|
323
|
-
│ ├─ Update-PsadtSkill.ps1 self-update from GitHub
|
|
324
|
-
│ │ per-package truth
|
|
325
|
-
│ ├─ Get-PsadtPackageManifest.ps1 manifest read (+ the artifact stem)
|
|
326
|
-
│ ├─ Set-PsadtPackageManifest.ps1 manifest write (merge / append)
|
|
327
|
-
│ │ package generators
|
|
328
|
-
│ ├─ New-MsiPackage.ps1 MSI packages
|
|
329
|
-
│ ├─ New-BrowserExtensionPackage.ps1 browser-extension force-install (opt-in)
|
|
330
|
-
│ ├─ New-WindowsFeaturePackage.ps1 optional features / capabilities (opt-in)
|
|
331
|
-
│ ├─ New-DriverPackage.ps1 driver packages, pnputil staging (opt-in)
|
|
332
|
-
│ ├─ Get-DriverSignatureInfo.ps1 driver trust classifier (signed? kernel? deployable?)
|
|
333
|
-
│ │ gates + deliverables
|
|
334
|
-
│ ├─ Invoke-PsadtPreflight.ps1 pre-flight GREEN/RED gate (Phase 5, 10 checks)
|
|
335
|
-
│ ├─ Invoke-PsadtSystemTest.ps1 SYSTEM test (Phase 6)
|
|
336
|
-
│ ├─ Invoke-PsadtPackage.ps1 build the .intunewin (Phase 7, named + verified)
|
|
337
|
-
│ ├─ New-PsadtReport.ps1 HTML dossier (Phase 8, always)
|
|
338
|
-
│ │ intune / graph
|
|
339
|
-
│ ├─ New-PsadtEntraApp.ps1 Entra app bootstrap (WAM)
|
|
340
|
-
│ ├─ Get-GraphToken.ps1 app-only Graph token (cert / DPAPI)
|
|
341
|
-
│ ├─ Test-PsadtIntuneAccess.ps1 access verdict (roles, capabilities, expiry)
|
|
342
|
-
│ ├─ Invoke-IntuneWin32Upload.ps1 direct upload (Phase 9)
|
|
343
|
-
│ ├─ Invoke-IntuneAppAssignment.ps1 group assignment (Phase 10, opt-in)
|
|
344
|
-
│ ├─ New-IntuneTrustedCertPolicy.ps1 Custom OMA-URI cert policy (self-contained)
|
|
345
|
-
│ ├─ New-IntuneFirewallPolicy.ps1 firewall-rule policy (self-contained)
|
|
346
|
-
│ ├─ _GraphCommon.ps1 shared Graph helpers (retry, errors, token roles)
|
|
347
|
-
│ └─ _GraphInteractive.ps1 shared WAM sign-in
|
|
348
|
-
├─ references/
|
|
349
|
-
│ ├─ README.md the reference map (label -> file)
|
|
350
|
-
│ ├─ phases-0-6.md · phases-7-12.md the twelve phases
|
|
351
|
-
│ ├─ appendix-a-errors.md … -q-drivers.md one file per appendix
|
|
352
|
-
│ ├─ switch-catalog/ engine defaults + JSON schema (App. L.0)
|
|
353
|
-
│ ├─ Report-Template.html the fixed dossier template
|
|
354
|
-
│ └─ app-registration.md THE Graph permission matrix + manual portal route
|
|
355
|
-
└─ tests/ Pester suite, 552 tests
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
Machine-local state lives outside the skill folder:
|
|
359
|
-
|
|
360
|
-
```
|
|
361
|
-
%LOCALAPPDATA%\psadt-deploy\ ($env:PSADT_DEPLOY_HOME overrides)
|
|
362
|
-
├─ config.json settings incl. the optional intune.* block
|
|
363
|
-
├─ secret.dpapi DPAPI client secret (only without cert auth)
|
|
364
|
-
└─ tools/ IntuneWinAppUtil.exe + WinGet module
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
And per package, next to `Invoke-AppDeployToolkit.ps1`:
|
|
368
|
-
|
|
369
|
-
```
|
|
370
|
-
psadt-package.json identity · gate decisions · research · results · artifacts
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## Status
|
|
374
|
-
|
|
375
|
-
In active use for the full build → package → test → dossier workflow, with the direct Graph upload
|
|
376
|
-
verified against a live tenant. The helper scripts are covered by 503 Pester tests.
|
|
377
|
-
|
|
378
|
-
One open point, honestly: **the driver `pnputil` exit-code semantics are documented, not verified here.**
|
|
379
|
-
`0` / `259` / `3010` and the two `0xE...` failures come from Microsoft's documentation; confirming them
|
|
380
|
-
against `setupapi.dev.log` on a DEV VM with a real vendor-signed and a real Microsoft-signed driver is
|
|
381
|
-
still open.
|
|
382
|
-
|
|
383
|
-
## Security
|
|
384
|
-
|
|
385
|
-
This skill installs software as SYSTEM, researches on the open web, and writes to an Intune tenant
|
|
386
|
-
through an Entra app with admin consent. [`SECURITY.md`](SECURITY.md) states that risk surface next to
|
|
387
|
-
the control that already covers each part of it — the dry-run-before-execute rule, the three-valued
|
|
388
|
-
access check, never-delete, role assertion before the first write, certificate before DPAPI secret,
|
|
389
|
-
the config home outside the skill folder, and the self-containment rule for anything that ships to a
|
|
390
|
-
test client. Each control names the file that implements it and the test that enforces it, so a review
|
|
391
|
-
can check the claims rather than take them.
|
|
392
|
-
|
|
393
|
-
Two deliberate non-features are explained there as well: the skill does **not** declare
|
|
394
|
-
`allowed-tools` (that field pre-approves tools, it does not restrict them), and content fetched during
|
|
395
|
-
research is treated as data, never as instructions — see
|
|
396
|
-
[`references/research-trust.md`](references/research-trust.md).
|
|
397
|
-
|
|
398
|
-
## Roadmap
|
|
399
|
-
|
|
400
|
-
Designed and waiting to be built:
|
|
401
|
-
|
|
402
|
-
- **Sync finished packages to a GitHub repo** — a setup option (`output.target` = `local` / `git` / `both`)
|
|
403
|
-
to push the per-app artifacts (`.intunewin`, dossier, detection, logo) to a Git repo instead of, or in
|
|
404
|
-
addition to, a local folder — versioned and shareable. Will need **Git LFS** for large `.intunewin` files
|
|
405
|
-
(GitHub's 100 MB per-file limit).
|
|
406
|
-
|
|
407
|
-
Have a request? Open an issue.
|
|
408
|
-
|
|
409
|
-
## Contributing
|
|
410
|
-
|
|
411
|
-
Issues and pull requests are welcome. Keep `SKILL.md`, the references and the docs in **English**. The only
|
|
412
|
-
non-English content is the generated end-user output (the Intune dossier and the Company-Portal app
|
|
413
|
-
description), whose language follows the `language.dossier` config value — **default German**, but
|
|
414
|
-
configurable per machine.
|
|
415
|
-
|
|
416
|
-
Two conventions worth knowing before you send a patch: generated `.ps1` content is **7-bit ASCII** (the
|
|
417
|
-
pre-flight fails on non-ASCII without a BOM), and anything that lands in a package's output folder must be
|
|
418
|
-
**self-contained** — it gets copied to test clients that have no skill installed.
|
|
419
|
-
|
|
420
|
-
## License
|
|
421
|
-
|
|
422
|
-
[MIT](LICENSE) © Patrick Taubert, PHAT Consulting GmbH
|
|
423
|
-
|
|
424
|
-
## Acknowledgements
|
|
425
|
-
|
|
426
|
-
- [PSAppDeployToolkit](https://github.com/PSAppDeployToolkit/PSAppDeployToolkit)
|
|
427
|
-
- [Microsoft Win32 Content Prep Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)
|
|
428
|
-
- [`Invoke-CommandAs`](https://github.com/mkellerman/Invoke-CommandAs)
|
|
429
|
-
- README structure inspired by [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills)
|
|
430
|
-
|
|
431
|
-
## Changelog
|
|
432
|
-
|
|
433
|
-
The two most recent releases are below. **[CHANGELOG.md](CHANGELOG.md)** carries the complete history,
|
|
434
|
-
every release since 0.1.0, and nothing is ever removed from it - this section is a window onto it, not a
|
|
435
|
-
second copy to keep in sync.
|
|
436
|
-
|
|
437
|
-
### 0.
|
|
438
|
-
- **Added: `
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
- **
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1
|
+
<h1 align="center">PSADT v4 → Intune Deployment Skill</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<em>A Claude Code skill that drives the full lifecycle of a PowerShell App Deployment Toolkit (PSADT) v4.x Intune Win32 package — from first conversation to a tested, upload-ready <code>.intunewin</code>.</em>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/pt1987/claude-code-psadt-skill/actions/workflows/tests.yml"><img src="https://github.com/pt1987/claude-code-psadt-skill/actions/workflows/tests.yml/badge.svg" alt="tests" /></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License: MIT" /></a>
|
|
10
|
+
<img src="https://img.shields.io/badge/PSADT-v4.x-0a7bbb?style=flat-square" alt="PSADT v4.x" />
|
|
11
|
+
<img src="https://img.shields.io/badge/Platform-Windows-0078d6?style=flat-square&logo=windows&logoColor=white" alt="Windows" />
|
|
12
|
+
<img src="https://img.shields.io/badge/Claude%20Code-Skill-d97757?style=flat-square" alt="Claude Code Skill" />
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center"><sub><a href="#quick-start">Quick start</a> · <a href="#how-it-works">How it works</a> · <a href="#features">Features</a> · <a href="#first-run-setup">Setup</a> · <a href="#security">Security</a> · <a href="#roadmap">Roadmap</a> · <a href="#changelog">Changelog</a></sub></p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What is this?
|
|
20
|
+
|
|
21
|
+
A **Claude Code skill** (not a plugin): a reusable instruction package that teaches the agent how to build,
|
|
22
|
+
package, test, troubleshoot and deploy a **PSADT v4.x Intune Win32 app**. You describe the application; the
|
|
23
|
+
skill runs the workflow — intake, web research, scaffolding, all three deployment types
|
|
24
|
+
(Install / Uninstall / Repair), pre-flight checks, the SYSTEM test, packaging, the dossier, and the
|
|
25
|
+
optional Graph upload.
|
|
26
|
+
|
|
27
|
+
A skill is a folder with a `SKILL.md` (YAML frontmatter + Markdown instructions), here bundled with
|
|
28
|
+
`scripts/` and `references/`. It loads progressively: the agent sees only the name and description until a
|
|
29
|
+
task makes it relevant, then the full body loads on demand.
|
|
30
|
+
|
|
31
|
+
<img width="1024" height="254" alt="image" src="https://github.com/user-attachments/assets/7c7931ba-dcae-4476-a648-11115eceb3b5" />
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
npx psadt-deploy-skill
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That installs the skill into `~/.claude/skills/psadt-deploy` and runs the setup doctor, which provisions
|
|
40
|
+
everything it can and names the handful of values only you can supply (see
|
|
41
|
+
[First-run setup](#first-run-setup)). Then open Claude Code in any folder and say what you want:
|
|
42
|
+
|
|
43
|
+
> *"Create the Win32 Intune package for 7-Zip 24.09"* — or *"package Notepad++ for Intune"*
|
|
44
|
+
|
|
45
|
+
The skill asks at most **four decision gates** (scope · deployment semantics · SYSTEM-test consent ·
|
|
46
|
+
upload confirmation). Everything else it researches and states as an assumption instead of asking.
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
Twelve phases, each owned by a script rather than by prose, so a step either passed or did not:
|
|
51
|
+
|
|
52
|
+
| Phase | What happens | Owner |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| **0** Setup | 13 prerequisite checks, GREEN/YELLOW/RED, `-Fix` provisions | `Initialize-PsadtSkill.ps1` |
|
|
55
|
+
| **1–2** Intake + research | blocker questions as clickable options; a local-evidence ladder (installed here? binary here? already written down?) answers what it can, and a research agent is dispatched only per question it leaves open | agent (gates 1–2) |
|
|
56
|
+
| **3** Scaffold | a generator writes launcher + detection + per-run log name + manifest; `New-ADTTemplate` only when none fits | `New-MsiPackage` · `New-BrowserExtensionPackage` · `New-WindowsFeaturePackage` · `New-DriverPackage` |
|
|
57
|
+
| **4** Customize | all three hooks filled from the research, helpers in the Extensions module | agent |
|
|
58
|
+
| **5** Pre-flight | 10 checks (encoding, AST parse, v3 cmdlets, structure, detection contract, manifest, log name, driver trust …) → GREEN/RED | `Invoke-PsadtPreflight.ps1` |
|
|
59
|
+
| **6** SYSTEM test | installs/uninstalls as **SYSTEM** like the IME does; **binding before any upload** | `Invoke-PsadtSystemTest.ps1` |
|
|
60
|
+
| **7** Package | one command → verified `.intunewin`, named after the app | `Invoke-PsadtPackage.ps1` |
|
|
61
|
+
| **8** Dossier | always, uploaded or not: bilingual self-contained HTML | `New-PsadtReport.ps1` |
|
|
62
|
+
| **9** Upload *(opt-in)* | dry run → confirm → `win32LobApp` via raw Graph | `Invoke-IntuneWin32Upload.ps1` |
|
|
63
|
+
| **10** Assignment *(opt-in)* | create/reuse Entra groups by naming scheme | `Invoke-IntuneAppAssignment.ps1` |
|
|
64
|
+
| **11–12** Test + rollout | DEV-VM cycles, test group, pilot → staged production | agent |
|
|
65
|
+
|
|
66
|
+
**Everything one app knows lives in `<pkg>\psadt-package.json`** — identity, the decisions taken at the
|
|
67
|
+
gates, the research findings, every phase's result and the artifacts produced. The generators write it,
|
|
68
|
+
every later phase reads and updates it, and pre-flight fails without it. That is what stops two packages of
|
|
69
|
+
the same app from disagreeing about their own version.
|
|
70
|
+
|
|
71
|
+
Depth lives in `references/` (phases 0–12 + appendices A–Q, one file per domain — see
|
|
72
|
+
`references/README.md`); `SKILL.md` stays the
|
|
73
|
+
control plane.
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
### Setup and prerequisites
|
|
78
|
+
|
|
79
|
+
- **Setup doctor** — one idempotent script checks PowerShell 7, Windows PowerShell 5.1, elevation, git,
|
|
80
|
+
PSAppDeployToolkit, the content-prep tool, `Invoke-CommandAs`, Pester, the config, a legacy config, the
|
|
81
|
+
skill tree, a pending update and the Intune credentials. Each line carries a concrete fix; `-Fix` applies
|
|
82
|
+
the ones that need no decision.
|
|
83
|
+
- **Self-healing prerequisites** — installs the PSAppDeployToolkit module from the PowerShell Gallery and
|
|
84
|
+
downloads `IntuneWinAppUtil.exe`, keeping both current against their official sources.
|
|
85
|
+
- **Config outside the skill folder** — `config.json`, `secret.dpapi` and `tools/` live in
|
|
86
|
+
`%LOCALAPPDATA%\psadt-deploy\` (override: `$env:PSADT_DEPLOY_HOME`), so a `git pull`, a re-clone or a
|
|
87
|
+
re-install can no longer take your setup with it. A pre-0.19 config keeps working and is migrated on
|
|
88
|
+
request, never deleted.
|
|
89
|
+
- **One-line install** — `npx psadt-deploy-skill` (Node 18+, zero dependencies) does clone-or-update plus
|
|
90
|
+
the doctor run in one step.
|
|
91
|
+
|
|
92
|
+
### Build and verify
|
|
93
|
+
|
|
94
|
+
- **Guided intake** — the blocker questions up front as clickable options, pre-filled with researched
|
|
95
|
+
defaults (app, latest version, installer type, package type).
|
|
96
|
+
- **Switch catalog before the web** — identifies the installer engine from the *binary* (byte signatures in
|
|
97
|
+
the PE overlay, resources and section table, not a filename guess) and serves that engine's documented
|
|
98
|
+
silent / uninstall / log / no-reboot switches from a catalog that ships with the skill. Offline, and it
|
|
99
|
+
reports every stage it checked including the misses. A candidate is still a claim until a run proves it.
|
|
100
|
+
- **Autonomous research** — checks the installed PSADT version against the latest release *and* whether
|
|
101
|
+
commands changed; researches silent install / uninstall / repair switches and known Intune pitfalls.
|
|
102
|
+
- **All three deployment types from the start** — Install, Uninstall *and* Repair, acid-tested, so
|
|
103
|
+
Company-Portal uninstalls actually work.
|
|
104
|
+
- **Pre-flight gate** — encoding/BOM, AST parse, launcher acid test, v3-cmdlet scan, hook structure,
|
|
105
|
+
detection-script contract, the package manifest, the per-run log name and driver trust. GREEN or RED,
|
|
106
|
+
with the failing file named.
|
|
107
|
+
- **Automated SYSTEM test loop** *(opt-in, binding before upload)* — installs, uninstalls and reinstalls as
|
|
108
|
+
the **SYSTEM** account via `Invoke-CommandAs`, mirroring the Intune Management Extension; reads the fresh
|
|
109
|
+
session log and the detection result, and hands back a structured verdict for the fix-and-retry loop.
|
|
110
|
+
Needs an elevated session; belongs on a VM with a snapshot.
|
|
111
|
+
- **Deterministic packaging** — `<outputRoot>\<Vendor>_<App>_<Version>_<Arch>\<same stem>.intunewin`,
|
|
112
|
+
verified after the fact (`Detection.xml`, `SetupFile`, size, SHA256), with the detection script and the
|
|
113
|
+
real logo beside it. It refuses an output folder inside the package, and never deletes a foreign
|
|
114
|
+
`.intunewin` it finds there.
|
|
115
|
+
- **One PSADT log per run** — `<Vendor>_<App>_<Version>_<Arch>_<Install|Uninstall|Repair>_<timestamp>.log`
|
|
116
|
+
instead of every run of every version appending to one unreadable file.
|
|
117
|
+
|
|
118
|
+
### Package types
|
|
119
|
+
|
|
120
|
+
The app's **native installer is always the default**. Everything else is opt-in and only on request:
|
|
121
|
+
|
|
122
|
+
- **MSI / EXE** — the ordinary case, via `New-MsiPackage.ps1` or a hand-filled scaffold.
|
|
123
|
+
- **WinGet** — the full `PSAppDeployToolkit.WinGet` lifecycle: the extension module self-heals into the
|
|
124
|
+
package, the Package ID is discovered with `Find-ADTWinGetPackage`, hooks use `*-ADTWinGet*`
|
|
125
|
+
(`-Scope Machine`). Never selected on its own initiative.
|
|
126
|
+
- **Browser extensions** — force-install via the Edge/Chrome/Firefox policy keys (including the Firefox
|
|
127
|
+
`REG_MULTI_SZ` trap), with selective removal on uninstall.
|
|
128
|
+
- **Windows features** — `Enable-WindowsOptionalFeature` and `Add-WindowsCapability`, offline source or a
|
|
129
|
+
temporary WSUS bypass that is restored afterwards, `EnablePending` handled honestly.
|
|
130
|
+
- **Third-party drivers** — the trust situation is classified *before* anything is built: Microsoft-signed
|
|
131
|
+
installs silently, vendor-signed needs the signer certificate owned in exactly one place, and a
|
|
132
|
+
vendor-signed **kernel** driver is refused because `TrustedPublisher` satisfies the PnP prompt but never
|
|
133
|
+
Code Integrity — it would install and then not load. Unsigned is refused outright, with three honest
|
|
134
|
+
options and no testsigning. Staging is per-INF `pnputil`; uninstall resolves `oemNN.inf` by original name
|
|
135
|
+
instead of a remembered index.
|
|
136
|
+
- **Script-only / remediation packages** — ESP-safe patterns for fix packages with no installer at all.
|
|
137
|
+
|
|
138
|
+
### Deliverables
|
|
139
|
+
|
|
140
|
+
- **HTML dossier — always generated**, uploaded or not. One self-contained file
|
|
141
|
+
(`Intune-Dossier.html`) built from a fixed template, never hand-assembled: the **Intune dossier** (App
|
|
142
|
+
Info, return-code map, detection rule, requirements, assignments, driver trust, and a ready-to-paste
|
|
143
|
+
**Markdown** description for the Company-Portal field) plus a **technical package report** (the three
|
|
144
|
+
hooks, PSADT cmdlets used, pre-flight and SYSTEM-test results, logo and `.intunewin` verification).
|
|
145
|
+
Bilingual with a DE/EN toggle, browser-translatable, logo embedded as a data URI.
|
|
146
|
+
- **Real logo only** — finds and downloads the actual application logo (vendor source or Wikimedia
|
|
147
|
+
Commons), verifies real pixel transparency *and* looks at the image. The PSADT default `AppIcon.png` is
|
|
148
|
+
blocked by hash.
|
|
149
|
+
- **Start Menu only** — creates Start Menu entries and removes stray desktop icons.
|
|
150
|
+
|
|
151
|
+
### Intune
|
|
152
|
+
|
|
153
|
+
- **Access as state, not as a 403** — `Test-PsadtIntuneAccess.ps1` answers before Phase 9 whether the app
|
|
154
|
+
can upload, assign groups or create policies, and for how long the credential lives. Verified / refused /
|
|
155
|
+
**unknown** are three different answers, and an offline check never overwrites what was verified before.
|
|
156
|
+
- **Direct upload via Microsoft Graph** *(opt-in)* — pushes the `.intunewin` as a `win32LobApp` (app +
|
|
157
|
+
logo), self-contained raw Graph, no third-party module. Identity comes from the manifest, so Intune shows
|
|
158
|
+
the same name and version as the artifact and the dossier. Read-only dry run → confirm → upload. Fills the
|
|
159
|
+
whole App-information tab, **never deletes an older version** (new versions coexist, with optional
|
|
160
|
+
supersedence wiring), never auto-assigns categories or notes.
|
|
161
|
+
- **One-time Entra bootstrap** — `New-PsadtEntraApp.ps1` signs in via **WAM**, creates the app, grants and
|
|
162
|
+
admin-consents the roles and stores the credential: a **certificate** (preferred — nothing secret at rest,
|
|
163
|
+
JWT client-assertion auth) or a DPAPI-encrypted client secret. Re-running it is normal: it finds the
|
|
164
|
+
recorded app, merges requested permissions instead of replacing them, and never prompts.
|
|
165
|
+
- **Opt-in group assignment** — creates/reuses Entra security groups by a configured naming scheme and
|
|
166
|
+
assigns Required / Available / Uninstall. Least-privilege (`Group.Create` + `GroupMember.Read.All`),
|
|
167
|
+
dry run → confirm, idempotent, and it never deletes a group or another app's assignment.
|
|
168
|
+
- **Certificate + firewall policies** — Custom OMA-URI profiles for `TrustedPublisher` / `TrustedPeople`
|
|
169
|
+
(the built-in template cannot reach those stores) and settings-catalog firewall-rule policies. Both
|
|
170
|
+
scripts are self-contained deliverables: they can be copied to a test client that has no skill installed.
|
|
171
|
+
|
|
172
|
+
### Operations
|
|
173
|
+
|
|
174
|
+
- **Troubleshooting** — decodes Intune error/HRESULT codes, maps symptoms to root causes, and triages the
|
|
175
|
+
right log (`AppWorkload.log`, the PSADT session log, `setupapi.dev.log` for drivers).
|
|
176
|
+
- **Self-update** — `scripts/Update-PsadtSkill.ps1` compares against GitHub, shows what changed, and
|
|
177
|
+
updates in place on your confirmation (`git pull --ff-only` for a clone, otherwise a branch-zip overwrite
|
|
178
|
+
of tracked files only). Machine-local state is never touched. Say *"psadt update"*.
|
|
179
|
+
- **503 Pester tests** over the helper scripts, including drift guards that fail when the docs and the code
|
|
180
|
+
disagree.
|
|
181
|
+
|
|
182
|
+
## Requirements
|
|
183
|
+
|
|
184
|
+
- Windows with PowerShell 5.1+ / PowerShell 7+
|
|
185
|
+
- For the `npx` installer only: **Node 18+** (the skill itself never needs Node)
|
|
186
|
+
- [PSAppDeployToolkit](https://psappdeploytoolkit.com/) v4.x *(installed/updated automatically from the
|
|
187
|
+
PowerShell Gallery if missing)*
|
|
188
|
+
- [Microsoft Win32 Content Prep Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)
|
|
189
|
+
*(provisioned automatically)*
|
|
190
|
+
- For the **SYSTEM test loop**: an **elevated** session; the
|
|
191
|
+
[`Invoke-CommandAs`](https://github.com/mkellerman/Invoke-CommandAs) module is installed automatically
|
|
192
|
+
- For the **direct Intune upload**: an Entra app with the Graph application role
|
|
193
|
+
`DeviceManagementApps.ReadWrite.All` (admin-consented) — created in one run by
|
|
194
|
+
`scripts/New-PsadtEntraApp.ps1` (WAM sign-in as Global Admin / Privileged Role Admin, device-code
|
|
195
|
+
fallback). Check what is actually in place with `scripts/Test-PsadtIntuneAccess.ps1`. Full permission
|
|
196
|
+
matrix and the manual portal route: `references/app-registration.md`.
|
|
197
|
+
- For **Pester tests**: Pester 5+ (`Install-Module Pester -MinimumVersion 5.0 -Scope CurrentUser`)
|
|
198
|
+
- **Optional (recommended): the [superpowers](https://github.com/obra/superpowers) plugin** — if installed,
|
|
199
|
+
the gated research fan-out and the reviewer gate use it. Not required: without it the skill falls back to the
|
|
200
|
+
native Agent tool and `/code-review`, and nothing in the workflow depends on the plugin.
|
|
201
|
+
|
|
202
|
+
## Installation
|
|
203
|
+
|
|
204
|
+
```powershell
|
|
205
|
+
npx psadt-deploy-skill
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Installs the **newest release** into `~/.claude/skills/psadt-deploy` and runs the setup doctor. Flags:
|
|
209
|
+
`--dir <path>` · `--project` (into `./.claude/skills`) · `--ref <tag|branch>` · `--no-setup`. Node 18+ and
|
|
210
|
+
Windows; the installer itself has zero dependencies and the package carries only `bin/` — the skill is
|
|
211
|
+
fetched from GitHub at install time.
|
|
212
|
+
|
|
213
|
+
### Which version you get
|
|
214
|
+
|
|
215
|
+
The default is the newest **release tag**, not `main`. This skill registers an Entra application with
|
|
216
|
+
admin consent and writes to an Intune tenant; installing whatever last landed on `main` is not a
|
|
217
|
+
defensible default for that.
|
|
218
|
+
|
|
219
|
+
```powershell
|
|
220
|
+
npx psadt-deploy-skill # newest release (default)
|
|
221
|
+
npx psadt-deploy-skill --ref v0.26.7 # pin an exact release
|
|
222
|
+
npx psadt-deploy-skill --ref main # the development branch, deliberately
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**For managed environments:** pin a tag, read the diff between it and the next one before moving, then
|
|
226
|
+
lift the pin. Releases are tagged `vX.Y.Z` and match the [Changelog](#changelog); tags exist from
|
|
227
|
+
**v0.24.0** onward — earlier versions predate the current history and cannot be tagged retroactively.
|
|
228
|
+
|
|
229
|
+
Re-running the installer updates an existing installation, and so does saying *"psadt update"* to Claude
|
|
230
|
+
Code. What counts as an update depends on what you installed: on a **pinned release** it is the next
|
|
231
|
+
release tag — unreleased work on `main` is deliberately invisible, because that is what pinning means. On
|
|
232
|
+
a **branch** installation it is the next commit, as before. Either way the update overwrites tracked
|
|
233
|
+
repository files only; `config.json`, `secret.dpapi` and `tools/` are never touched.
|
|
234
|
+
|
|
235
|
+
**Or clone it yourself** — the repo root *is* the skill folder:
|
|
236
|
+
|
|
237
|
+
```powershell
|
|
238
|
+
git clone https://github.com/pt1987/claude-code-psadt-skill.git "$env:USERPROFILE\.claude\skills\psadt-deploy"
|
|
239
|
+
pwsh "$env:USERPROFILE\.claude\skills\psadt-deploy\scripts\Initialize-PsadtSkill.ps1" -Fix
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`npx skills add pt1987/claude-code-psadt-skill` works too, since `SKILL.md` sits in the repository root.
|
|
243
|
+
|
|
244
|
+
No git on the machine? The installer falls back to the GitHub tarball and Windows' own `tar.exe`, so the
|
|
245
|
+
one-liner still works — including with `--ref <tag>`, which is the combination a locked-down machine
|
|
246
|
+
actually needs.
|
|
247
|
+
|
|
248
|
+
The skill activates automatically when you ask Claude Code to build an Intune package, or when you work in
|
|
249
|
+
a folder containing `Invoke-AppDeployToolkit.ps1`.
|
|
250
|
+
|
|
251
|
+
### What is deliberately not in the skill frontmatter
|
|
252
|
+
|
|
253
|
+
`SKILL.md` declares `name`, `description` and `license`, and nothing else. The omissions are choices, not
|
|
254
|
+
oversights:
|
|
255
|
+
|
|
256
|
+
- **`paths`** would look like the right way to express "activates in a folder containing
|
|
257
|
+
`Invoke-AppDeployToolkit.ps1`". It is the opposite: the field *limits* activation to files matching the
|
|
258
|
+
globs. Setting it would switch the skill off for the most common request there is — packaging an app in
|
|
259
|
+
an empty folder, where `Invoke-AppDeployToolkit.ps1` does not exist yet because Phase 3 is what creates
|
|
260
|
+
it. The folder case is covered by the last sentence of the description instead.
|
|
261
|
+
- **`allowed-tools`** grants tools up front; it does not restrict them. For a skill that installs software
|
|
262
|
+
as SYSTEM and writes to a tenant, being asked per call is the point. See [`SECURITY.md`](SECURITY.md).
|
|
263
|
+
- **`metadata.version`** is ignored by Claude Code, and the version already lives in `CHANGELOG.md`,
|
|
264
|
+
`package.json` (kept in sync by a test) and on the website. A fourth place to forget on release day, for
|
|
265
|
+
no behaviour, is not worth it.
|
|
266
|
+
- **`shell`** only matters for `!` command injection in `SKILL.md`, which this skill does not use — and a
|
|
267
|
+
failing `!` command aborts the *entire* skill invocation, so an `Initialize-PsadtSkill` call wired up that
|
|
268
|
+
way would be a single point of failure for every packaging request.
|
|
269
|
+
- **`context: fork` / `agent`** would isolate the skill in a subagent. It orchestrates its own sub-agents
|
|
270
|
+
and needs the main context to hold the decision gates.
|
|
271
|
+
|
|
272
|
+
## First-run setup
|
|
273
|
+
|
|
274
|
+
`scripts/Initialize-PsadtSkill.ps1` (also reachable by saying *"psadt setup"* / *"psadt doctor"*) checks
|
|
275
|
+
every prerequisite in one pass and reports **GREEN / YELLOW / RED**. Every line comes with a concrete fix
|
|
276
|
+
hint, and `-Fix` applies the ones that need no decision (module installs, the tool download, the
|
|
277
|
+
`language.*` defaults, `paths.intuneWinAppUtil`, and migrating a pre-0.19 config). It is idempotent — run it
|
|
278
|
+
as often as you like.
|
|
279
|
+
|
|
280
|
+
Only four values genuinely need you; the doctor lists them in `.Missing` and takes them via `-Set`:
|
|
281
|
+
|
|
282
|
+
```powershell
|
|
283
|
+
pwsh scripts/Initialize-PsadtSkill.ps1 -Fix -Set @{
|
|
284
|
+
'paths.packageRoot' = 'D:\Pakete'; 'paths.outputRoot' = 'D:\Intune'
|
|
285
|
+
'author.person' = 'Pat Taubert'; 'author.company' = 'PHAT Consulting'
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
| Setting | Purpose |
|
|
290
|
+
|---|---|
|
|
291
|
+
| `paths.packageRoot` / `outputRoot` | Where packages are built and where artifacts are written |
|
|
292
|
+
| `paths.intuneWinAppUtil` | Content-prep tool location — filled by `-Fix` |
|
|
293
|
+
| `language.script` / `dossier` | Script language (EN) vs. dossier language (DE for the Company Portal) — filled by `-Fix` |
|
|
294
|
+
| `author.person` / `company` | Stamped into every package's `AppScriptAuthor` |
|
|
295
|
+
| `intune.*` *(optional)* | Direct upload: tenant/client, credential reference, verified roles — written by `New-PsadtEntraApp.ps1` |
|
|
296
|
+
| `intune.groups.*` *(optional)* | Opt-in group assignment (`enabled` / `create` / `membershipType` / `naming`) — guide Appendix M |
|
|
297
|
+
|
|
298
|
+
### Where the setup is stored
|
|
299
|
+
|
|
300
|
+
`config.json`, `secret.dpapi` and `tools/` live in the **config home** — `%LOCALAPPDATA%\psadt-deploy\`,
|
|
301
|
+
overridable with `$env:PSADT_DEPLOY_HOME` — **not** in the skill folder, so they survive a `git pull`, a
|
|
302
|
+
re-clone and a re-install. They are machine-local and never committed. A `config.json` from a pre-0.19
|
|
303
|
+
install (beside `scripts/`) keeps working read-only; the doctor flags it and `-Fix` migrates it, renaming
|
|
304
|
+
the originals to `*.migrated` rather than deleting anything.
|
|
305
|
+
|
|
306
|
+
> DPAPI is bound to the Windows user profile: a re-installed OS invalidates a stored client secret. The
|
|
307
|
+
> doctor and `Test-PsadtIntuneAccess.ps1` both say so, and the fix is one `New-PsadtEntraApp.ps1` run.
|
|
308
|
+
|
|
309
|
+
## Project structure
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
psadt-deploy/
|
|
313
|
+
├─ SKILL.md · README.md · CHANGELOG.md · SECURITY.md · LICENSE
|
|
314
|
+
├─ package.json · bin/install.mjs the npx installer (Node 18+, zero dependencies)
|
|
315
|
+
├─ scripts/
|
|
316
|
+
│ │ setup + config
|
|
317
|
+
│ ├─ Initialize-PsadtSkill.ps1 setup doctor (Phase 0, GREEN/YELLOW/RED, -Fix/-Set)
|
|
318
|
+
│ ├─ Get-PsadtConfig.ps1 config read + config-home resolver
|
|
319
|
+
│ ├─ Set-PsadtConfig.ps1 config write (deep merge, DPAPI secret, -Remove)
|
|
320
|
+
│ ├─ Get-PsadtModule.ps1 PSADT module (self-heal)
|
|
321
|
+
│ ├─ Get-IntuneWinAppUtil.ps1 content-prep tool (self-heal)
|
|
322
|
+
│ ├─ Get-WinGetModule.ps1 WinGet extension (opt-in)
|
|
323
|
+
│ ├─ Update-PsadtSkill.ps1 self-update from GitHub
|
|
324
|
+
│ │ per-package truth
|
|
325
|
+
│ ├─ Get-PsadtPackageManifest.ps1 manifest read (+ the artifact stem)
|
|
326
|
+
│ ├─ Set-PsadtPackageManifest.ps1 manifest write (merge / append)
|
|
327
|
+
│ │ package generators
|
|
328
|
+
│ ├─ New-MsiPackage.ps1 MSI packages
|
|
329
|
+
│ ├─ New-BrowserExtensionPackage.ps1 browser-extension force-install (opt-in)
|
|
330
|
+
│ ├─ New-WindowsFeaturePackage.ps1 optional features / capabilities (opt-in)
|
|
331
|
+
│ ├─ New-DriverPackage.ps1 driver packages, pnputil staging (opt-in)
|
|
332
|
+
│ ├─ Get-DriverSignatureInfo.ps1 driver trust classifier (signed? kernel? deployable?)
|
|
333
|
+
│ │ gates + deliverables
|
|
334
|
+
│ ├─ Invoke-PsadtPreflight.ps1 pre-flight GREEN/RED gate (Phase 5, 10 checks)
|
|
335
|
+
│ ├─ Invoke-PsadtSystemTest.ps1 SYSTEM test (Phase 6)
|
|
336
|
+
│ ├─ Invoke-PsadtPackage.ps1 build the .intunewin (Phase 7, named + verified)
|
|
337
|
+
│ ├─ New-PsadtReport.ps1 HTML dossier (Phase 8, always)
|
|
338
|
+
│ │ intune / graph
|
|
339
|
+
│ ├─ New-PsadtEntraApp.ps1 Entra app bootstrap (WAM)
|
|
340
|
+
│ ├─ Get-GraphToken.ps1 app-only Graph token (cert / DPAPI)
|
|
341
|
+
│ ├─ Test-PsadtIntuneAccess.ps1 access verdict (roles, capabilities, expiry)
|
|
342
|
+
│ ├─ Invoke-IntuneWin32Upload.ps1 direct upload (Phase 9)
|
|
343
|
+
│ ├─ Invoke-IntuneAppAssignment.ps1 group assignment (Phase 10, opt-in)
|
|
344
|
+
│ ├─ New-IntuneTrustedCertPolicy.ps1 Custom OMA-URI cert policy (self-contained)
|
|
345
|
+
│ ├─ New-IntuneFirewallPolicy.ps1 firewall-rule policy (self-contained)
|
|
346
|
+
│ ├─ _GraphCommon.ps1 shared Graph helpers (retry, errors, token roles)
|
|
347
|
+
│ └─ _GraphInteractive.ps1 shared WAM sign-in
|
|
348
|
+
├─ references/
|
|
349
|
+
│ ├─ README.md the reference map (label -> file)
|
|
350
|
+
│ ├─ phases-0-6.md · phases-7-12.md the twelve phases
|
|
351
|
+
│ ├─ appendix-a-errors.md … -q-drivers.md one file per appendix
|
|
352
|
+
│ ├─ switch-catalog/ engine defaults + JSON schema (App. L.0)
|
|
353
|
+
│ ├─ Report-Template.html the fixed dossier template
|
|
354
|
+
│ └─ app-registration.md THE Graph permission matrix + manual portal route
|
|
355
|
+
└─ tests/ Pester suite, 552 tests
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Machine-local state lives outside the skill folder:
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
%LOCALAPPDATA%\psadt-deploy\ ($env:PSADT_DEPLOY_HOME overrides)
|
|
362
|
+
├─ config.json settings incl. the optional intune.* block
|
|
363
|
+
├─ secret.dpapi DPAPI client secret (only without cert auth)
|
|
364
|
+
└─ tools/ IntuneWinAppUtil.exe + WinGet module
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
And per package, next to `Invoke-AppDeployToolkit.ps1`:
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
psadt-package.json identity · gate decisions · research · results · artifacts
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Status
|
|
374
|
+
|
|
375
|
+
In active use for the full build → package → test → dossier workflow, with the direct Graph upload
|
|
376
|
+
verified against a live tenant. The helper scripts are covered by 503 Pester tests.
|
|
377
|
+
|
|
378
|
+
One open point, honestly: **the driver `pnputil` exit-code semantics are documented, not verified here.**
|
|
379
|
+
`0` / `259` / `3010` and the two `0xE...` failures come from Microsoft's documentation; confirming them
|
|
380
|
+
against `setupapi.dev.log` on a DEV VM with a real vendor-signed and a real Microsoft-signed driver is
|
|
381
|
+
still open.
|
|
382
|
+
|
|
383
|
+
## Security
|
|
384
|
+
|
|
385
|
+
This skill installs software as SYSTEM, researches on the open web, and writes to an Intune tenant
|
|
386
|
+
through an Entra app with admin consent. [`SECURITY.md`](SECURITY.md) states that risk surface next to
|
|
387
|
+
the control that already covers each part of it — the dry-run-before-execute rule, the three-valued
|
|
388
|
+
access check, never-delete, role assertion before the first write, certificate before DPAPI secret,
|
|
389
|
+
the config home outside the skill folder, and the self-containment rule for anything that ships to a
|
|
390
|
+
test client. Each control names the file that implements it and the test that enforces it, so a review
|
|
391
|
+
can check the claims rather than take them.
|
|
392
|
+
|
|
393
|
+
Two deliberate non-features are explained there as well: the skill does **not** declare
|
|
394
|
+
`allowed-tools` (that field pre-approves tools, it does not restrict them), and content fetched during
|
|
395
|
+
research is treated as data, never as instructions — see
|
|
396
|
+
[`references/research-trust.md`](references/research-trust.md).
|
|
397
|
+
|
|
398
|
+
## Roadmap
|
|
399
|
+
|
|
400
|
+
Designed and waiting to be built:
|
|
401
|
+
|
|
402
|
+
- **Sync finished packages to a GitHub repo** — a setup option (`output.target` = `local` / `git` / `both`)
|
|
403
|
+
to push the per-app artifacts (`.intunewin`, dossier, detection, logo) to a Git repo instead of, or in
|
|
404
|
+
addition to, a local folder — versioned and shareable. Will need **Git LFS** for large `.intunewin` files
|
|
405
|
+
(GitHub's 100 MB per-file limit).
|
|
406
|
+
|
|
407
|
+
Have a request? Open an issue.
|
|
408
|
+
|
|
409
|
+
## Contributing
|
|
410
|
+
|
|
411
|
+
Issues and pull requests are welcome. Keep `SKILL.md`, the references and the docs in **English**. The only
|
|
412
|
+
non-English content is the generated end-user output (the Intune dossier and the Company-Portal app
|
|
413
|
+
description), whose language follows the `language.dossier` config value — **default German**, but
|
|
414
|
+
configurable per machine.
|
|
415
|
+
|
|
416
|
+
Two conventions worth knowing before you send a patch: generated `.ps1` content is **7-bit ASCII** (the
|
|
417
|
+
pre-flight fails on non-ASCII without a BOM), and anything that lands in a package's output folder must be
|
|
418
|
+
**self-contained** — it gets copied to test clients that have no skill installed.
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
[MIT](LICENSE) © Patrick Taubert, PHAT Consulting GmbH
|
|
423
|
+
|
|
424
|
+
## Acknowledgements
|
|
425
|
+
|
|
426
|
+
- [PSAppDeployToolkit](https://github.com/PSAppDeployToolkit/PSAppDeployToolkit)
|
|
427
|
+
- [Microsoft Win32 Content Prep Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)
|
|
428
|
+
- [`Invoke-CommandAs`](https://github.com/mkellerman/Invoke-CommandAs)
|
|
429
|
+
- README structure inspired by [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills)
|
|
430
|
+
|
|
431
|
+
## Changelog
|
|
432
|
+
|
|
433
|
+
The two most recent releases are below. **[CHANGELOG.md](CHANGELOG.md)** carries the complete history,
|
|
434
|
+
every release since 0.1.0, and nothing is ever removed from it - this section is a window onto it, not a
|
|
435
|
+
second copy to keep in sync.
|
|
436
|
+
|
|
437
|
+
### 0.35.0 - 2026-09-17
|
|
438
|
+
- **Added: `scripts/New-ExePackage.ps1`** - the EXE family (Inno, NSIS, electron-builder) had no
|
|
439
|
+
generator, so every such package was hand-scaffolded. It now generates hooks that resolve the
|
|
440
|
+
uninstaller from the ARP entry at run time, WAIT for the app to actually disappear instead of
|
|
441
|
+
trusting an exit code, and detect with a version floor over the ARP entry and the binary.
|
|
442
|
+
- **Added: every action snapshots the installed-application entry, successful Installs included** -
|
|
443
|
+
including `InstallLocation`, `QuietUninstallString` and the property types PSADT hands the launcher,
|
|
444
|
+
surfaced as `InstalledAppFacts`. The old dump ran only when detection contradicted an action, so the
|
|
445
|
+
strings a resolver hook has to match were never on disk and were guessed at, one VM run per guess.
|
|
446
|
+
- **Added: fail-fast** - a red Install or Uninstall now skips Reinstall/Repair/FinalUninstall, which
|
|
447
|
+
could only re-prove the same failure. Each of four RED Firefox runs had spent ~3 minutes doing so.
|
|
448
|
+
- **Added: pre-flight `AsyncUninstall`** warns when an uninstall trusts the exit code of a vendor EXE
|
|
449
|
+
(the NSIS family relaunches from `%TEMP%` and returns instantly: 116 ms, exit 0, nothing deleted).
|
|
450
|
+
- **Changed: readable phase names** on the progress window (`SystemTaskCanary` -> "Probing: can
|
|
451
|
+
anything run as SYSTEM?"); the ids stay, because every consumer keys on them.
|
|
452
|
+
- **Changed: guest preparation dropped the always-failing WMI salvage pass and polls instead of
|
|
453
|
+
sleeping** - fixed overhead per run 139 s -> 81 s.
|
|
454
|
+
- **Fixed: a `GREEN_PARTIAL` run satisfied the upload gate** in `New-PsadtReport.ps1`.
|
|
455
|
+
|
|
456
|
+
Verified on three applications never packaged here: Audacity 4.0.0 (MSI) 12:12, VS Code 1.138.0
|
|
457
|
+
(Inno) 22:10, draw.io 31.4.5 (NSIS) **10:49 with a single VM run** - all GREEN on the first gate
|
|
458
|
+
attempt, against ~70 min and no gate at all for Firefox beforehand. Suite 631 -> 657.
|
|
459
|
+
|
|
460
|
+
### 0.34.2 - 2026-09-16
|
|
461
|
+
- **Added: `tests/SiteFigures.Tests.ps1`** - the landing page's stat tiles are now derived from the
|
|
462
|
+
repository and compared, instead of being hand-maintained and unchecked. A reader found the page
|
|
463
|
+
claiming 19 installer engines in the tile and "1 of 14" in the Phase 2 step right below it; the
|
|
464
|
+
script count had also been one behind since 0.33.0. index.html lives on gh-pages, so the suite had
|
|
465
|
+
never seen it. The guard reads it out of that ref, skips itself when the ref is absent, and the
|
|
466
|
+
workflow fetches it so CI checks it for real. Both stale figures fixed. Suite 624 -> 631.
|
|
467
|
+
|
|
468
|
+
### 0.34.1 - 2026-09-16
|
|
469
|
+
- **Changed: SKILL.md now names `-TrustedPublisherCert` at Phase 6**, with `-Scenarios` for iteration and
|
|
470
|
+
`STOP.txt` for cancelling. 0.34.0 documented the certificate in phase 6.1 only, because the control
|
|
471
|
+
plane had 55 bytes of headroom against its 5000-token budget - the wrong trade for a failure that is
|
|
472
|
+
expensive and silent, since an agent that never opens 6.1 repeats the 25-minute timeout blind. Room was
|
|
473
|
+
made the way the budget test prescribes: `rule:author-version-changelog` and `rule:start-menu-only`
|
|
474
|
+
moved to `references/conventions.md`, which already carried their full text and which SKILL.md routes
|
|
475
|
+
to. Both are editorial rules - their failure costs a doc edit or a stray desktop icon, not a
|
|
476
|
+
deployment. Control plane 17348 / 17500 bytes, headroom 55 -> 152. Suite 624, unchanged.
|
|
477
|
+
|