Skip to content

feat: add unattend script and tests for ISO build#13

Merged
Timpan4 merged 1 commit intomainfrom
feat-addatt-script
Mar 28, 2026
Merged

feat: add unattend script and tests for ISO build#13
Timpan4 merged 1 commit intomainfrom
feat-addatt-script

Conversation

@Timpan4
Copy link
Copy Markdown
Owner

@Timpan4 Timpan4 commented Mar 28, 2026

Summary by CodeRabbit

  • New Features

    • ISO build now validates unattended install files before image creation; post-install tweaks step added to bootstrap for deferred customization.
  • Bug Fixes

    • Removed empty product-key placeholder and stripped embedded install-time customization to keep installs minimal.
  • Tests

    • Added tests for unattended validation, ISO build integration, and bootstrap post-install tweaks.
  • Documentation

    • Updated install-time docs to reflect reduced customization and revised logging guidance.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9dd31820-9942-41f3-a673-59a84c032d6b

📥 Commits

Reviewing files that changed from the base of the PR and between 3b0da13 and 9c2795e.

📒 Files selected for processing (9)
  • AUTOUNATTEND.md
  • autounattend.xml
  • bootstrap.ps1
  • build-iso.ps1
  • tests/Autounattend.Tests.ps1
  • tests/Bootstrap.Tests.ps1
  • tests/BuildIso.Tests.ps1
  • tests/ValidateUnattend.Tests.ps1
  • validate-unattend.ps1

📝 Walkthrough

Walkthrough

Autounattend.xml was simplified (removed ProductKey and specialize/Extensions). A new validate-unattend.ps1 was added and is invoked from build-iso.ps1 to schema- and policy-validate the unattend file (optionally inspecting Source ISO). Tests and docs updated to reflect these changes.

Changes

Cohort / File(s) Summary
Unattend Configuration
autounattend.xml
Removed Microsoft-Windows-Setup windowsPE <ProductKey> block, removed the specialize pass and the Extensions embedded scripts; retained existing FirstLogon SynchronousCommand entries and adjusted comment punctuation.
Validation Script
validate-unattend.ps1
New admin-run script: loads unattend XML, locates/extracts Windows SIM schema DLL, validates XML against schema, enforces policy checks (reject empty ProductKey, disallow DiskConfiguration/InstallTo, require FirstLogonCommands with C:\Setup\bootstrap.ps1), and optionally mounts Source ISO to inspect install.wim/install.esd and image metadata.
Build Integration
build-iso.ps1
Adds $validateUnattendScript and calls validate-unattend.ps1 -UnattendPath ... -SourceISO ... after file/hash checks and before ISO/image operations; failures stop the build.
Bootstrap / Post-install
bootstrap.ps1
Inserted postInstallTweaks step, added helper functions for registry/safe operations, provisioned app removal, optional feature disabling, and orchestration (Invoke-PostInstallTweaks) with failure handling and Explorer restart; step ordering updated.
Tests
tests/Autounattend.Tests.ps1, tests/BuildIso.Tests.ps1, tests/ValidateUnattend.Tests.ps1, tests/Bootstrap.Tests.ps1
Added tests: assert absence of ProductKey block; verify build-iso.ps1 invokes validator with -SourceISO and logs a success message; static checks for validate-unattend script behaviors (schema handling, ordering, policy checks, ISO inspection, bootstrap requirement); and assertions for new bootstrap post-install identifiers.
Documentation
AUTOUNATTEND.md
Removed documentation for specialize/Extensions and embedded scripts; updated to describe minimal install-time customization and document that post-install customization runs via bootstrap/Sophia at first login.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Build as build-iso.ps1
    participant Validate as validate-unattend.ps1
    participant XML as autounattend.xml
    participant Schema as Schema DLL
    participant ISO as Source ISO

    User->>Build: run build-iso.ps1
    activate Build
    Build->>Build: verify files & hashes
    Build->>Validate: call validate-unattend.ps1 (-UnattendPath, -SourceISO)
    activate Validate
    Validate->>XML: load XML
    XML-->>Validate: provide content
    Validate->>Schema: locate & extract schema (DLL)
    Schema-->>Validate: schema text
    Validate->>Validate: validate XML against schema
    Validate->>Validate: enforce policy checks (ProductKey, DiskConfiguration/InstallTo, FirstLogonCommands)
    opt Source ISO provided
        Validate->>ISO: mount ISO
        ISO-->>Validate: confirm install.wim/install.esd
        Validate->>Validate: inspect image metadata (Get-WindowsImage)
        Validate->>ISO: dismount ISO
    end
    Validate-->>Build: validation passed
    deactivate Validate
    Build->>Build: continue ISO build (oscdimg etc.)
    Build-->>User: ISO creation complete
    deactivate Build
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

