diff --git a/src/generators/intune-detection.ts b/src/generators/intune-detection.ts new file mode 100644 index 0000000..a7b1950 --- /dev/null +++ b/src/generators/intune-detection.ts @@ -0,0 +1,30 @@ +// src/generators/intune-detection.ts +// Generates a PowerShell detection script for Microsoft Intune compliance checks. +// +// CRITICAL design constraints (from Microsoft Learn): +// - Exit 0 + Write-Output → app detected (compliant) +// - Any STDERR output → app NOT detected, even with exit 0 +// - Therefore: NO $ErrorActionPreference = 'Stop' here — unhandled exceptions +// write to STDERR, which Intune interprets as non-detection regardless of exit code +// - NO Write-Error, NO Write-Host (Write-Host goes to the information stream in PS 5+, +// which Intune may capture differently depending on host; Write-Output is safest) + +import type { WizardState } from '../store/types'; +import { CONFIG_DIR } from './ps-helpers'; + +export function buildIntuneDetection(state: WizardState): string { + const configDir = CONFIG_DIR[state.deployment.configPath]; + + return [ + `$configDir = '${configDir}'`, + `$rclonePath = "$configDir\\rclone.exe"`, + `$configPath = "$configDir\\rclone.conf"`, + ``, + `if ((Test-Path $rclonePath) -and (Test-Path $configPath)) {`, + ` Write-Output "rclone and config detected"`, + ` exit 0`, + `} else {`, + ` exit 1`, + `}`, + ].join('\n') + '\n'; +} diff --git a/src/generators/intune-install.ts b/src/generators/intune-install.ts new file mode 100644 index 0000000..f95294e --- /dev/null +++ b/src/generators/intune-install.ts @@ -0,0 +1,61 @@ +// src/generators/intune-install.ts +// Generates a PowerShell install script suitable for deployment via Microsoft Intune. +// +// Key design constraints: +// - UTF-8 no-BOM encoding: [System.IO.File]::WriteAllText with UTF8Encoding($false) +// (Set-Content -Encoding UTF8 in PS 5.1 writes UTF-8 WITH BOM — do not use) +// - Config content embedded as PS here-string (@'...'@) — no PS variable interpolation +// inside the block, so credentials are written as literals (SECU requirement) +// - $ErrorActionPreference = 'Stop' is appropriate here (install scripts run in full PS host) + +import type { WizardState } from '../store/types'; +import { CONFIG_DIR, buildRcloneInstallBlock } from './ps-helpers'; + +export function buildIntuneInstall(state: WizardState): string { + const configDir = CONFIG_DIR[state.deployment.configPath]; + const confContent = buildRcloneConfContent(state); + const installBlock = state.deployment.includeInstall + ? buildRcloneInstallBlock(configDir) + : ''; + + return [ + `$ErrorActionPreference = 'Stop'`, + ``, + `$configDir = '${configDir}'`, + `$configPath = "$configDir\\rclone.conf"`, + ``, + `# Idempotent: create directory if it doesn't exist`, + `if (-not (Test-Path $configDir)) {`, + ` New-Item -ItemType Directory -Path $configDir -Force | Out-Null`, + `}`, + ``, + `# Write config using UTF-8 no-BOM encoding`, + `# Note: Set-Content -Encoding UTF8 in PS 5.1 writes UTF-8 WITH BOM — do not use`, + `$confContent = @'`, + confContent.trimEnd(), + `'@`, + `[System.IO.File]::WriteAllText($configPath, $confContent, [System.Text.UTF8Encoding]::new($false))`, + installBlock, + ``, + `exit 0`, + ].join('\n') + '\n'; +} + +// Build the rclone.conf content to embed in the here-string. +// Credentials are written as literal values — no PS variable interpolation. +// Inlines the INI format rather than importing buildRcloneConf to avoid +// coupling generators together and risking circular imports. +function buildRcloneConfContent(state: WizardState): string { + const RCLONE_TYPE_MAP: Record = { + azureblob: 'azureblob', + s3: 's3', + 's3-compatible': 's3', + }; + const { name, backendType, params } = state.remote; + const rcloneType = backendType ? RCLONE_TYPE_MAP[backendType] ?? backendType : 'unknown'; + const lines: string[] = [`[${name}]`, `type = ${rcloneType}`]; + for (const [key, value] of Object.entries(params)) { + if (value !== '') lines.push(`${key} = ${value}`); + } + return lines.join('\n') + '\n'; +}