Commit initial

This commit is contained in:
2026-04-15 17:57:12 +02:00
parent 005d8e797e
commit 55516ee10f
269 changed files with 26854 additions and 0 deletions
+350
View File
@@ -0,0 +1,350 @@
# Pitfalls Research
**Domain:** Printer deployment webapp — PowerShell script generation, .intunewin packaging, driver management
**Researched:** 2026-04-10
**Confidence:** HIGH (most findings verified against official Microsoft docs and reputable community sources)
---
## Critical Pitfalls
### Pitfall 1: IntuneWinAppUtil.exe Is Windows-Only — Cannot Run in a Linux Docker Container
**What goes wrong:**
The project plans to bundle IntuneWinAppUtil.exe inside the Docker container to produce .intunewin files. If the Docker image is Linux-based (the default and most common choice), the Windows-only .exe cannot execute. The .intunewin file simply cannot be generated, making the core export feature non-functional.
**Why it happens:**
Development starts on a local Windows machine where bundling the .exe seems natural. The Linux container incompatibility only surfaces at deployment time or when the container image is built on a CI system. The Microsoft tool has no Linux build and cannot run under Wine reliably in production.
**How to avoid:**
Use the `Svrooij.ContentPrep` C# library (NuGet) or its `SvRooij.ContentPrep.Cmdlet` PowerShell module as a cross-platform reimplementation of the .intunewin format. It targets .NET Standard 2.0, runs on Linux, is open-source, and is faster than the official tool. Integrate it directly into the webapp's backend rather than shelling out to an .exe. The format has been reverse-engineered and documented: it is a ZIP archive with AES-256 encrypted content and a metadata XML wrapper.
**Warning signs:**
- Docker base image is `ubuntu`, `debian`, `alpine`, or any non-Windows image
- Any reference to `Process.Start("IntuneWinAppUtil.exe", ...)` in the codebase
- `.intunewin` generation works locally on dev machine but fails in container
**Phase to address:**
Foundation / architecture phase — the packaging approach must be decided before any export feature is built. Choosing the wrong approach here causes a full rewrite of the export module.
---
### Pitfall 2: Two-Step Driver Installation Skipped — pnputil Before Add-PrinterDriver
**What goes wrong:**
The generated PowerShell script calls `Add-PrinterDriver` directly with an INF file path. This always fails with "The specified driver does not exist in the driver store." `Add-PrinterDriver` cannot install a driver from a raw INF — it can only reference drivers already staged in the Windows Driver Store.
**Why it happens:**
The PowerShell documentation for `Add-PrinterDriver` does not make this prerequisite obvious. Developers assume it works like a driver setup wizard. The error message ("driver does not exist") is also misleading — the driver file is present, but it has not been staged.
**How to avoid:**
The generated install script must always follow this two-step sequence:
1. `pnputil.exe /add-driver ".\drivers\*.inf" /subdirs /install` — stages the driver into the Driver Store
2. `Add-PrinterDriver -Name "Exact Driver Name from INF"` — registers the staged driver
The driver name passed to step 2 must exactly match the `DriverDesc` value inside the INF file — not the filename, not a display label from the UI.
**Warning signs:**
- Script uses `Add-PrinterDriver` without a preceding `pnputil` call
- Driver name is derived from user-entered text rather than parsed from the INF file
- Test machines work (driver previously staged) but fresh endpoints fail
**Phase to address:**
Script generation phase. The INF parser must extract the correct driver name, and the script template must enforce the two-step sequence unconditionally.
---
### Pitfall 3: 32-bit PowerShell Execution Context in Intune — WOW64 Redirect
**What goes wrong:**
Intune's Management Extension (IME) launches Win32 app install scripts in a 32-bit PowerShell process by default. In 32-bit context, `C:\Windows\System32` is silently redirected to `C:\Windows\SysWOW64`, and registry writes to `HKLM:\SOFTWARE` go to `HKLM:\SOFTWARE\WOW6432Node` instead. Printer driver staging via `pnputil` called from `System32` breaks. Registry detection rules written for 64-bit paths miss the actual keys written by a 32-bit installer.
**Why it happens:**
The IME host process `IntuneManagementExtension.exe` is a 32-bit process. Unless explicitly forced otherwise, all child processes inherit this context. This is a well-documented but frequently overlooked Intune behavior.
**How to avoid:**
The generated install script must include a self-relaunch guard at the top that detects 32-bit execution on a 64-bit OS and relaunches itself in 64-bit PowerShell:
```powershell
if ($env:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
$scriptPath = $PSCOMMANDPATH
& "$env:WINDIR\SysNative\WindowsPowerShell\v1.0\powershell.exe" `
-NoProfile -ExecutionPolicy Bypass -File $scriptPath @args
exit $LASTEXITCODE
}
```
All generated scripts must include this block unconditionally. The Intune package install command must also reference `%WinDir%\SysNative\WindowsPowerShell\v1.0\PowerShell.exe` rather than the default `powershell.exe`.
**Warning signs:**
- Script works when tested interactively but fails in Intune deployment
- `pnputil` path errors on 64-bit machines
- Registry detection rules find nothing despite successful local testing
- `$env:PROCESSOR_ARCHITEW6432` is not checked anywhere in the generated script
**Phase to address:**
Script generation phase — the 64-bit relaunch guard must be in the base script template from day one.
---
### Pitfall 4: Elevation Detection Conflates "Administrator" with "SYSTEM"
**What goes wrong:**
The generated script checks `[Security.Principal.WindowsPrincipal]::IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)` to decide whether to self-elevate. This returns `$true` for both an elevated user AND for the SYSTEM account. The script then either skips self-elevation when running as SYSTEM (correct), or incorrectly triggers a UAC prompt when running in an elevated non-SYSTEM context, breaking silent Intune deployment.
A second common mistake: the script self-elevates by launching `Start-Process powershell -Verb RunAs`, which triggers a UAC dialog. When Intune runs in SYSTEM context, UAC prompts never appear — the elevated child process launches silently but the parent exits with code 0, causing Intune to mark the deployment as successful while the real installation runs in a detached process with no error reporting.
**Why it happens:**
SYSTEM and "elevated administrator" are conflated. SYSTEM always passes the `IsInRole(Administrator)` check, so the check alone cannot distinguish "running under Intune/RMM as SYSTEM" from "running as an elevated local admin user."
**How to avoid:**
Use the identity name to explicitly detect SYSTEM:
```powershell
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$isSystem = $identity -eq "NT AUTHORITY\SYSTEM"
$isAdmin = ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
if ($isSystem) {
# Running under Intune/NinjaRMM — no elevation needed, proceed directly
} elseif (-not $isAdmin) {
# Running as unprivileged user — self-elevate via Start-Process -Verb RunAs
# Then exit immediately so Intune does not track two instances
}
```
When self-elevating, the parent process must `exit` immediately after launching the elevated child, and the child must perform all real work and exit with the correct code.
**Warning signs:**
- Elevation check only uses `IsInRole` without checking for SYSTEM identity
- Self-elevation uses `Start-Process -Verb RunAs` without immediate `exit` of the parent
- Intune reports "Success" but printer is not installed
**Phase to address:**
Script generation phase — the elevation logic is part of the base script template. Write and test this logic against a SYSTEM-context simulator (e.g., PsExec -s) before shipping.
---
### Pitfall 5: Driver Name Mismatch Between UI Label and INF DriverDesc
**What goes wrong:**
The webapp lets users type a "driver name" free-form, or shows them the filename of the uploaded INF. The generated script passes this string to `Add-PrinterDriver -Name`. If the string does not exactly match the `DriverDesc` value inside the INF file (including spaces, capitalization, and special characters), the command fails silently or with a cryptic error.
**Why it happens:**
The relationship between the INF filename, the device model name, and the `DriverDesc` field is non-obvious. Driver packages for multi-model lines (HP, Ricoh) contain dozens of `DriverDesc` entries in the same INF. The correct one is not always the first, and it is not the filename.
**How to avoid:**
The webapp must parse uploaded INF files at upload time and extract all `DriverDesc` values from `[Version]` and `[Manufacturer]` sections. Present these as a dropdown — never trust free-form user entry for the driver name. Store the parsed name in the driver record, and use it verbatim in script generation. INF parsing is straightforward text processing: find lines matching `DriverDesc\s*=\s*(.+)` in the INF file.
**Warning signs:**
- Driver name field is a free-text input rather than a parsed value
- Driver upload stores only the filename, not the parsed metadata
- No INF parsing step in the driver upload workflow
**Phase to address:**
Driver management phase — INF parsing must happen at upload time, not at export time.
---
### Pitfall 6: Unsigned or Test-Signed Drivers Block Installation on Windows 11
**What goes wrong:**
Older or third-party printer drivers may lack a valid `.cat` (catalog) digital signature file or may be test-signed. Windows 10 (21H2+) and Windows 11 enforce driver signature requirements by default. `pnputil /add-driver` fails with "The third-party INF does not contain digital signature information." The error appears in the deployment log but not always surfaced to the technician.
**Why it happens:**
MSPs dealing with legacy hardware (especially older Brother, Ricoh, or Canon models) frequently encounter legacy drivers that were signed with expired certificates or not at all. The issue is invisible during testing on machines where the driver was previously installed via a setup wizard that bypassed enforcement.
**How to avoid:**
At driver upload time, the webapp should inspect the uploaded ZIP/INF package for the presence of a `.cat` file alongside the INF. If absent, warn the technician immediately with a clear message: "This driver package has no catalog file. Installation may fail on Windows 10 21H2+ and Windows 11 unless driver signature enforcement is disabled — which is not recommended for managed endpoints." The generated script should log `pnputil` output to a temp file so failures are captured. Recommend technicians obtain WHQL-certified drivers from manufacturer download portals.
**Warning signs:**
- Uploaded driver ZIP contains `.inf` but no `.cat` file
- `pnputil` output is not captured or logged in the generated script
- Deployment works on older test machines but fails on Windows 11 endpoints
**Phase to address:**
Driver management phase (upload validation) and script generation phase (pnputil output logging).
---
### Pitfall 7: Generated Scripts Are Not Idempotent — Re-runs Cause Errors
**What goes wrong:**
Intune will re-run the install script if the detection rule fails to match (e.g., after a Windows update, a reimaging, or a detection script bug). If the script does not check for existing state before acting, it throws errors: `Add-PrinterPort` fails with "Port already exists," `Add-Printer` fails with "Printer already exists," and the script exits with a non-zero code, triggering endless Intune retry loops.
**Why it happens:**
Script authors test the happy path (fresh machine), not the re-run path. Idempotency is easy to overlook in a one-shot deployment context.
**How to avoid:**
Every generated script must guard each step:
```powershell
# Port
if (-not (Get-PrinterPort -Name $portName -ErrorAction SilentlyContinue)) {
Add-PrinterPort -Name $portName -PrinterHostAddress $ipAddress
}
# Printer
if (-not (Get-Printer -Name $printerName -ErrorAction SilentlyContinue)) {
Add-Printer -Name $printerName -DriverName $driverName -PortName $portName
}
```
The detection rule (separate from the install script) must check for the printer queue by exact name using `Get-Printer`. The generated detection script must be included in the package and documented.
**Warning signs:**
- Script template uses `Add-PrinterPort` / `Add-Printer` without `Get-` guards
- No detection script template included in the export package
- Technicians report "deployment loops" or repeated installs
**Phase to address:**
Script generation phase — idempotency guards belong in the base template, not as an afterthought.
---
### Pitfall 8: Script Injection via Unsanitized User Input in Generated PowerShell
**What goes wrong:**
The webapp takes user input (printer name, IP address, port name, driver name) and interpolates it into a generated PowerShell script string. A malicious or careless input like `MyPrinter"; Remove-Item C:\Windows -Recurse -Force; #` breaks out of the string literal and injects arbitrary PowerShell commands into the generated script. The generated script is then downloaded and executed with SYSTEM privileges on endpoints.
**Why it happens:**
Script generation via string templates is the obvious implementation approach. Developers working on an internal tool often deprioritize injection risks, especially when no authentication is required and the tool is network-isolated.
**How to avoid:**
Never interpolate raw user input into script string literals. Use PowerShell's own string quoting rules — single-quoted strings (`'...'`) do not interpolate. For values that must appear inside double-quoted strings, escape all embedded quotes and special characters, or use parameter passing at the call site rather than embedding values inline. Validate all inputs before storage: IP addresses via regex `^\d{1,3}(\.\d{1,3}){3}$`, printer names against a character allowlist `[A-Za-z0-9 \-_()]`, port names similarly. Reject inputs that fail validation at the form level. Treat this as non-negotiable even for an internal tool — a compromised technician machine or a mistake can still cause damage.
**Warning signs:**
- Script template uses `"...$printerName..."` without escaping
- No input validation on printer name, IP, or port name fields
- Generated script contains verbatim user-provided strings
**Phase to address:**
Script generation phase. Input validation must be defined in the data model before the template engine is built.
---
## Technical Debt Patterns
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|----------|-------------------|----------------|-----------------|
| Hard-code `IntuneWinAppUtil.exe` as the packaging backend | Fastest path to a working demo on Windows | Breaks entirely in Linux Docker; requires full rewrite of export module | Never — decide on cross-platform library from day one |
| Free-text driver name input instead of INF parsing | Faster UI to build | Constant technician error; driver not found failures; support burden | Never for the name field; free-text acceptable for display name only |
| Single-step driver install (no pnputil) | Simpler script template | 100% failure rate on clean endpoints | Never |
| No idempotency guards in install script | Simpler code | Endless Intune retry loops when detection logic has edge cases | Never in generated scripts |
| Skip 64-bit relaunch guard | Smaller script | Silent failures on 64-bit Intune-managed machines (the majority of fleet) | Never |
| Store uploaded driver ZIPs with original filenames | Zero-effort storage | Path traversal risk; filename collisions across clients; no sanitization | Never — normalize to UUID-based filenames at upload |
| No logging in generated scripts | Shorter scripts | Zero diagnostic information when deployment fails; blind troubleshooting | MVP: acceptable if a `$logPath` variable placeholder is in the template from the start |
---
## Integration Gotchas
| Integration | Common Mistake | Correct Approach |
|-------------|----------------|------------------|
| Intune Win32 app upload | Packaging with IntuneWinAppUtil.exe from Docker/Linux | Use `Svrooij.ContentPrep` C# library — cross-platform, no .exe dependency |
| Intune deployment context | Assuming scripts run as 64-bit | Add `PROCESSOR_ARCHITEW6432` relaunch guard; reference `SysNative` path in install command |
| Intune detection rules | Using file-based detection (driver INF path) | Use PowerShell detection script: `Get-Printer -Name "ExactName"` then `exit 0` or `exit 1` |
| NinjaRMM script execution | Relying on default execution policy | Wrap all generated scripts with `-ExecutionPolicy Bypass` in the call instruction documented in the ZIP README |
| pnputil | Calling with relative paths | Use `$PSScriptRoot` to build absolute paths to INF files; pnputil does not resolve relative paths reliably under SYSTEM |
| Add-PrinterDriver | Passing display name or filename | Parse and store `DriverDesc` from INF at upload time; pass exact value at script generation time |
---
## Performance Traps
| Trap | Symptoms | Prevention | When It Breaks |
|------|----------|------------|----------------|
| Synchronous .intunewin generation on request | Web request hangs for 5-30 seconds during packaging of large driver sets | Run packaging as a background job; return a job ID; poll or use a progress endpoint | Any driver bundle over ~50 MB |
| Storing driver ZIPs in-memory during upload | Memory spikes; container OOM on large driver packages (HP PCL6 drivers can be 200 MB+) | Stream upload directly to disk; set a hard upload size limit with feedback | Files over ~50 MB on containers with 512 MB RAM |
| Re-packaging every export request | Slow UX for technicians exporting the same printer repeatedly | Cache generated packages keyed by (printer config hash + driver hash); invalidate on config change | High-frequency re-export scenario (multi-client MSP) |
---
## Security Mistakes
| Mistake | Risk | Prevention |
|---------|------|------------|
| Injecting unsanitized user input into script string templates | Generated scripts contain arbitrary PowerShell executed as SYSTEM on endpoints — full system compromise | Whitelist-validate all inputs; use single-quoted PS strings or explicit escaping; never use `Invoke-Expression` in templates |
| Storing uploaded driver files with user-provided filenames | Path traversal: a filename like `../../etc/passwd` or `../../app/main.py` overwrites application files | Rename all uploaded files to `{uuid}{ext}` immediately on receipt; validate extension against allowlist (`.zip`, `.inf`, `.cab`) |
| No file type validation on driver uploads | A technician (or attacker on the internal network) uploads an executable disguised as a driver | Check MIME type AND file magic bytes, not just extension; reject anything not ZIP/INF/CAB |
| Executing arbitrary uploaded content on the server side | If the server processes INF files by shelling out to Windows tools, a crafted INF could exploit the parser | Parse INF files with a safe text parser (regex/line scan); never execute uploaded files server-side |
| No-auth endpoint serving driver downloads | Internal tool assumption breaks if the container is accidentally exposed; driver packages can be exfiltrated | Document network isolation requirement explicitly; add a health-check-only public endpoint; bind to localhost or internal interface only |
---
## UX Pitfalls
| Pitfall | User Impact | Better Approach |
|---------|-------------|-----------------|
| No validation feedback on driver upload | Technician uploads a driver, generates a package, deploys it, and only discovers the driver was unsigned or malformed after endpoint failure | Parse and validate the INF at upload time; show driver name, version, architecture, and signature status before the upload is confirmed |
| Driver name is free-text | Technician guesses the driver name; script fails silently on endpoints | Parse `DriverDesc` from INF; present as a read-only confirmed value or a dropdown if multiple models exist |
| No copy-to-clipboard for detection script | Technicians manually retype the detection rule into Intune, introducing errors | Include a "copy detection script" button alongside every generated package |
| No package preview before download | Technician downloads, uploads to Intune, and only then realizes IP was wrong | Show a collapsible "what's in this package" summary: script preview, driver files list, icon, detection script |
| Export fails silently with generic error | Technician has no actionable information | Show specific error: "Driver INF not found in ZIP," "Driver name not matched," "Packaging failed: [detail]" |
---
## "Looks Done But Isn't" Checklist
- [ ] **Driver staging:** Script calls `pnputil /add-driver` before `Add-PrinterDriver` — verify with a clean VM, not a dev machine where the driver is pre-staged
- [ ] **64-bit guard:** `$env:PROCESSOR_ARCHITEW6432` check is the first meaningful block in every generated install script
- [ ] **SYSTEM detection:** Script distinguishes SYSTEM identity from elevated-administrator identity before any self-elevation logic
- [ ] **Idempotency:** Re-running the install script on a machine where the printer is already installed exits with code 0 and makes no changes
- [ ] **Detection script:** Every exported package includes a separate detection script, not just the install script
- [ ] **Driver name source:** Driver name in generated script comes from parsed INF `DriverDesc`, not from user text field
- [ ] **INF signature check:** Upload flow warns when no `.cat` file is present in the driver package
- [ ] **.intunewin format:** Package can be created from the Docker container without `IntuneWinAppUtil.exe`
- [ ] **NinjaRMM ZIP:** ZIP contains a `README.txt` or `INSTALL.txt` explaining how to run the script (`-ExecutionPolicy Bypass`)
- [ ] **Port guard:** `Add-PrinterPort` is preceded by `Get-PrinterPort` check
- [ ] **Input validation:** Printer name, IP address, and port name fields reject invalid characters at form submission
---
## Recovery Strategies
| Pitfall | Recovery Cost | Recovery Steps |
|---------|---------------|----------------|
| IntuneWinAppUtil.exe bundled in Linux container | HIGH | Rewrite export module to use `Svrooij.ContentPrep` C# library; test .intunewin format compatibility with Intune upload |
| Driver name free-text in DB with wrong values | MEDIUM | Add INF parser; run migration to re-parse all stored driver packages and update name fields; regenerate cached packages |
| No 64-bit guard in script template | LOW | Add guard to base template; regenerate all cached packages; inform technicians to re-download existing packages |
| Unsigned driver deployed to fleet | HIGH | Remove printer and driver from all endpoints via remediation script; source signed driver; redeploy |
| Script injection in generated output | HIGH | Audit all stored printer configs for malicious inputs; add validation to data model; re-generate all scripts |
---
## Pitfall-to-Phase Mapping
| Pitfall | Prevention Phase | Verification |
|---------|------------------|--------------|
| IntuneWinAppUtil.exe Linux incompatibility | Foundation / Architecture | Build and run the container on a Linux host; generate a .intunewin file; validate it uploads to Intune |
| pnputil two-step requirement | Script generation | Deploy generated script to a clean Windows 11 VM via Intune; confirm printer appears |
| 32-bit WOW64 execution context | Script generation | Deploy via Intune (not interactive); check `$env:PROCESSOR_ARCHITEW6432` in script log |
| SYSTEM vs. elevated-admin confusion | Script generation | Run script via PsExec -s on a test machine; confirm no UAC prompt; confirm correct behavior |
| Driver name mismatch | Driver management (upload) | Upload a multi-model HP or Ricoh INF; verify dropdown shows correct `DriverDesc` values |
| Unsigned driver blocking | Driver management (upload) | Upload a driver ZIP with no `.cat` file; confirm warning is shown before upload completes |
| Non-idempotent scripts | Script generation | Run install script twice on the same machine; confirm exit code 0, no errors, no duplicate printers |
| Script injection | Script generation + input validation | Submit `"; malicious code #` as printer name; verify it is rejected at form level and absent from generated script |
| Driver filename path traversal | Driver management (upload) | Upload a file named `../../test.txt`; verify it is stored as a UUID-named file |
---
## Sources
- MSEndpointMgr: [Install Network Printers via Intune Win32 Apps](https://msendpointmgr.com/2022/01/03/install-network-printers-intune-win32apps-powershell/)
- Call4Cloud: [Deploy Printer Drivers Intune — pnputil/Printbrm/PrnDrvr](https://call4cloud.nl/deploy-printer-drivers-intune-win32app/)
- Call4Cloud: [Sysnative, 64-bit, WOW6432Node in Intune](https://call4cloud.nl/sysnative-64-bit-ime-intune-syswow64-wow6432node/)
- Svrooij.io: [Open-source Intune Content Prep — cross-platform reimplementation](https://svrooij.io/2023/10/19/open-source-intune-content-prep/)
- Svrooij.io: [Analysing the Win32 Content Prep Tool format](https://svrooij.io/2023/10/04/analysing-win32-content-prep-tool/)
- Microsoft Learn: [Install a printer driver via PowerShell](https://learn.microsoft.com/en-us/answers/questions/1180091/install-a-printer-via-powershell-script)
- Microsoft Learn: [Preventing script injection in PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/security/preventing-script-injection?view=powershell-7.5)
- Microsoft Learn: [Prepare a Win32 App for Intune](https://learn.microsoft.com/en-us/intune/intune-service/apps/apps-win32-prepare)
- WOSHub: [PowerShell managing printers and drivers](https://woshub.com/powershell-managing-printers-and-their-drivers-in-windows-8/)
- SMBtotheCloud: [User vs System install behavior in Intune](https://smbtothecloud.com/user-vs-system-install-behavior-know-what-your-scripts-are-doing-and-how-to-open-powershell-as-system/)
- Patchmypc: [Intune Win32 PowerShell 64-bit switch not working](https://patchmypc.com/blog/intune-win32-powershell-script-installer-64-bit-switch-not-working/)
- Dennis Span: [Printer Drivers Installation and Troubleshooting Guide](https://dennisspan.com/printer-drivers-installation-and-troubleshooting-guide/)
- GitHub Microsoft: [Microsoft Win32 Content Prep Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)
---
*Pitfalls research for: ImpTune — printer deployment webapp*
*Researched: 2026-04-10*