diff --git a/.planning/phases/04-script-generation/04-RESEARCH.md b/.planning/phases/04-script-generation/04-RESEARCH.md
new file mode 100644
index 0000000..6d4e697
--- /dev/null
+++ b/.planning/phases/04-script-generation/04-RESEARCH.md
@@ -0,0 +1,584 @@
+# Phase 4: Script Generation - Research
+
+**Researched:** 2026-04-10
+**Domain:** Jinja2 template-based PowerShell script generation; Intune/RMM printer deployment patterns
+**Confidence:** HIGH
+
+---
+
+## Summary
+
+Phase 4 produces three PowerShell scripts — install, uninstall, and detection — rendered from
+Jinja2 templates stored in `imptune/templates/scripts/`. The project already uses Jinja2 3.1.*
+(it is in `requirements.txt` and drives all HTML pages), so no new dependency is required.
+Script generation fits naturally as a new module `imptune/generators/script_generator.py` plus a
+FastAPI router `imptune/api/scripts.py`, following the exact same patterns as `intunewin_builder.py`
+and the existing API routers.
+
+The most important correctness risk is a **duplex mode name mismatch**: the `Printer` model stores
+`OneSided | LongEdge | ShortEdge`, but `Set-PrintConfiguration -DuplexingMode` accepts
+`OneSided | TwoSidedLongEdge | TwoSidedShortEdge`. The templates must translate these values.
+
+The second highest risk is the **WOW64 / 32-bit Intune execution context**: Intune's Win32 app
+installer runs in a 32-bit PowerShell process. `pnputil.exe` does not exist under SysWOW64, so the
+install script must detect the 32-bit environment and relaunch itself under 64-bit PowerShell before
+any driver operations occur.
+
+**Primary recommendation:** Render scripts from Jinja2 `.ps1.j2` templates with
+`trim_blocks=True, lstrip_blocks=True`. Place templates at
+`imptune/templates/scripts/{install,uninstall,detect}.ps1.j2`. Return rendered content as
+`PlainTextResponse` with `Content-Disposition: attachment` from a GET endpoint.
+
+---
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| SCRPT-01 | Generate PowerShell install script (pnputil staging + Add-PrinterPort + Add-PrinterDriver + Add-Printer + Set-PrintConfiguration) | Verified PowerShell cmdlets; pnputil two-step pattern documented below |
+| SCRPT-02 | Generate PowerShell uninstall script (Remove-Printer + Remove-PrinterDriver + Remove-PrinterPort) | Standard cmdlets; idempotency via -ErrorAction SilentlyContinue |
+| SCRPT-03 | Generate Intune detection script (printer-name registry check → exit 0 / exit 1) | Intune detection contract verified: Write-Output + exit 0 for present, exit 1 for absent |
+| SCRPT-04 | Install script detects SYSTEM vs user context and self-elevates via UAC when run by user | Pattern verified: [Environment]::UserName check + Start-Process -Verb Runas |
+| SCRPT-05 | Install script includes 64-bit WOW64 relaunch guard for Intune's 32-bit execution context | Pattern verified: $env:PROCESSOR_ARCHITECTURE + SysNative path |
+
+
+---
+
+## Standard Stack
+
+### Core (already installed — no new packages needed)
+
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| Jinja2 | 3.1.* | Template rendering engine | Already in requirements.txt; used for all HTML pages |
+| FastAPI | 0.115.* | HTTP routing + response types | Already in requirements.txt; existing router pattern |
+| Peewee | 3.17.* | ORM for reading Printer + Driver records | Already in requirements.txt |
+
+### No New Dependencies
+
+Script generation requires **zero new packages**. Jinja2 is the template engine; FastAPI returns
+`PlainTextResponse` with an attachment header; the Printer/Driver ORM models provide all data.
+
+### Installation
+
+```bash
+# Nothing to install — all dependencies already present in requirements.txt
+```
+
+---
+
+## Architecture Patterns
+
+### Recommended File Layout
+
+```
+imptune/
+├── generators/
+│ ├── __init__.py
+│ ├── intunewin_builder.py # existing
+│ └── script_generator.py # NEW — renders templates to strings
+├── templates/
+│ ├── scripts/ # NEW directory
+│ │ ├── install.ps1.j2
+│ │ ├── uninstall.ps1.j2
+│ │ └── detect.ps1.j2
+│ └── ... (existing HTML templates)
+├── api/
+│ ├── scripts.py # NEW — GET /printers/{id}/scripts/{type}
+│ └── ... (existing routers)
+tests/
+└── test_script_generator.py # NEW
+```
+
+### Pattern 1: Jinja2 Environment for Script Templates
+
+Use a separate `Environment` with `trim_blocks=True` and `lstrip_blocks=True` to prevent Jinja
+control-block lines from producing blank lines in rendered scripts.
+
+```python
+# imptune/generators/script_generator.py
+from pathlib import Path
+from jinja2 import Environment, FileSystemLoader
+
+_SCRIPTS_DIR = Path(__file__).parent.parent / "templates" / "scripts"
+
+_env = Environment(
+ loader=FileSystemLoader(str(_SCRIPTS_DIR)),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ keep_trailing_newline=True,
+)
+
+def render_install(printer, inf_filename: str, driver_name: str) -> str:
+ """Render install.ps1.j2 with the given printer config."""
+ tpl = _env.get_template("install.ps1.j2")
+ return tpl.render(
+ printer_name=printer.name,
+ ip_address=printer.ip_address,
+ port_name=printer.port_name,
+ driver_name=driver_name,
+ inf_filename=inf_filename,
+ duplex_mode=_duplex_map[printer.duplex_mode], # translate enum
+ color=str(printer.color_mode).lower(), # "$true" / "$false"
+ paper_size=printer.paper_size,
+ collate=str(printer.collate).lower(),
+ )
+
+_duplex_map = {
+ "OneSided": "OneSided",
+ "LongEdge": "TwoSidedLongEdge",
+ "ShortEdge": "TwoSidedShortEdge",
+}
+```
+
+### Pattern 2: FastAPI Script Download Endpoint
+
+```python
+# imptune/api/scripts.py
+from fastapi import APIRouter
+from fastapi.responses import PlainTextResponse
+from imptune.db.models import Printer, Driver
+from imptune.generators.script_generator import render_install, render_uninstall, render_detect
+import json
+
+router = APIRouter(prefix="/printers")
+
+@router.get("/{printer_id}/scripts/install", response_class=PlainTextResponse)
+def download_install_script(printer_id: int) -> PlainTextResponse:
+ printer = Printer.get_or_none(Printer.id == printer_id)
+ if printer is None:
+ return PlainTextResponse("Not found", status_code=404)
+ driver = printer.driver
+ driver_names = json.loads(driver.driver_desc) if driver and driver.driver_desc else []
+ driver_name = driver_names[0] if driver_names else ""
+ inf_filename = driver.inf_filename if driver else ""
+ content = render_install(printer, inf_filename, driver_name)
+ return PlainTextResponse(
+ content=content,
+ headers={"Content-Disposition": 'attachment; filename="install.ps1"'},
+ )
+```
+
+Register in `main.py` alongside existing routers:
+```python
+from imptune.api import scripts
+app.include_router(scripts.router)
+```
+
+### Pattern 3: Install Script Structure (Jinja2 Template Logic)
+
+The `.ps1.j2` template must implement the following blocks in order:
+
+```
+1. WOW64 guard (detect 32-bit, relaunch self in 64-bit, exit 32-bit process)
+2. SYSTEM vs user context check (self-elevate via UAC if running as user)
+3. pnputil step 1: /add-driver /install (uses $PSScriptRoot)
+4. Idempotency check: Add-PrinterPort only if port does not exist
+5. Add-PrinterDriver -Name
+6. Idempotency check: Add-Printer only if printer does not exist
+7. Set-PrintConfiguration for duplex, color, paper size, collate
+```
+
+### Pattern 4: Idempotency Guards
+
+All install operations must be idempotent to avoid Intune re-run failures:
+
+```powershell
+# Port idempotency
+if (-not (Get-PrinterPort -Name "{{ port_name }}" -ErrorAction SilentlyContinue)) {
+ Add-PrinterPort -Name "{{ port_name }}" -PrinterHostAddress "{{ ip_address }}"
+}
+
+# Printer idempotency
+if (-not (Get-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue)) {
+ Add-Printer -Name "{{ printer_name }}" -PortName "{{ port_name }}" -DriverName "{{ driver_name }}"
+}
+```
+
+### Anti-Patterns to Avoid
+
+- **Hardcoding System32 paths:** `C:\Windows\System32\pnputil.exe` fails from 32-bit context. Use `"$env:WINDIR\SysNative\pnputil.exe"` OR the WOW64 guard ensures the script already runs 64-bit by the time pnputil is called (then `pnputil.exe` resolves correctly from PATH).
+- **UAC elevation when already SYSTEM:** SYSTEM account does not need UAC and `Start-Process -Verb Runas` fails silently. The install script must detect the current identity and skip elevation when running as SYSTEM.
+- **Skipping idempotency checks:** Running `Add-Printer` twice throws a non-terminating error that Intune logs as a warning. Wrap all add operations with existence checks.
+- **Using bare jinja2.Environment without trim_blocks:** Produces extra blank lines from `{% if %}` blocks that make scripts harder to read and diff.
+- **Returning scripts as `application/octet-stream`:** Use `text/plain` so browsers open them without a save dialog, or use `Content-Disposition: attachment` explicitly.
+
+---
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Script whitespace | Manual string concatenation | Jinja2 with trim_blocks | Edge cases: trailing newlines, empty blocks, nested conditionals |
+| Template file loading | Inline heredocs in Python | Jinja2 FileSystemLoader | Testable in isolation; editor syntax highlighting; version-controlled separately |
+| Printer existence check | Registry query in Python | `Get-Printer` / `Get-PrinterPort` in the script itself | The check must run on the endpoint, not on the server |
+| Detection logic | Custom WMI query | Get-Printer + registry path pattern | Intune's detection contract is well-defined: exit 0 + STDOUT for present |
+
+**Key insight:** Script logic (WOW64 guard, UAC self-elevation, idempotency) lives in the `.ps1.j2`
+template file, not in Python. Python only provides data variables. This keeps scripts readable and
+testable as real PowerShell without a Python interpreter on the endpoint.
+
+---
+
+## Common Pitfalls
+
+### Pitfall 1: Duplex Mode Name Mismatch
+
+**What goes wrong:** `Set-PrintConfiguration -DuplexingMode LongEdge` throws an error — the
+accepted values are `OneSided | TwoSidedLongEdge | TwoSidedShortEdge`.
+
+**Why it happens:** The Printer model was designed with short names (`LongEdge`, `ShortEdge`) for
+UI simplicity. The PowerShell cmdlet uses the full names.
+
+**How to avoid:** Use the `_duplex_map` dict in `script_generator.py` to translate before rendering.
+
+**Warning signs:** `Set-PrintConfiguration` throws `"Cannot bind parameter 'DuplexingMode'"`.
+
+---
+
+### Pitfall 2: WOW64 — pnputil Not Found
+
+**What goes wrong:** `pnputil.exe /add-driver` fails with "The system cannot find the file specified"
+when Intune's 32-bit PowerShell runs the script.
+
+**Why it happens:** Under WOW64, `C:\Windows\System32` is redirected to `SysWOW64`. `pnputil.exe`
+does not exist in `SysWOW64`.
+
+**How to avoid:** Place the WOW64 relaunch guard as the VERY FIRST executable block in the install
+script (before any function definitions or logic):
+
+```powershell
+# WOW64 Guard — must be first
+if ($env:PROCESSOR_ARCHITECTURE -eq "x86" -and $env:PROCESSOR_ARCHITEW6432) {
+ $64bit = "$env:WINDIR\SysNative\WindowsPowerShell\v1.0\powershell.exe"
+ & $64bit -NoProfile -ExecutionPolicy Bypass -File $PSCommandPath @args
+ exit $LASTEXITCODE
+}
+```
+
+**Warning signs:** Error in Intune management console about `pnputil.exe` not found; install fails
+only on 64-bit machines through Intune but succeeds when run manually.
+
+---
+
+### Pitfall 3: UAC Elevation When Running as SYSTEM
+
+**What goes wrong:** Calling `Start-Process powershell -Verb Runas` when already running as the
+SYSTEM account causes the elevation attempt to fail or prompt unexpectedly.
+
+**Why it happens:** SYSTEM is already the highest privilege. `-Verb Runas` triggers UAC which does
+not make sense for a service account.
+
+**How to avoid:** Check the current user identity before attempting elevation:
+
+```powershell
+$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
+$isSystem = $currentUser.IsSystem
+$isAdmin = ([System.Security.Principal.WindowsPrincipal]$currentUser).IsInRole(
+ [System.Security.Principal.WindowsBuiltInRole]::Administrator)
+
+if (-not $isAdmin -and -not $isSystem) {
+ # Re-launch with elevation
+ Start-Process powershell.exe -Verb Runas `
+ -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" `
+ -Wait
+ exit $LASTEXITCODE
+}
+# If SYSTEM or already admin, continue directly
+```
+
+**Warning signs:** UAC dialog appears when Intune runs the script; script hangs waiting for user input.
+
+---
+
+### Pitfall 4: Detection Script STDOUT Requirement
+
+**What goes wrong:** Detection script exits 0 but Intune still marks app as "Not installed".
+
+**Why it happens:** Intune's detection contract requires both exit 0 AND a non-empty STDOUT string.
+Exit 0 alone is insufficient.
+
+**How to avoid:**
+```powershell
+$printer = Get-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue
+if ($printer) {
+ Write-Output "Installed"
+ exit 0
+} else {
+ exit 1
+}
+```
+
+**Warning signs:** Script returns 0 in testing but Intune keeps re-installing.
+
+---
+
+### Pitfall 5: $PSScriptRoot Empty in Intune Context
+
+**What goes wrong:** `$PSScriptRoot` is empty when PowerShell executes a script via
+`-Command` flag rather than `-File` flag.
+
+**Why it happens:** `$PSScriptRoot` is only populated when the script is launched with `-File`.
+
+**How to avoid:** Always configure Intune install command as:
+```
+powershell.exe -NoProfile -ExecutionPolicy Bypass -File "install.ps1"
+```
+Not as `-Command ".\install.ps1"`. Document the required Intune install command string in the
+generated script header comment.
+
+---
+
+### Pitfall 6: Set-PrintConfiguration Requires Printer Already Exist
+
+**What goes wrong:** `Set-PrintConfiguration` throws if called before `Add-Printer` completes.
+
+**Why it happens:** The Print Spooler service may need a moment to register the printer.
+
+**How to avoid:** Call `Set-PrintConfiguration` immediately after `Add-Printer` in the same script
+block. No sleep is required if the same PowerShell session registers the printer synchronously.
+
+---
+
+## Code Examples
+
+Verified patterns from official sources:
+
+### WOW64 Relaunch Guard (SCRPT-05)
+
+```powershell
+# Source: community-verified pattern, consistent with call4cloud.nl + patchmypc.com research
+# Must appear BEFORE any other logic in the script
+if ($env:PROCESSOR_ARCHITECTURE -eq "x86" -and $env:PROCESSOR_ARCHITEW6432) {
+ $ps64 = "$env:WINDIR\SysNative\WindowsPowerShell\v1.0\powershell.exe"
+ & $ps64 -NoProfile -ExecutionPolicy Bypass -File "$PSCommandPath" @args
+ exit $LASTEXITCODE
+}
+```
+
+### SYSTEM vs User Context Detection + UAC Self-Elevation (SCRPT-04)
+
+```powershell
+# Source: [System.Security.Principal.WindowsIdentity] — .NET BCL, available in all PS versions
+$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
+$isSystem = $id.IsSystem
+$isAdmin = ([System.Security.Principal.WindowsPrincipal]$id).IsInRole(
+ [System.Security.Principal.WindowsBuiltInRole]::Administrator)
+
+if (-not $isSystem -and -not $isAdmin) {
+ Start-Process powershell.exe `
+ -Verb Runas `
+ -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" `
+ -Wait
+ exit $LASTEXITCODE
+}
+```
+
+### pnputil Two-Step Driver Staging (SCRPT-01)
+
+```powershell
+# Source: msendpointmgr.com + call4cloud.nl verified
+# Step 1: Stage INF into Windows Driver Store
+pnputil.exe /add-driver "$PSScriptRoot\drivers\{{ inf_filename }}" /install
+
+# Step 2: Install named driver from Driver Store
+Add-PrinterDriver -Name "{{ driver_name }}"
+```
+
+> Note: By the time this runs, the WOW64 guard has already relaunched in 64-bit PowerShell,
+> so `pnputil.exe` resolves to `System32\pnputil.exe` without needing an explicit path.
+
+### Port + Printer Creation with Idempotency
+
+```powershell
+# Source: call4cloud.nl pattern, verified against Microsoft PrintManagement module docs
+if (-not (Get-PrinterPort -Name "{{ port_name }}" -ErrorAction SilentlyContinue)) {
+ Add-PrinterPort -Name "{{ port_name }}" -PrinterHostAddress "{{ ip_address }}"
+}
+
+if (-not (Get-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue)) {
+ Add-Printer -Name "{{ printer_name }}" `
+ -PortName "{{ port_name }}" `
+ -DriverName "{{ driver_name }}"
+}
+```
+
+### Set-PrintConfiguration (SCRPT-01)
+
+```powershell
+# Source: Microsoft Learn — Set-PrintConfiguration (windowsserver2025-ps)
+# DuplexingMode accepted values: OneSided, TwoSidedLongEdge, TwoSidedShortEdge
+# PaperSize accepted values include: A4, Letter, Legal (and many others)
+# Color: Boolean ($true / $false)
+# Collate: Boolean ($true / $false)
+Set-PrintConfiguration -PrinterName "{{ printer_name }}" `
+ -DuplexingMode {{ duplex_mode }} `
+ -Color ${{ color }} `
+ -PaperSize {{ paper_size }} `
+ -Collate ${{ collate }}
+```
+
+### Uninstall Script (SCRPT-02)
+
+```powershell
+# Source: call4cloud.nl verified; -ErrorAction SilentlyContinue for idempotency
+Remove-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue
+Remove-PrinterDriver -Name "{{ driver_name }}" -ErrorAction SilentlyContinue
+Remove-PrinterPort -Name "{{ port_name }}" -ErrorAction SilentlyContinue
+```
+
+> Note on driver removal order: Remove-Printer BEFORE Remove-PrinterDriver. Removing the driver
+> while a printer still references it produces an error.
+
+### Detection Script (SCRPT-03)
+
+```powershell
+# Source: Intune detection script contract (powershellisfun.com + andrewstaylor.com verified)
+# Intune requires: exit 0 + non-empty STDOUT = installed; any other exit = not installed
+$printer = Get-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue
+if ($printer) {
+ Write-Output "Installed: {{ printer_name }}"
+ exit 0
+} else {
+ exit 1
+}
+```
+
+### Jinja2 Environment Setup for Script Templates
+
+```python
+# Source: Jinja2 3.1.x official docs — trim_blocks + lstrip_blocks for non-HTML rendering
+from jinja2 import Environment, FileSystemLoader
+from pathlib import Path
+
+_env = Environment(
+ loader=FileSystemLoader(str(Path(__file__).parent.parent / "templates" / "scripts")),
+ trim_blocks=True, # removes newline after block tags ({% %})
+ lstrip_blocks=True, # strips leading spaces/tabs before block tags
+ keep_trailing_newline=True, # preserves final newline (important for scripts)
+)
+```
+
+---
+
+## State of the Art
+
+| Old Approach | Current Approach | Notes |
+|--------------|------------------|-------|
+| IntuneWinAppUtil.exe for packaging | Python-native (Phase 1 decision) | Locked decision |
+| Hardcoded scripts per printer | Jinja2 template rendering | Enables regeneration (PRNT-10) |
+| printui.exe for settings export | Set-PrintConfiguration cmdlet | Cmdlet is the current standard |
+| HKLM\...\Print\Printers registry check | Get-Printer cmdlet check | Both work; Get-Printer is more reliable |
+
+**Deprecated/outdated:**
+- `wmic printer` queries: deprecated in Windows 11, use `Get-Printer`
+- `Set-WmiInstance Win32_PrinterConfiguration`: superseded by `Set-PrintConfiguration`
+
+---
+
+## Open Questions
+
+1. **Multiple driver names per INF**
+ - What we know: The model stores `driver_desc` as a JSON list of names (e.g., `["HP Universal", "HP Universal PCL6"]`)
+ - What's unclear: Which name should be used in the script when multiple are present?
+ - Recommendation: Use the **first** name from the list (index 0). The driver selection UI (Phase 2, DRV-03) enforced a single selection — that selected name should be stored separately, or the template receives `driver_name` as the first list element. The planner should decide whether to store the selected driver name on the Printer record or derive it at render time.
+
+2. **driver_name field on Printer model**
+ - What we know: The `Printer` model has a `driver` FK to `Driver`, but no `driver_name` field storing the specific selected name.
+ - What's unclear: Phase 2 let users pick a driver name from a dropdown, but this selection is not persisted on the Printer record.
+ - Recommendation: Either (a) add a `selected_driver_name` CharField to the Printer model in this phase, or (b) derive it as `json.loads(printer.driver.driver_desc)[0]` at render time. Option (b) avoids a schema change and is simpler for v1.
+
+3. **inf_filename on Driver record**
+ - What we know: `Driver.inf_filename` is nullable. If null, pnputil staging cannot proceed.
+ - Recommendation: The generate endpoint should return a 400/422 with a clear message if `inf_filename` is null or driver is unassigned.
+
+---
+
+## Validation Architecture
+
+### Test Framework
+
+| Property | Value |
+|----------|-------|
+| Framework | pytest >= 8.0 |
+| Config file | none — discovered automatically |
+| Quick run command | `python -m pytest tests/test_script_generator.py -x -q` |
+| Full suite command | `python -m pytest tests/ -x -q` |
+
+### Phase Requirements → Test Map
+
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| SCRPT-01 | render_install() produces script containing pnputil, Add-PrinterPort, Add-PrinterDriver, Add-Printer, Set-PrintConfiguration | unit | `python -m pytest tests/test_script_generator.py::test_render_install_contains_pnputil -x` | ❌ Wave 0 |
+| SCRPT-01 | Set-PrintConfiguration receives correct duplex/color/paper/collate values | unit | `python -m pytest tests/test_script_generator.py::test_render_install_print_config -x` | ❌ Wave 0 |
+| SCRPT-02 | render_uninstall() produces script with Remove-Printer, Remove-PrinterDriver, Remove-PrinterPort | unit | `python -m pytest tests/test_script_generator.py::test_render_uninstall -x` | ❌ Wave 0 |
+| SCRPT-03 | render_detect() exits 0 with Write-Output when printer present; exits 1 when absent | unit | `python -m pytest tests/test_script_generator.py::test_render_detect -x` | ❌ Wave 0 |
+| SCRPT-04 | Install script contains IsSystem + IsInRole check + Start-Process Runas | unit | `python -m pytest tests/test_script_generator.py::test_render_install_uac_guard -x` | ❌ Wave 0 |
+| SCRPT-05 | Install script contains PROCESSOR_ARCHITECTURE check + SysNative relaunch | unit | `python -m pytest tests/test_script_generator.py::test_render_install_wow64_guard -x` | ❌ Wave 0 |
+| SCRPT-01 | GET /printers/{id}/scripts/install returns 200 PlainTextResponse with .ps1 content | integration | `python -m pytest tests/test_script_generator.py::test_install_endpoint -x` | ❌ Wave 0 |
+| SCRPT-02 | GET /printers/{id}/scripts/uninstall returns 200 | integration | `python -m pytest tests/test_script_generator.py::test_uninstall_endpoint -x` | ❌ Wave 0 |
+| SCRPT-03 | GET /printers/{id}/scripts/detect returns 200 | integration | `python -m pytest tests/test_script_generator.py::test_detect_endpoint -x` | ❌ Wave 0 |
+
+### Sampling Rate
+
+- **Per task commit:** `python -m pytest tests/test_script_generator.py -x -q`
+- **Per wave merge:** `python -m pytest tests/ -x -q`
+- **Phase gate:** Full suite green before `/gsd:verify-work`
+
+### Wave 0 Gaps
+
+- [ ] `tests/test_script_generator.py` — all SCRPT-01 through SCRPT-05 unit + integration tests
+- [ ] `imptune/templates/scripts/install.ps1.j2` — template file
+- [ ] `imptune/templates/scripts/uninstall.ps1.j2` — template file
+- [ ] `imptune/templates/scripts/detect.ps1.j2` — template file
+- [ ] `imptune/generators/script_generator.py` — render functions
+- [ ] `imptune/api/scripts.py` — FastAPI router
+
+---
+
+## Sources
+
+### Primary (HIGH confidence)
+
+- Microsoft Learn — `Set-PrintConfiguration` (windowsserver2025-ps, updated 2025-05-14):
+ https://learn.microsoft.com/en-us/powershell/module/printmanagement/set-printconfiguration?view=windowsserver2025-ps
+ — confirmed parameter names and accepted enum values for DuplexingMode, PaperSize, Color, Collate
+- Jinja2 3.1.x official docs — Environment trim_blocks/lstrip_blocks:
+ https://jinja.palletsprojects.com/en/stable/templates/
+- FastAPI docs — PlainTextResponse / Custom Response:
+ https://fastapi.tiangolo.com/advanced/custom-response/
+- Project codebase — `imptune/db/models.py`, `imptune/generators/intunewin_builder.py`,
+ `imptune/api/printers.py`, `requirements.txt` — all read directly
+
+### Secondary (MEDIUM confidence)
+
+- msendpointmgr.com — pnputil two-step staging + Add-PrinterPort/Add-PrinterDriver/Add-Printer
+ sequence: https://msendpointmgr.com/2022/01/03/install-network-printers-intune-win32apps-powershell/
+- call4cloud.nl — pnputil SysNative path, idempotency patterns, detection registry path:
+ https://call4cloud.nl/deploy-printer-drivers-intune-win32app/
+- powershellisfun.com — Intune detection script contract (exit 0 + STDOUT):
+ https://powershellisfun.com/2023/11/30/microsoft-intune-powershell-detection-scripts/
+- andrewstaylor.com — detection script demystified:
+ https://andrewstaylor.com/2022/04/19/demystifying-intune-custom-app-detection-scripts/
+
+### Tertiary (LOW confidence)
+
+- WOW64 relaunch guard gist (community pattern, not official MS docs):
+ https://gist.github.com/talatham/ad406d5428ccec641f075a7019cd29a8
+ — Cross-verified with patchmypc.com and call4cloud.nl articles describing the same pattern.
+
+---
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — zero new dependencies; all libraries already in requirements.txt
+- Architecture patterns: HIGH — follows existing project conventions (generators/ + api/ + templates/)
+- PowerShell cmdlet parameters: HIGH — verified against Microsoft Learn official docs
+- WOW64 guard pattern: MEDIUM — community-verified, consistent across multiple sources, not in official MS docs
+- UAC self-elevation pattern: MEDIUM — community-verified, stable pattern since PS 3.0
+- Pitfalls: HIGH — duplex mismatch verified against official docs; others verified against multiple community sources
+
+**Research date:** 2026-04-10
+**Valid until:** 2026-07-10 (stable domain; PowerShell PrintManagement module rarely changes)