Covers rclone.conf INI generation (type mapping, empty field omission), PowerShell encoding (UTF-8 no-BOM via UTF8Encoding($false)), Intune detection script contract (STDERR breaks detection), and RMM SYSTEM context patterns. All key findings verified against live official sources. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
32 KiB
Phase 2: Generators - Research
Researched: 2026-03-26 Domain: Pure TypeScript generator functions (rclone INI conf), PowerShell script templating (Intune install, Intune detection, RMM), PowerShell encoding (UTF-8 no-BOM in SYSTEM context) Confidence: HIGH
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| CONF-01 | App generates a valid rclone.conf file (INI format, correct key/value pairs per backend type) | rclone INI format verified from official docs; backend type values and field keys verified from rclone.org |
| DEPL-01 | App generates a PowerShell install script for MS Intune (runs as SYSTEM, places config at machine-wide path, idempotent, correct exit codes) | SYSTEM context encoding requirements verified from Microsoft Learn; idempotency patterns documented |
| DEPL-02 | App generates a separate Intune detection script (checks rclone.exe presence at install path AND config file presence; exits 0 if both found) | Intune detection script contract (exit 0 + STDOUT) verified from Microsoft Learn; STDERR-breaks-detection pitfall documented |
| DEPL-03 | App generates a PowerShell script for RMM tools (generic SYSTEM-context, idempotent, works across NinjaRMM/Datto/ConnectWise) | Self-contained idempotent PowerShell pattern documented; no RMM-specific SDK required |
| DEPL-04 | User can toggle "Include rclone installation" to add rclone binary download step to generated scripts | rclone official download URL pattern verified; conditional script section design documented |
| DEPL-05 | User can choose config deployment path: machine-wide (C:\ProgramData\rclone\) vs user profile (%APPDATA%\rclone\) |
Both Windows path conventions documented; SYSTEM context caveat for %APPDATA% documented |
| </phase_requirements> |
Summary
Phase 2 implements pure generator functions: given a completed WizardState, produce correct string content for rclone.conf and all PowerShell scripts. These are the most correctness-critical deliverables in the entire project — a wrong rclone config key silently breaks authentication, a wrong PowerShell encoding breaks Intune script upload, and a detection script that writes to STDERR fails to detect even when the app is installed.
All generators are pure TypeScript functions with no React dependencies. They take WizardState as input and return a string. This makes them trivially unit-testable and completely decoupled from UI. The rclone.conf generator reads remote.backendType, remote.name, and remote.params from state; the PowerShell generators additionally read deployment.includeInstall, deployment.configPath, and deployment.scriptTargets. The configPath option maps to literal Windows paths: machine-wide → C:\ProgramData\rclone\, user-profile → %APPDATA%\rclone\.
The single most complex research finding is PowerShell encoding: Intune recommends UTF-8 with BOM for detection scripts (so Windows PowerShell 5.1 correctly handles non-ASCII characters), but many community sources say no-BOM. For scripts that contain only ASCII characters (which all generated scripts do — all credential values are treated as opaque strings), this distinction is moot. The generator outputs plain JavaScript strings; the encoding is only relevant if Phase 4 downloads the script as a file. Phase 2 does not handle file saving — it only produces string content. The encoding concern is therefore a Phase 4 (download manager) concern, not a Phase 2 concern.
Primary recommendation: Implement all generators as pure TypeScript functions in src/generators/. Each function takes WizardState and returns string. Test with Vitest unit tests that assert specific substrings are present or absent in the output. No templating library is needed — template literals are sufficient and produce readable, auditable script content.
Standard Stack
Core
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| TypeScript template literals | Built-in | String generation for all outputs | Pure functions, no dep, fully auditable — you can read the exact PS code that will be generated |
| WizardState (Phase 1) | Already built | Input to all generators | Phase 1 types.ts already defines the full state shape; generators consume it directly |
| Vitest | 2.x (already installed) | Unit testing generator output | Already configured in vitest.config.ts with node environment — ideal for pure string functions |
No Additional Libraries Needed
Phase 2 adds zero new npm dependencies. All generation is string manipulation over the existing WizardState type. The BACKEND_REGISTRY from Phase 1 (src/schemas/registry.ts) provides the field-to-key mapping that the rclone.conf generator iterates over.
Alternatives Considered
| Instead of | Could Use | Tradeoff |
|---|---|---|
| Template literals | Handlebars / Mustache | Template engines add a dependency and obscure the output. With template literals, the generated script is plainly readable in source code — critical for a security-sensitive tool. |
| Template literals | Tagged template literals | Unnecessary complexity for this use case; plain template literals are cleaner |
| Inline type assertions | Zod parse of WizardState before generation | Overkill — WizardState already typed; generators can assume valid input (wizard validates before reaching review step) |
Architecture Patterns
Recommended Project Structure
src/
├── generators/
│ ├── rclone-conf.ts # buildRcloneConf(state) → string
│ ├── intune-install.ts # buildIntuneInstall(state) → string
│ ├── intune-detection.ts # buildIntuneDetection(state) → string
│ ├── rmm-script.ts # buildRmmScript(state) → string
│ ├── index.ts # Re-exports all four generators
│ ├── rclone-conf.test.ts # Unit tests for CONF-01
│ ├── intune-install.test.ts
│ ├── intune-detection.test.ts
│ └── rmm-script.test.ts
├── schemas/ # Phase 1 — unchanged
└── store/ # Phase 1 — unchanged
Pattern 1: rclone.conf INI Generator
What: Produces a valid INI-format rclone.conf string. The INI format is: [remoteName] section header, then key = value pairs, one per line, blank line between sections (only one remote in v1). The mandatory type field maps BackendType to rclone's internal backend name.
BackendType → rclone type mapping (HIGH confidence — verified from rclone.org):
'azureblob'→type = azureblob's3'→type = s3's3-compatible'→type = s3(NOTs3-compatible; rclone usestype = s3withprovider = Otherfor all S3-compatible storage)
When to use: Always first — the rclone.conf content is the foundation; scripts reference the config path.
Example:
// src/generators/rclone-conf.ts
import type { WizardState } from '../store/types';
const BACKEND_TYPE_MAP: Record<string, string> = {
azureblob: 'azureblob',
s3: 's3',
's3-compatible': 's3',
};
export function buildRcloneConf(state: WizardState): string {
const { name, backendType, params } = state.remote;
if (!backendType || !name) {
throw new Error('buildRcloneConf: remote name and backendType are required');
}
const rcloneType = BACKEND_TYPE_MAP[backendType];
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';
}
Correct rclone.conf output for Azure Blob (account + SAS URL):
[my-azure]
type = azureblob
account = mystorageaccount
sas_url = https://mystorageaccount.blob.core.windows.net/?sv=...
Correct rclone.conf output for S3:
[my-s3]
type = s3
provider = AWS
access_key_id = AKIAIOSFODNN7EXAMPLE
secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region = us-east-1
Correct rclone.conf output for S3-compatible (Wasabi example):
[my-wasabi]
type = s3
provider = Other
access_key_id = mykey
secret_access_key = mysecret
endpoint = https://s3.wasabisys.com
Pattern 2: Intune Install Script Generator
What: A PowerShell script that runs as SYSTEM, creates the config directory, writes the rclone.conf content, and optionally downloads rclone.exe. Must be idempotent (safe to run twice). Must exit with code 0 on success, non-zero on error.
Key requirements (HIGH confidence — verified from Microsoft Learn):
- Runs as SYSTEM — no user context, no
$env:USERPROFILE, no interactive prompts - Machine-wide path:
C:\ProgramData\rclone\— accessible by SYSTEM account - User profile path:
%APPDATA%\rclone\— caution:%APPDATA%resolves toC:\Windows\system32\config\systemprofile\AppData\Roamingwhen running as SYSTEM, NOT the logged-in user's APPDATA. This is documented behavior and should be surfaced as a UI warning in Phase 3. [System.IO.File]::WriteAllText()with explicitSystem.Text.UTF8Encoding($false)for UTF-8 no-BOM file writing (required because PowerShell 5.1'sSet-Content -Encoding UTF8writes UTF-8 with BOM)exit 0/exit 1at end of script — Intune reads the process exit code
rclone download URL (HIGH confidence — verified from rclone.org/install/):
https://downloads.rclone.org/rclone-current-windows-amd64.zip
This always points to the latest stable release. It is the official URL from rclone.org.
Example:
# Generated by buildIntuneInstall()
$ErrorActionPreference = 'Stop'
$configDir = 'C:\ProgramData\rclone'
$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
$confContent = @'
[my-azure]
type = azureblob
account = mystorageaccount
key = BASE64KEY
'@
[System.IO.File]::WriteAllText($configPath, $confContent, [System.Text.UTF8Encoding]::new($false))
# Optional: download rclone.exe (only if includeInstall = true)
$rclonePath = "$configDir\rclone.exe"
if (-not (Test-Path $rclonePath)) {
$zipPath = "$env:TEMP\rclone.zip"
Invoke-WebRequest -Uri 'https://downloads.rclone.org/rclone-current-windows-amd64.zip' -OutFile $zipPath -UseBasicParsing
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($zipPath)
$entry = $zip.Entries | Where-Object { $_.Name -eq 'rclone.exe' }
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $rclonePath, $true)
$zip.Dispose()
Remove-Item $zipPath -Force
}
exit 0
Pattern 3: Intune Detection Script Generator
What: A PowerShell script that checks whether rclone.exe AND rclone.conf both exist at the expected paths. Exits 0 and writes to STDOUT when both are found. Exits non-zero when either is missing.
Intune detection contract (HIGH confidence — verified from Microsoft Learn docs updated 2026-02-06):
- Exit code 0 + STDOUT output → app is detected (installed)
- Exit code non-zero → app is not detected
- Any output to STDERR → detection result is "not installed", even if exit code is 0 and STDOUT has data
Critical: The detection script must NEVER write to STDERR. Use try/catch and handle errors silently or exit non-zero rather than letting PowerShell write an exception to STDERR.
Example:
# Generated by buildIntuneDetection()
$configDir = 'C:\ProgramData\rclone'
$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
}
Note: No $ErrorActionPreference = 'Stop' here — exceptions would go to STDERR and break detection.
Pattern 4: RMM Script Generator
What: A self-contained PowerShell script for generic RMM tools (NinjaRMM, Datto, ConnectWise). Same logic as the Intune install script but without Intune-specific packaging concerns. Must be idempotent.
RMM context considerations (MEDIUM confidence — community knowledge, no single authoritative source):
- All major RMM platforms run scripts as SYSTEM
- Scripts must be self-contained — no external file dependencies, no IntuneWinAppUtil packaging
- Path behavior under SYSTEM is the same as Intune (same
%APPDATA%caveat applies) - No specific exit code contract (unlike Intune) — use
exit 0/exit 1as standard practice - Include
$ErrorActionPreference = 'Stop'so failures are surfaced to the RMM platform
Structural difference from Intune install: The RMM script has no separate "detection" artifact — detection is handled by the RMM platform's own file/registry check capability. The generated script only installs.
Pattern 5: Conditional rclone Install Block
What: When deployment.includeInstall = true, all three script generators (Intune install, RMM) include a download + extract section. When false, that section is omitted.
Implementation: Each generator function receives the full WizardState. A helper function buildRcloneInstallBlock(installPath: string): string returns the download/extract PowerShell block or empty string, and is called conditionally.
function buildRcloneInstallBlock(installPath: string): string {
return `
# Download rclone binary
$rclonePath = "${installPath}\\rclone.exe"
if (-not (Test-Path $rclonePath)) {
$zipPath = "$env:TEMP\\rclone.zip"
Invoke-WebRequest -Uri 'https://downloads.rclone.org/rclone-current-windows-amd64.zip' -OutFile $zipPath -UseBasicParsing
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($zipPath)
$entry = $zip.Entries | Where-Object { $_.Name -eq 'rclone.exe' }
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $rclonePath, $true)
$zip.Dispose()
Remove-Item $zipPath -Force
}`;
}
Pattern 6: Config Path Resolution
What: deployment.configPath maps to absolute Windows paths in all generated scripts.
const CONFIG_PATHS: Record<WizardState['deployment']['configPath'], string> = {
'machine-wide': 'C:\\ProgramData\\rclone',
'user-profile': '%APPDATA%\\rclone',
};
The Intune detection script must use the same path the install script wrote to. Both generators must share this mapping (import from a shared constants file or directly from the config path mapping).
Anti-Patterns to Avoid
- Echoing credential values in scripts: The install script must write the config file content using a PowerShell here-string (
@'...'@), not by echoing individual field values withWrite-Hostorecho. AWrite-Host "account = $account"would leak credentials to the Intune management extension log. - Using
Set-Content -Encoding UTF8in PowerShell 5.1: This writes UTF-8 with BOM. Use[System.IO.File]::WriteAllText()with an explicitUTF8Encoding($false)instance instead. - Using
$ErrorActionPreference = 'Stop'in detection scripts: Unhandled exceptions write to STDERR, which causes Intune to report the app as "not installed" even when it is. - Hardcoding
C:\ProgramData\rclonein the detection script independently from the install script: If the install path is user-profile, the detection script must check%APPDATA%\rclone, not the machine-wide path. Both scripts must derive their path from the samedeployment.configPathvalue. - Using
Invoke-WebRequestwithout-UseBasicParsingin SYSTEM context: IE's first-run wizard may not be completed for the SYSTEM account, causingInvoke-WebRequestto hang without this flag on Windows Server and some Windows 10 builds. - s3-compatible using
type = s3-compatiblein rclone.conf: The correct rclone type value iss3withprovider = Other. Usings3-compatibleas the type string will cause rclone to fail to find the backend.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| INI file serialization | Custom INI writer | Template literal line-by-line | rclone.conf INI is trivially simple — one section, flat key=value. No library needed and any library adds unnecessary complexity. |
| PowerShell templating | Handlebars / Mustache | Template literals | Scripts are short and fully auditable. Template engines obscure the generated output. |
| UTF-8 no-BOM file writing | Custom byte manipulation | [System.IO.File]::WriteAllText(path, content, [System.Text.UTF8Encoding]::new($false)) |
This is the canonical PowerShell 5.1 idiom for no-BOM UTF-8 writing. |
| rclone binary distribution | Bundle rclone.exe | Download from downloads.rclone.org at deploy time |
Bundling adds binary to the web app; download URL is official and always points to latest stable. |
| Script zip/archive | JSZip at generation time | Return plain strings; Phase 4 handles zipping | Separating string generation from file operations keeps generators pure and easily testable. |
Key insight: All generation complexity in this domain comes from correctness constraints (right keys, right encoding, right exit codes), not from algorithmic complexity. Keep generators as simple readable template-literal functions. Every character of the generated PowerShell should be visible in the TypeScript source.
Common Pitfalls
Pitfall 1: s3-compatible type value in rclone.conf
What goes wrong: Generator writes type = s3-compatible for S3-compatible backends. rclone does not recognize this type string; the remote fails silently or throws "didn't find section in config file".
Why it happens: The BackendType in our TypeScript is 's3-compatible', which is the UI identifier. The rclone config type value is different: all S3 and S3-compatible remotes use type = s3, differentiated by the provider field (AWS for AWS S3, Other for S3-compatible).
How to avoid: The generator must have an explicit mapping: { 's3-compatible': 's3' }. The registry already sets provider = Other for S3-compatible backends — the generator just needs to map the type value correctly.
Warning signs: Test that asserts output contains type = s3 for an s3-compatible state, and provider = Other.
Pitfall 2: Detection script STDERR contamination
What goes wrong: The detection script includes $ErrorActionPreference = 'Stop'. An exception (e.g., access denied, path not found on an alternate drive) writes to STDERR. Intune sees STDERR output and reports the app as "not installed" even though it checked correctly and returned exit 0.
Why it happens: The Intune detection contract is: exit 0 + STDOUT → installed. Any STDERR → not installed, regardless of exit code.
How to avoid: Detection scripts must not set $ErrorActionPreference = 'Stop'. Use Test-Path which never throws — it returns $false if a path is inaccessible or doesn't exist.
Warning signs: Detection script passes local tests but Intune always shows "not installed" in the portal.
Pitfall 3: %APPDATA% resolves differently under SYSTEM
What goes wrong: User selects "user profile" config path. The install script uses %APPDATA%\rclone\ and the path resolves to C:\Windows\system32\config\systemprofile\AppData\Roaming\rclone\ under the SYSTEM account. The user expects the config to appear in their own C:\Users\<username>\AppData\Roaming\rclone\.
Why it happens: %APPDATA% is a per-user environment variable. Under SYSTEM, it resolves to the SYSTEM account's roaming profile, not the logged-in user's profile.
How to avoid: This is a documentation/UX concern more than a generator concern. The generator correctly uses %APPDATA% when configPath = 'user-profile'. Phase 3 should surface a warning when the user selects user-profile path with Intune as a target. For Phase 2, document this in the test for the user-profile path.
Warning signs: Config file not found at expected user path after Intune deployment.
Pitfall 4: Empty optional fields included in rclone.conf
What goes wrong: For Azure Blob, user fills in account and key but leaves sas_url empty. The generator includes sas_url = (empty value) in the output. rclone may interpret an empty sas_url key differently from an absent one, potentially causing auth failures.
Why it happens: Generator iterates over all params entries without filtering empty strings.
How to avoid: The generator must skip key/value pairs where value is empty string: if (value !== '') { lines.push(...) }. This is included in the Pattern 1 code example above.
Warning signs: Generated config contains lines like sas_url = or region = .
Pitfall 5: Credential values visible in PowerShell transcript logs
What goes wrong: The install script uses Write-Host "Writing config..." and then interpolates the credential value into a string that gets logged. Intune Management Extension logs can be read by any local admin.
Why it happens: Developer writes $confContent = "account = $account" and the variable contains the actual access key.
How to avoid: Write all credential content inside a PowerShell here-string literal (@'...'@ — single-quoted, no interpolation). Better yet: the generator embeds the literal credential values directly into the here-string, which is a string in JavaScript. The PowerShell script receives the conf content as a verbatim string with no variable interpolation happening at run time.
Warning signs: Any $ variable interpolation inside the config content block of the generated script.
Code Examples
buildRcloneConf — complete implementation pattern
// src/generators/rclone-conf.ts
import type { WizardState } from '../store/types';
// Maps our BackendType to rclone's internal type string
// IMPORTANT: s3-compatible uses 'type = s3' — not 's3-compatible'
const RCLONE_TYPE_MAP: Record<string, string> = {
azureblob: 'azureblob',
s3: 's3',
's3-compatible': 's3',
};
export function buildRcloneConf(state: WizardState): string {
const { name, backendType, params } = state.remote;
if (!backendType) throw new Error('backendType is required');
if (!name) throw new Error('remote name is required');
const rcloneType = RCLONE_TYPE_MAP[backendType];
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';
}
buildIntuneDetection — complete implementation pattern
// src/generators/intune-detection.ts
import type { WizardState } from '../store/types';
const CONFIG_DIR: Record<WizardState['deployment']['configPath'], string> = {
'machine-wide': 'C:\\ProgramData\\rclone',
'user-profile': '%APPDATA%\\rclone',
};
export function buildIntuneDetection(state: WizardState): string {
const configDir = CONFIG_DIR[state.deployment.configPath];
// Note: No $ErrorActionPreference = 'Stop' — exceptions go to STDERR and break detection
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';
}
Vitest test pattern for generator output
// src/generators/rclone-conf.test.ts
import { describe, it, expect } from 'vitest';
import { buildRcloneConf } from './rclone-conf';
import { INITIAL_STATE } from '../store/types';
import type { WizardState } from '../store/types';
const azureState: WizardState = {
...INITIAL_STATE,
remote: {
name: 'my-azure',
backendType: 'azureblob',
params: { account: 'mystorageaccount', key: 'BASE64KEY', sas_url: '' },
},
};
describe('buildRcloneConf', () => {
it('produces [remoteName] section header', () => {
expect(buildRcloneConf(azureState)).toContain('[my-azure]');
});
it('produces type = azureblob for azureblob backend', () => {
expect(buildRcloneConf(azureState)).toContain('type = azureblob');
});
it('produces type = s3 for s3-compatible backend (not s3-compatible)', () => {
const s3CompatState: WizardState = {
...INITIAL_STATE,
remote: {
name: 'my-wasabi',
backendType: 's3-compatible',
params: { provider: 'Other', access_key_id: 'k', secret_access_key: 's', endpoint: 'https://s3.wasabisys.com', region: '' },
},
};
const conf = buildRcloneConf(s3CompatState);
expect(conf).toContain('type = s3');
expect(conf).not.toContain('type = s3-compatible');
});
it('omits empty optional fields', () => {
const conf = buildRcloneConf(azureState); // sas_url = ''
expect(conf).not.toContain('sas_url');
});
});
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
Set-Content -Encoding UTF8 for UTF-8 file writing |
[System.IO.File]::WriteAllText(path, content, [System.Text.UTF8Encoding]::new($false)) |
PS 5.1 limitation (unchanged) | Set-Content -Encoding UTF8 always adds BOM in PS 5.1; the .NET method is the reliable no-BOM path |
Invoke-WebRequest without flags |
Invoke-WebRequest -UseBasicParsing |
Consistent guidance for years | Without -UseBasicParsing, the IE engine is invoked; on SYSTEM accounts this can hang |
| Intune PowerShell detection: any output format | Exit 0 + Write-Output + NO Write-Error / no STDERR | Consistent Intune behavior | STDERR output (even with exit 0) marks app as not detected |
Deprecated/outdated:
Add-Contentfor file writing in PS 5.1: auto-detects encoding from existing file; unreliable for new files, defaults to ANSI. Do not use.Out-File -Encoding UTF8in PS 5.1: writes UTF-8 with BOM. Avoid.
Open Questions
-
%APPDATA%user-profile path in SYSTEM context- What we know: Under SYSTEM,
%APPDATA%resolves toC:\Windows\system32\config\systemprofile\AppData\Roaming\— not the logged-in user's profile. This is confirmed Windows behavior. - What's unclear: Whether the user-profile option is intended for manual deployment (user runs the script themselves) vs SYSTEM deployment. The REQUIREMENTS.md does not specify.
- Recommendation: Generate the script with
%APPDATA%\rcloneas the path whenconfigPath = 'user-profile', and add an inline comment in the generated script warning that under SYSTEM context this path resolves to the SYSTEM profile, not the current user. Leave the UX warning for Phase 3.
- What we know: Under SYSTEM,
-
Intune: UTF-8 BOM vs no-BOM recommendation contradiction
- What we know: Microsoft Learn (2026-02-06) says "We recommend encoding your script as UTF-8 BOM" for detection scripts. Other Intune documentation says no-BOM. For Phase 2, generators produce plain JavaScript strings — encoding is irrelevant at this layer.
- What's unclear: Whether BOM matters for scripts containing only ASCII characters. (It does not — BOM only matters when non-ASCII characters are present.)
- Recommendation: All generated scripts use only ASCII characters (credential values are opaque strings, path names are ASCII). The BOM question is a Phase 4 (file download) concern. Mark for Phase 4 research.
-
rclone.exe download: 32-bit vs 64-bit architecture
- What we know: The URL
https://downloads.rclone.org/rclone-current-windows-amd64.ziptargets x64. Most Windows endpoints managed by Intune/RMM are x64. - What's unclear: Whether to auto-detect architecture or hardcode x64.
- Recommendation: Hardcode
amd64in v1. The vast majority of managed Windows endpoints are x64. Add architecture selection as a v2 enhancement if needed.
- What we know: The URL
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | Vitest 4.1.1 (already installed) |
| Config file | vitest.config.ts — exists, environment: 'node', globals: true |
| Quick run command | npm test (runs vitest run) |
| Full suite command | npm test |
Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| CONF-01 | buildRcloneConf() produces valid INI with correct type and key/value pairs for all three backends |
unit | npm test -- src/generators/rclone-conf.test.ts |
❌ Wave 0 |
| DEPL-01 | Intune install script contains config directory creation, UTF-8 no-BOM write idiom, correct path, exit 0 | unit (substring assertions) | npm test -- src/generators/intune-install.test.ts |
❌ Wave 0 |
| DEPL-02 | Detection script exits 0 with Write-Output when both files present; exits 1 otherwise; no $ErrorActionPreference Stop | unit (substring assertions) | npm test -- src/generators/intune-detection.test.ts |
❌ Wave 0 |
| DEPL-03 | RMM script is self-contained, contains directory creation and config write, no Intune-specific constructs | unit (substring assertions) | npm test -- src/generators/rmm-script.test.ts |
❌ Wave 0 |
| DEPL-04 | With includeInstall=true: all three scripts contain rclone download URL; with false: URL is absent |
unit (presence/absence assertions) | npm test -- src/generators/ |
❌ Wave 0 |
| DEPL-05 | With configPath='machine-wide': scripts contain C:\ProgramData\rclone; with user-profile: %APPDATA%\rclone |
unit | npm test -- src/generators/ |
❌ Wave 0 |
Sampling Rate
- Per task commit:
npm test - Per wave merge:
npm test - Phase gate: All generator unit tests green before marking Phase 2 complete
Wave 0 Gaps
src/generators/rclone-conf.test.ts— covers CONF-01 (all three backends, type mapping, empty field omission)src/generators/intune-install.test.ts— covers DEPL-01, DEPL-04, DEPL-05src/generators/intune-detection.test.ts— covers DEPL-02, DEPL-05src/generators/rmm-script.test.ts— covers DEPL-03, DEPL-04, DEPL-05src/generators/index.ts— re-exports (no tests needed; but file must exist for Phase 4 imports)
Sources
Primary (HIGH confidence)
- https://rclone.org/azureblob/ — Azure Blob type value (
azureblob), field keys (account,key,sas_url) - https://rclone.org/s3/ — S3 type value (
s3), provider values (AWS,Other), field keys (access_key_id,secret_access_key,region,endpoint) - https://rclone.org/install/ — Official rclone Windows download URL (
downloads.rclone.org/rclone-current-windows-amd64.zip) - https://learn.microsoft.com/en-us/intune/intune-service/apps/apps-win32-add (updated 2026-02-06) — Win32 app detection script contract: exit 0 + STDOUT = installed; STDERR = not installed; UTF-8 BOM recommendation
- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-7.4 — PS 5.1 encoding behavior:
Set-Content -Encoding UTF8always adds BOM;UTF8Encoding($false)is the no-BOM path
Secondary (MEDIUM confidence)
- Phase 1 RESEARCH.md — rclone.conf INI format, field key verification status (MEDIUM from Phase 1, now upgraded to HIGH after live rclone.org verification in this research)
- Phase 1
src/schemas/registry.ts— confirmed field keys:account,key,sas_url(azureblob);provider,access_key_id,secret_access_key,region(s3); addsendpoint(s3-compatible) - https://debay.blog/2019/10/03/powershell-utf8-and-bom/ — PowerShell UTF-8 BOM behavior in 5.1
- https://www.spguides.com/powershell-write-to-file-utf8/ —
[System.IO.File]::WriteAllTextwithUTF8Encoding($false)as canonical PS 5.1 no-BOM pattern
Tertiary (LOW confidence)
- Community knowledge on RMM platform SYSTEM context behavior — multiple sources confirm all major RMM tools run scripts as SYSTEM; no single authoritative Microsoft/NinjaRMM/Datto source verified
Metadata
Confidence breakdown:
- rclone.conf INI format and field keys: HIGH — verified from live rclone.org docs (azureblob, s3 pages)
- PowerShell encoding (UTF-8 no-BOM): HIGH — verified from Microsoft Learn official PS encoding docs
- Intune detection script contract: HIGH — verified from Microsoft Learn Win32 app docs (2026-02-06)
- RMM SYSTEM context behavior: MEDIUM — community consensus, no single authoritative source
- rclone download URL: HIGH — verified from rclone.org/install/ official docs
Research date: 2026-03-26 Valid until: 2026-06-26 (rclone backend field names are stable; Intune detection contract is stable; PowerShell 5.1 encoding behavior is stable)