diff --git a/imptune/generators/script_generator.py b/imptune/generators/script_generator.py new file mode 100644 index 0000000..5c03a7f --- /dev/null +++ b/imptune/generators/script_generator.py @@ -0,0 +1,65 @@ +"""Script generator module — renders PowerShell scripts from Jinja2 templates. + +Provides render_install() which produces a complete PowerShell install script +containing WOW64 guard, UAC self-elevation, pnputil two-step driver staging, +idempotent port/printer creation, and Set-PrintConfiguration with duplex mapping. +""" +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, +) + +_duplex_map = { + "OneSided": "OneSided", + "LongEdge": "TwoSidedLongEdge", + "ShortEdge": "TwoSidedShortEdge", +} + + +def render_install( + printer_name: str, + ip_address: str, + port_name: str, + driver_name: str, + inf_filename: str, + duplex_mode: str, + color_mode: bool, + paper_size: str, + collate: bool, +) -> str: + """Render install.ps1.j2 with the given printer configuration. + + Args: + printer_name: Display name of the printer. + ip_address: IP address for the printer TCP/IP port. + port_name: Port name (e.g. "IP_192.168.1.10"). + driver_name: Exact driver name as registered in Windows. + inf_filename: INF filename inside the drivers/ subfolder. + duplex_mode: One of "OneSided", "LongEdge", "ShortEdge" (model values). + color_mode: True for color printing, False for mono. + paper_size: Paper size string (e.g. "A4", "Letter"). + collate: True to enable collation. + + Returns: + Rendered PowerShell script as a string. + """ + tpl = _env.get_template("install.ps1.j2") + return tpl.render( + printer_name=printer_name, + ip_address=ip_address, + port_name=port_name, + driver_name=driver_name, + inf_filename=inf_filename, + duplex_mode=_duplex_map.get(duplex_mode, duplex_mode), + color=str(color_mode).lower(), + paper_size=paper_size, + collate=str(collate).lower(), + ) diff --git a/imptune/templates/scripts/install.ps1.j2 b/imptune/templates/scripts/install.ps1.j2 new file mode 100644 index 0000000..8cf7610 --- /dev/null +++ b/imptune/templates/scripts/install.ps1.j2 @@ -0,0 +1,67 @@ +# Generated by ImpTune — Printer: {{ printer_name }} +# Install command: powershell.exe -NoProfile -ExecutionPolicy Bypass -File "install.ps1" +# +# This script installs the printer "{{ printer_name }}" via Intune Win32 app deployment. +# It must be launched with -File (not -Command) so $PSScriptRoot is populated. + +# --------------------------------------------------------------------------- +# WOW64 Guard — relaunch in 64-bit PowerShell if running under WOW64 (Intune +# runs Win32 app scripts in a 32-bit process; pnputil is 64-bit only). +# This block MUST be the first executable code 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 +# Skip elevation when already running as SYSTEM (Intune MDM context). +# --------------------------------------------------------------------------- +$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 +# Step 1: Stage INF + all associated files into Windows Driver Store +# Step 2: Install the named driver from the Driver Store +# --------------------------------------------------------------------------- +pnputil.exe /add-driver "$PSScriptRoot\drivers\{{ inf_filename }}" /install + +Add-PrinterDriver -Name "{{ driver_name }}" + +# --------------------------------------------------------------------------- +# Idempotent port creation +# --------------------------------------------------------------------------- +if (-not (Get-PrinterPort -Name "{{ port_name }}" -ErrorAction SilentlyContinue)) { + Add-PrinterPort -Name "{{ port_name }}" -PrinterHostAddress "{{ ip_address }}" +} + +# --------------------------------------------------------------------------- +# Idempotent printer creation +# --------------------------------------------------------------------------- +if (-not (Get-Printer -Name "{{ printer_name }}" -ErrorAction SilentlyContinue)) { + Add-Printer -Name "{{ printer_name }}" ` + -PortName "{{ port_name }}" ` + -DriverName "{{ driver_name }}" +} + +# --------------------------------------------------------------------------- +# Configure print settings +# --------------------------------------------------------------------------- +Set-PrintConfiguration -PrinterName "{{ printer_name }}" ` + -DuplexingMode {{ duplex_mode }} ` + -Color ${{ color }} ` + -PaperSize {{ paper_size }} ` + -Collate ${{ collate }}