psadt-deploy-skill 0.35.0 → 0.37.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 +178 -477
- package/package.json +37 -37
package/README.md
CHANGED
|
@@ -1,477 +1,178 @@
|
|
|
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
|
|
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="#
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
skill
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
the
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
- **
|
|
103
|
-
|
|
104
|
-
- **
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- **
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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="#what-makes-it-different">What makes it different</a> · <a href="#go-deeper">Go deeper</a> · <a href="#security">Security</a> · <a href="#changelog">Changelog</a></sub></p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<img width="3200" height="2000" alt="psadt-workflow-v035" src="https://github.com/user-attachments/assets/d9b1c137-3e0a-4a80-a40f-9a234903bb97" />
|
|
20
|
+
|
|
21
|
+
## What is this?
|
|
22
|
+
|
|
23
|
+
A **Claude Code skill**: a reusable instruction package that teaches the agent how to build,
|
|
24
|
+
package, test, troubleshoot and deploy a **PSADT v4.x Intune Win32 app**. You name the application; the
|
|
25
|
+
skill runs the workflow - intake, research, scaffolding, all three deployment types
|
|
26
|
+
(Install / Uninstall / Repair), pre-flight checks, a SYSTEM test, packaging, the dossier, and the
|
|
27
|
+
optional Graph upload.
|
|
28
|
+
|
|
29
|
+
It loads progressively: the agent sees only the name and description until a task makes it relevant.
|
|
30
|
+
|
|
31
|
+
### What that looks like in practice
|
|
32
|
+
|
|
33
|
+
Three applications, none of them packaged here before, each taken to a `.intunewin` with a GREEN SYSTEM
|
|
34
|
+
gate on the first attempt. The times are the gate itself, read back from each run's `result.json`: all
|
|
35
|
+
five actions as `NT AUTHORITY\SYSTEM` in one throwaway sandbox, with the detection script evaluated
|
|
36
|
+
after every one of them.
|
|
37
|
+
|
|
38
|
+
| Application | Installer engine | SYSTEM gate | VM runs | Verdict |
|
|
39
|
+
|---|---|---|---|---|
|
|
40
|
+
| Audacity 4.0.0 | MSI | **3:11** | 1 | GREEN |
|
|
41
|
+
| draw.io 31.4.5 | NSIS / electron-builder | **4:19** | 1 | GREEN |
|
|
42
|
+
| VS Code 1.138.0 | Inno Setup | **6:12** | 1 | GREEN |
|
|
43
|
+
|
|
44
|
+
Those runs also produced the findings that make the packages correct: draw.io ships an MSI alongside the
|
|
45
|
+
EXE that installs **per-user** and would have vanished into the SYSTEM profile; Audacity regenerates its
|
|
46
|
+
MSI ProductCode on **every build**, so a ProductCode detection rule works exactly once; VS Code's
|
|
47
|
+
uninstaller hands back an exit code while a copy of itself is still deleting. The skill finds that kind of
|
|
48
|
+
thing before it ships, not after a helpdesk ticket.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
npx psadt-deploy-skill
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Installs the skill into `~/.claude/skills/psadt-deploy` and runs the setup doctor, which provisions
|
|
57
|
+
everything it can and names the handful of values only you can supply. Then open Claude Code in any folder
|
|
58
|
+
and say what you want:
|
|
59
|
+
|
|
60
|
+
> *"Create the Win32 Intune package for 7-Zip 24.09"* - or *"package Notepad++ for Intune"*
|
|
61
|
+
|
|
62
|
+
The skill asks at most **four decision gates** (scope · deployment semantics · SYSTEM-test consent ·
|
|
63
|
+
upload confirmation). Everything else it researches and states as an assumption instead of asking.
|
|
64
|
+
|
|
65
|
+
Details, flags, version pinning and requirements: [`docs/installation.md`](docs/installation.md).
|
|
66
|
+
|
|
67
|
+
## How it works
|
|
68
|
+
|
|
69
|
+
Thirteen phases, each owned by a script rather than by prose, so a step either passed or did not:
|
|
70
|
+
|
|
71
|
+
| Phase | What happens | Owner |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| **0** Setup | 13 prerequisite checks, GREEN/YELLOW/RED, `-Fix` provisions | `Initialize-PsadtSkill.ps1` |
|
|
74
|
+
| **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) |
|
|
75
|
+
| **3** Scaffold | a generator writes launcher + detection + per-run log name + manifest | `New-MsiPackage` · `New-ExePackage` · `New-BrowserExtensionPackage` · `New-WindowsFeaturePackage` · `New-DriverPackage` |
|
|
76
|
+
| **4** Customize | all three hooks filled from the research, helpers in the Extensions module | agent |
|
|
77
|
+
| **5** Pre-flight | 11 checks (encoding, AST parse, v3 cmdlets, structure, detection contract, manifest, log name, driver trust …) → GREEN/RED | `Invoke-PsadtPreflight.ps1` |
|
|
78
|
+
| **6** SYSTEM test | the whole loop in a throwaway Windows Sandbox, every action as **SYSTEM** like the IME does - no elevation, host untouched. **Binding before any upload** | `Invoke-PsadtSandboxTest.ps1` |
|
|
79
|
+
| **7** Package | one command → verified `.intunewin`, named after the app | `Invoke-PsadtPackage.ps1` |
|
|
80
|
+
| **8** Dossier | always, uploaded or not: bilingual self-contained HTML | `New-PsadtReport.ps1` |
|
|
81
|
+
| **9** Upload *(opt-in)* | dry run → confirm → `win32LobApp` via raw Graph | `Invoke-IntuneWin32Upload.ps1` |
|
|
82
|
+
| **10** Assignment *(opt-in)* | create/reuse Entra groups by naming scheme | `Invoke-IntuneAppAssignment.ps1` |
|
|
83
|
+
| **11-12** Test + rollout | real devices via a test group, pilot → staged production | agent |
|
|
84
|
+
|
|
85
|
+
**Everything one app knows lives in `<pkg>\psadt-package.json`** - identity, the decisions taken at the
|
|
86
|
+
gates, the research findings, every phase's result and the artifacts produced. The generators write it,
|
|
87
|
+
every later phase reads and updates it, and pre-flight fails without it. That is what stops two packages of
|
|
88
|
+
the same app from disagreeing about their own version.
|
|
89
|
+
|
|
90
|
+
## What makes it different
|
|
91
|
+
|
|
92
|
+
- **The installer engine is read from the binary**, not guessed from a filename - byte signatures in the
|
|
93
|
+
PE overlay, resources and section table - and 19 engines' documented silent switches ship with the skill,
|
|
94
|
+
offline. A candidate is still a claim until a run proves it.
|
|
95
|
+
- **The SYSTEM test is real.** Every action runs as `NT AUTHORITY\SYSTEM` through a scheduled task in a
|
|
96
|
+
throwaway Windows Sandbox - the same context the Intune Management Extension uses - and the verdict is
|
|
97
|
+
keyed on the detection script, the same rule Intune evaluates. No elevation on your machine, and the
|
|
98
|
+
machine is never modified.
|
|
99
|
+
- **An uninstall has to prove it removed something.** Inno Setup and NSIS uninstallers return an exit code
|
|
100
|
+
while a copy of themselves is still deleting; generated hooks wait for the application to disappear and
|
|
101
|
+
fail loudly if it does not.
|
|
102
|
+
- **Nothing is deleted that you did not ask to delete** - not an older Intune app version, not a foreign
|
|
103
|
+
`.intunewin`, not a group, not another app's assignment.
|
|
104
|
+
- **A dossier is produced every time**, uploaded or not: one self-contained bilingual HTML file with the
|
|
105
|
+
return-code map, the detection rule, the hooks, the test results - and a ready-to-paste Company-Portal
|
|
106
|
+
description.
|
|
107
|
+
- **657 Pester tests**, including drift guards that fail when the documentation and the code disagree -
|
|
108
|
+
one of them reads the published landing page and compares its figures against this repository.
|
|
109
|
+
|
|
110
|
+
## Go deeper
|
|
111
|
+
|
|
112
|
+
| | |
|
|
113
|
+
|---|---|
|
|
114
|
+
| [**Website**](https://pt1987.github.io/claude-code-psadt-skill/) | the phase-by-phase walkthrough, the pre-flight checks, and the trap in each of the 19 installer engines |
|
|
115
|
+
| [`docs/installation.md`](docs/installation.md) | install flags, version pinning, requirements, manual clone |
|
|
116
|
+
| [`docs/features.md`](docs/features.md) | the complete feature list, package type by package type |
|
|
117
|
+
| [`docs/setup-and-structure.md`](docs/setup-and-structure.md) | first-run setup, config home, full project structure |
|
|
118
|
+
| [`SKILL.md`](SKILL.md) | the control plane the agent actually reads |
|
|
119
|
+
| [`references/README.md`](references/README.md) | the reference map: phases 0-12 and appendices A-Q |
|
|
120
|
+
| [`SECURITY.md`](SECURITY.md) | the risk surface and the control covering each part of it |
|
|
121
|
+
|
|
122
|
+
## Status
|
|
123
|
+
|
|
124
|
+
In active use for the full build → package → test → dossier workflow, with the direct Graph upload
|
|
125
|
+
verified against a live tenant. The helper scripts are covered by 657 Pester tests.
|
|
126
|
+
|
|
127
|
+
One open point, honestly: **the driver `pnputil` exit-code semantics are documented, not verified here.**
|
|
128
|
+
`0` / `259` / `3010` and the two `0xE...` failures come from Microsoft's documentation; confirming them
|
|
129
|
+
against `setupapi.dev.log` on a DEV VM with a real vendor-signed and a real Microsoft-signed driver is
|
|
130
|
+
still open.
|
|
131
|
+
|
|
132
|
+
## Security
|
|
133
|
+
|
|
134
|
+
This skill installs software as SYSTEM, researches on the open web, and writes to an Intune tenant
|
|
135
|
+
through an Entra app with admin consent. [`SECURITY.md`](SECURITY.md) states that risk surface next to
|
|
136
|
+
the control that already covers each part of it, and each control names the file that implements it and
|
|
137
|
+
the test that enforces it - so a review can check the claims rather than take them.
|
|
138
|
+
|
|
139
|
+
Two deliberate non-features: the skill does **not** declare `allowed-tools` (that field pre-approves
|
|
140
|
+
tools, it does not restrict them), and content fetched during research is treated as data, never as
|
|
141
|
+
instructions - see [`references/research-trust.md`](references/research-trust.md).
|
|
142
|
+
|
|
143
|
+
## Roadmap
|
|
144
|
+
|
|
145
|
+
**Sync finished packages to a GitHub repo** - a setup option (`output.target` = `local` / `git` / `both`)
|
|
146
|
+
to push the per-app artifacts to a Git repo instead of, or in addition to, a local folder. Will need
|
|
147
|
+
**Git LFS** for large `.intunewin` files. Have a request? Open an issue.
|
|
148
|
+
|
|
149
|
+
## Contributing
|
|
150
|
+
|
|
151
|
+
Issues and pull requests are welcome. Keep `SKILL.md`, the references and the docs in **English** - the
|
|
152
|
+
only non-English content is the generated end-user output, whose language follows `language.dossier`
|
|
153
|
+
(default German). Two conventions worth knowing before you send a patch: generated `.ps1` content is
|
|
154
|
+
**7-bit ASCII** (pre-flight fails on non-ASCII without a BOM), and anything that lands in a package's
|
|
155
|
+
output folder must be **self-contained**, because it gets copied to test clients that have no skill
|
|
156
|
+
installed.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
[MIT](LICENSE) © Patrick Taubert, PHAT Consulting GmbH
|
|
161
|
+
|
|
162
|
+
## Acknowledgements
|
|
163
|
+
|
|
164
|
+
- [PSAppDeployToolkit](https://github.com/PSAppDeployToolkit/PSAppDeployToolkit)
|
|
165
|
+
- [Microsoft Win32 Content Prep Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)
|
|
166
|
+
- [`Invoke-CommandAs`](https://github.com/mkellerman/Invoke-CommandAs)
|
|
167
|
+
- README structure inspired by [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills)
|
|
168
|
+
|
|
169
|
+
## Changelog
|
|
170
|
+
|
|
171
|
+
**[CHANGELOG.md](CHANGELOG.md)** carries the complete history, every release since 0.1.0, and nothing is
|
|
172
|
+
ever removed from it.
|
|
173
|
+
|
|
174
|
+
Latest: **0.37.0 - The MSI skip was the wrong half of a correct sentence.** The verified-switch store
|
|
175
|
+
introduced in 0.36.0 skipped MSI packages because msiexec's switches are deterministic. Their
|
|
176
|
+
PROPERTIES are not, and the researched `ADDLOCAL` selections are the expensive half. MSI packages are
|
|
177
|
+
recorded now; what is refused is a property that looks like a secret. The engine probe also reports
|
|
178
|
+
ProductName for MSI files, which it never could, so the same-product fallback works for them at all.
|
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "psadt-deploy-skill",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Installer for the psadt-deploy Claude Code skill: build, test and deploy PSADT v4.x Intune Win32 packages.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"psadt",
|
|
7
|
-
"psappdeploytoolkit",
|
|
8
|
-
"intune",
|
|
9
|
-
"win32",
|
|
10
|
-
"claude-code",
|
|
11
|
-
"claude-skill",
|
|
12
|
-
"packaging"
|
|
13
|
-
],
|
|
14
|
-
"homepage": "https://github.com/pt1987/claude-code-psadt-skill#readme",
|
|
15
|
-
"bugs": {
|
|
16
|
-
"url": "https://github.com/pt1987/claude-code-psadt-skill/issues"
|
|
17
|
-
},
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/pt1987/claude-code-psadt-skill.git"
|
|
21
|
-
},
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"author": "Patrick Taubert, PHAT Consulting GmbH",
|
|
24
|
-
"type": "module",
|
|
25
|
-
"bin": {
|
|
26
|
-
"psadt-deploy-skill": "bin/install.mjs"
|
|
27
|
-
},
|
|
28
|
-
"files": [
|
|
29
|
-
"bin"
|
|
30
|
-
],
|
|
31
|
-
"engines": {
|
|
32
|
-
"node": ">=18"
|
|
33
|
-
},
|
|
34
|
-
"os": [
|
|
35
|
-
"win32"
|
|
36
|
-
]
|
|
37
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "psadt-deploy-skill",
|
|
3
|
+
"version": "0.37.0",
|
|
4
|
+
"description": "Installer for the psadt-deploy Claude Code skill: build, test and deploy PSADT v4.x Intune Win32 packages.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"psadt",
|
|
7
|
+
"psappdeploytoolkit",
|
|
8
|
+
"intune",
|
|
9
|
+
"win32",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"claude-skill",
|
|
12
|
+
"packaging"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/pt1987/claude-code-psadt-skill#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/pt1987/claude-code-psadt-skill/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/pt1987/claude-code-psadt-skill.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Patrick Taubert, PHAT Consulting GmbH",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"psadt-deploy-skill": "bin/install.mjs"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"os": [
|
|
35
|
+
"win32"
|
|
36
|
+
]
|
|
37
|
+
}
|