feat(02-03): implement buildIntuneInstall and buildIntuneDetection
- buildIntuneInstall: UTF-8 no-BOM write via WriteAllText, PS here-string for config, conditional rclone download block - buildIntuneDetection: no $ErrorActionPreference, Write-Output + exit 0/1 pattern - Both generators use CONFIG_DIR from ps-helpers for consistent path resolution - All 15 intune-install and intune-detection tests pass GREEN
This commit is contained in:
@@ -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';
|
||||||
|
}
|
||||||
@@ -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<string, string> = {
|
||||||
|
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';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user