🐰
I hopped through XML, nibbling tags light,
Found an empty key and sent it from sight.
Schemas sang yes, policies held tight,
Bootstrap waits for the network’s bright.
Builds now hop onward into the night. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add unattend script and tests for ISO build' accurately reflects the main changes: introduction of validate-unattend.ps1 script, integration into build-iso.ps1, and comprehensive test coverage across multiple test files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-addatt-script

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a65f97aaa8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread validate-unattend.ps1
}

foreach ($entry in $resourceSet) {
if ($entry.Name -like 'Unattend*') {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match schema resource by dictionary key

In Get-UnattendSchemaString, iterating a ResourceSet yields dictionary entries (Key/Value), so checking $entry.Name never matches and the loop falls through to Unable to extract unattend schema. That makes unattend validation fail even when the WSIM DLL is present, and because build-iso.ps1 now always invokes this script, ISO builds are blocked in normal usage. Use the entry key field when matching Unattend* resources.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
build-iso.ps1 (1)

194-195: Consider validating validate-unattend.ps1 existence before invocation.

The script path is defined but not checked for existence like other required files. If the validation script is missing, the error at line 235 will be less clear than a dedicated file-not-found message.

🔧 Suggested fix
     $validateUnattendScript = Join-Path $ScriptRoot "validate-unattend.ps1"
+    if (-not (Test-Path $validateUnattendScript)) {
+        throw "Required file not found: validate-unattend.ps1"
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build-iso.ps1` around lines 194 - 195, The script defines
$validateUnattendScript but doesn't verify it exists before invocation; add a
Test-Path check for $validateUnattendScript (similar to other required-file
checks), and if missing call Write-Error (or throw) with a clear
"validate-unattend.ps1 not found" message so the failure is explicit before you
attempt to run the script.
validate-unattend.ps1 (2)

94-98: Consider renaming event handler parameters to avoid analyzer warnings.

The parameters $sender and $eventArgs trigger PSScriptAnalyzer warnings because they resemble automatic variables. While functionally correct, renaming improves clarity.

🔧 Suggested fix
     $validationErrors = [System.Collections.Generic.List[string]]::new()
-    $eventHandler = [System.Xml.Schema.ValidationEventHandler]{
-        param($sender, $eventArgs)
-        $validationErrors.Add($eventArgs.Message)
+    $eventHandler = [System.Xml.Schema.ValidationEventHandler]{
+        param($s, $e)
+        $validationErrors.Add($e.Message)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@validate-unattend.ps1` around lines 94 - 98, Rename the parameters of the
ValidationEventHandler block to avoid PSScriptAnalyzer automatic-variable
warnings: update the event handler assigned to $eventHandler so it uses
non-automatic names (e.g., $source and $args or $senderObj and $event) instead
of $sender and $eventArgs, and update the handler body to reference the new name
(for example, replace $eventArgs.Message with $args.Message) while leaving
$validationErrors and the handler assignment unchanged.

106-112: StringReader is not disposed.

The StringReader created for the schema text should be disposed. While StringReader doesn't hold unmanaged resources, following the IDisposable pattern consistently is good practice.

🔧 Suggested fix
-    $schemaReader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($SchemaText))
+    $schemaStringReader = [System.IO.StringReader]::new($SchemaText)
+    $schemaReader = [System.Xml.XmlReader]::Create($schemaStringReader)
     try {
         $settings.Schemas.Add('urn:schemas-microsoft-com:unattend', $schemaReader) | Out-Null
     }
     finally {
         $schemaReader.Dispose()
+        $schemaStringReader.Dispose()
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@validate-unattend.ps1` around lines 106 - 112, The StringReader created
inline for the schema text isn't disposed; assign it to a variable (e.g.,
$stringReader = [System.IO.StringReader]::new($SchemaText)), pass that to
[System.Xml.XmlReader]::Create($stringReader) to create $schemaReader, and
ensure you dispose the StringReader in the same finally block (call
$schemaReader.Dispose() and $stringReader.Dispose() or use PowerShell's
using/try/finally pattern) so both $schemaReader and the underlying StringReader
are properly cleaned up when calling
$settings.Schemas.Add('urn:schemas-microsoft-com:unattend', $schemaReader).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@build-iso.ps1`:
- Around line 194-195: The script defines $validateUnattendScript but doesn't
verify it exists before invocation; add a Test-Path check for
$validateUnattendScript (similar to other required-file checks), and if missing
call Write-Error (or throw) with a clear "validate-unattend.ps1 not found"
message so the failure is explicit before you attempt to run the script.

In `@validate-unattend.ps1`:
- Around line 94-98: Rename the parameters of the ValidationEventHandler block
to avoid PSScriptAnalyzer automatic-variable warnings: update the event handler
assigned to $eventHandler so it uses non-automatic names (e.g., $source and
$args or $senderObj and $event) instead of $sender and $eventArgs, and update
the handler body to reference the new name (for example, replace
$eventArgs.Message with $args.Message) while leaving $validationErrors and the
handler assignment unchanged.
- Around line 106-112: The StringReader created inline for the schema text isn't
disposed; assign it to a variable (e.g., $stringReader =
[System.IO.StringReader]::new($SchemaText)), pass that to
[System.Xml.XmlReader]::Create($stringReader) to create $schemaReader, and
ensure you dispose the StringReader in the same finally block (call
$schemaReader.Dispose() and $stringReader.Dispose() or use PowerShell's
using/try/finally pattern) so both $schemaReader and the underlying StringReader
are properly cleaned up when calling
$settings.Schemas.Add('urn:schemas-microsoft-com:unattend', $schemaReader).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: adbb184d-b995-416f-a431-9b865faffb0d

📥 Commits

Reviewing files that changed from the base of the PR and between a8c4ed2 and a65f97a.

📒 Files selected for processing (6)
  • autounattend.xml
  • build-iso.ps1
  • tests/Autounattend.Tests.ps1
  • tests/BuildIso.Tests.ps1
  • tests/ValidateUnattend.Tests.ps1
  • validate-unattend.ps1
💤 Files with no reviewable changes (1)
  • autounattend.xml

@Timpan4 Timpan4 force-pushed the feat-addatt-script branch from a65f97a to 3b0da13 Compare March 28, 2026 13:40
@Timpan4 Timpan4 force-pushed the feat-addatt-script branch from 3b0da13 to 9c2795e Compare March 28, 2026 13:44
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b0da13551

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread build-iso.ps1

# Validate autounattend.xml before doing any expensive ISO work
Write-Step "Validating autounattend.xml"
& $validateUnattendScript -UnattendPath $requiredFiles["autounattend.xml"] -SourceISO $SourceISO
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep URL-only ISO builds from hard-failing on WSIM

build-iso.ps1 now always runs validate-unattend.ps1 before locating/downloading oscdimg, but that validator throws when the WSIM schema DLL is not installed locally (Find-UnattendSchemaDll in validate-unattend.ps1). In environments that intentionally use -OscdimgDownloadUrl without a full ADK install (documented in docs/ISO-GENERATION.md as supported), this change blocks ISO creation with no way to pass -SchemaDllPath through build-iso.ps1, so a previously supported workflow now fails.

Useful? React with 👍 / 👎.

@Timpan4 Timpan4 merged commit d8822a4 into main Mar 28, 2026
2 checks passed
@Timpan4 Timpan4 deleted the feat-addatt-script branch March 28, 2026 13:44
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c2795e340

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bootstrap.ps1
try {
Set-RegistryValueSafe -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" -Name "ConfigureChatAutoInstall" -Value 0 -Type DWord
Set-RegistryValueSafe -Path "HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\Start" -Name "ConfigureStartPins" -Value '{"pinnedList":[]}' -Type String
Set-RegistryValueSafe -Path "HKU:\.DEFAULT\Control Panel\Accessibility\StickyKeys" -Name "Flags" -Value "10" -Type String
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use HKEY_USERS path instead of HKU drive

Set-RegistryValueSafe is called with HKU:\.DEFAULT\..., but default Windows PowerShell sessions only mount HKLM: and HKCU: registry drives, so this path throws “drive not found.” In this function all registry writes are wrapped in one try, so that exception prevents the remaining HKCU tweaks (HideFileExt, TaskbarAl, etc.) from running, leaving the new post-install tweak step partially/non-functional on standard setups.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant