Compare commits

...

14 Commits

Author SHA1 Message Date
titus
b481064437 null-safe patches 2026-03-05 17:20:30 -06:00
Gabi
d2dbf03572 Update Invoke-WPFFixesWinget.ps1 (#4160) 2026-03-05 16:39:50 -06:00
Chris Titus
88c68fd411 update bug report 2026-03-04 15:50:00 -06:00
Chris Titus
e82aaf03f0 update bug report 2026-03-04 15:46:18 -06:00
Chris Titus
9a77fed09b minimize feature request template 2026-03-04 15:29:48 -06:00
Chris Titus
336ac6fb8f remove key flag from unattended (#4154) 2026-03-04 15:14:16 -06:00
Gabi
72ad35bbd8 Remove product key from autounattend.xml (#4152) 2026-03-04 12:26:53 -06:00
Chris Titus
df17ca4695 Fix offline mode (#4153)
* Fix usb error on drive with no existing partitions

* fix offline mode

* Add offline banner
2026-03-04 12:21:18 -06:00
Chris Titus
7f46e8d60d Fix usb error on drive with no existing partitions (#4151) 2026-03-04 11:44:10 -06:00
Chris Titus
9769cafa7d change willshow ui (#4150) 2026-03-04 09:25:54 -06:00
Chris Titus
42dfc8c82b fix write failure on letter assignment from failed usb format 2026-03-04 09:21:39 -06:00
Gabi
d13295bdd8 Update start.ps1 (#4141) 2026-03-03 14:25:14 -06:00
Sean (ANGRYxScotsman)
5fc566b46f Winutil website edit (#4140)
* updated the iso creator docs

* added creator info to the arch docs
2026-03-03 09:17:19 -06:00
Chris Titus
b493737982 Win11 Creator USB and Log Fixes (#4139)
* initial usb fixes

* fix full button width

* Cleanup and Verbose output for copy

* expand ui and fix clean and reset

* Add minimal driver injection

* initial driver support

* fix verbage

* fix syntax error

* create log file on iso generation

* inject to boot.wim for install

* fix single driver install issues

* fix first run probs

* cleanup injection

* improve clean up

* Fix OSCDIMG output and cleanup comments

* Fix Scrollviewer in Status Log

* large drive support and change to Exfat

* Fix BOOT for older UEFI and Add error checks for small usb drives

* fix single usb drive error
2026-03-03 00:19:37 -06:00
20 changed files with 1064 additions and 745 deletions

View File

@@ -6,9 +6,6 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
# 🐞 **Issue Report**
Thank you for taking the time to report an issue! Please provide as much detail as possible to help us address the problem efficiently.
## ⚠️ **IMPORTANT** ## ⚠️ **IMPORTANT**
- 🛠️ **Supported environments only:** We only support Windows 11. - 🛠️ **Supported environments only:** We only support Windows 11.
- 💡 For general questions, use the [Discussions section](https://github.com/Christitustech/winutil/discussions) or join our Community-driven [Discord Server](https://discord.gg/RUbZUZyByQ). - 💡 For general questions, use the [Discussions section](https://github.com/Christitustech/winutil/discussions) or join our Community-driven [Discord Server](https://discord.gg/RUbZUZyByQ).
@@ -24,11 +21,16 @@ body:
validations: validations:
required: true required: true
- type: input - type: dropdown
id: affected_part id: affected_part
attributes: attributes:
label: 📜 What part of Winutil are you having issues with? label: What part of Winutil are you having issues with?
placeholder: "e.g., Tweaks, etc." options:
- Program Install Tab
- Tweaks Tab
- Config Tab
- Updates Tab
- Win11 Creator Tab
validations: validations:
required: true required: true
@@ -39,24 +41,8 @@ body:
validations: validations:
required: true required: true
- type: textarea
id: steps_to_reproduce
attributes:
label: 🔄 Steps to reproduce the issue.
placeholder: "e.g., Step 1: ..., Step 2: ..."
validations:
required: true
- type: textarea - type: textarea
id: error_output id: error_output
attributes: attributes:
label: ❌ Paste the full error output (if available). label: ❌ Paste the full error output (if available) or Screenshot.
placeholder: "Include any relevant logs or error messages." placeholder: "Include any relevant logs or error messages."
- type: textarea
id: additional_context
attributes:
label: 🖼️ Additional context.
placeholder: "Include screenshots, code blocks (use triple backticks ```), or any other relevant information."
validations:
required: false

View File

@@ -13,45 +13,10 @@ body:
- 🛠️ **Supported environments only:** We only support Windows 11. - 🛠️ **Supported environments only:** We only support Windows 11.
- 💡 For general questions, use the [Discussions section](https://github.com/Christitustech/winutil/discussions) or join our Community-driven [Discord Server](https://discord.gg/RUbZUZyByQ). - 💡 For general questions, use the [Discussions section](https://github.com/Christitustech/winutil/discussions) or join our Community-driven [Discord Server](https://discord.gg/RUbZUZyByQ).
- type: checkboxes
attributes:
label: ⚙️ Issue Checklist
options:
- label: I have read the guidelines.
- label: I checked for duplicate issues.
- label: I searched for existing discussions.
- label: I checked for an existing pull request that addresses this request.
validations:
required: true
- type: textarea
id: problem_statement
attributes:
label: ❓ Is your feature request related to a problem?
placeholder: "Provide a clear and concise description of the issue you're facing. Example: 'I'm always frustrated when [...]'"
validations:
required: false
- type: textarea - type: textarea
id: proposed_solution id: proposed_solution
attributes: attributes:
label: 💡 Describe the solution you'd like label: 💡 Describe the solution you'd like
placeholder: "Provide a clear and concise description of what you want to happen." placeholder: "Provide a clear and concise description."
validations: validations:
required: true required: true
- type: textarea
id: alternatives
attributes:
label: 🔄 Describe alternatives you've considered
placeholder: "Provide details on any alternative solutions or features you've thought about."
validations:
required: false
- type: textarea
id: additional_context
attributes:
label: 🖼️ Additional context
placeholder: "Include screenshots, code blocks (use triple backticks ```), or any other relevant information."
validations:
required: false

View File

@@ -47,7 +47,7 @@ jobs:
- name: Setup Pages - name: Setup Pages
id: pages id: pages
uses: actions/configure-pages@v5 uses: actions/configure-pages@v5
- name: Generate Dev Docs from JSON - name: Generate Dev Docs from JSON
shell: pwsh shell: pwsh
run: | run: |

View File

@@ -128,6 +128,154 @@ winutil/
- CheckBoxes for options - CheckBoxes for options
- ListBoxes for selections - ListBoxes for selections
## Win11 Creator Architecture
The **Win11 Creator** is a specialized subsystem within Winutil that creates customized Windows 11 ISOs. It operates independently from the main package installation and tweak system.
### Win11 Creator Components
**Core Functions** (`functions/private/`):
- `Invoke-WinUtilISO.ps1`: Main orchestrator containing all Win11 Creator functions
- `Invoke-WinUtilISOBrowse`: ISO file selection dialog
- `Invoke-WinUtilISOMountAndVerify`: Validates and mounts ISO, verifies it's official Windows 11
- `Invoke-WinUtilISOModify`: Launches modification in background runspace
- `Invoke-WinUtilISOExport`: Handles ISO and USB export
- `Invoke-WinUtilISOCheckExistingWork`: Recovers incomplete work sessions
- `Invoke-WinUtilISOCleanAndReset`: Cleans up temp directories and resets UI
- `Invoke-WinUtilISOScript.ps1`: Applies modifications to mounted install.wim
- Removes provisioned AppX packages (40+ bloatware apps)
- Injects drivers (optional) from current system
- Removes OneDrive setup files
- Applies offline registry tweaks (hardware bypass, privacy, telemetry, OOBE)
- Deletes telemetry scheduled task definitions
- Pre-stages setup scripts from autounattend.xml
- Removes unused Windows editions
- Cleans component store via DISM
### Win11 Creator Data Flow
```
User selects official Windows 11 ISO
Invoke-WinUtilISOBrowse → OpenFileDialog, validates file size
Invoke-WinUtilISOMountAndVerify
├─ Mount ISO via Mount-DiskImage
├─ Verify install.wim or install.esd exists
├─ Check for "Windows 11" in image metadata
├─ Extract available editions (Home, Pro, Enterprise, etc.)
└─ Store ISO path, drive letter, WIM path, image info in $sync
User optionally enables Driver Injection checkbox
Invoke-WinUtilISOModify (runs in background runspace)
├─ Create work directory: ~WinUtil_Win11ISO_[timestamp]
├─ Copy ISO contents to disk (~5-6 GB)
├─ Mount install.wim at selected edition/index
├─ Invoke-WinUtilISOScript:
│ ├─ Remove 40+ bloat AppX packages
│ ├─ Export and inject drivers (if enabled)
│ ├─ Remove OneDrive setup
│ ├─ Load offline registry hives
│ ├─ Apply 50+ registry tweaks (hardware bypass, privacy, telemetry, OOBE, etc.)
│ ├─ Delete telemetry scheduled task files
│ ├─ Pre-stage setup scripts from autounattend.xml to C:\Windows\Setup\Scripts\
│ └─ Unload registry hives
├─ DISM /Cleanup-Image /StartComponentCleanup /ResetBase (saves 300-800 MB)
├─ Dismount and save modified install.wim (~10+ minutes, slowest step)
├─ Export selected edition only (removes all other editions, saves 1-2 GB each)
├─ Dismount source ISO
└─ Report completion, enable export options
Invoke-WinUtilISOExport (user chooses output)
├─ Option 1: Save as ISO
│ ├─ Build bootable ISO via oscdimg.exe (BIOS/UEFI dual-boot)
│ └─ Output: Win11_Modified_[date].iso (2.5-3.5 GB)
└─ Option 2: Write to USB
├─ Format USB as GPT
├─ Create 512 MB EFI partition
├─ Copy modified ISO contents
└─ Output: Bootable USB (minimum 8 GB)
Invoke-WinUtilISOCleanAndReset (optional)
└─ Delete temp working directory (~10-15 GB)
└─ Reset UI to initial state
```
### Win11 Creator Validation & Safety
**ISO Validation**:
- Only accepts official Microsoft Windows 11 ISOs
- Validates presence of install.wim or install.esd
- Checks image metadata for "Windows 11" string
- Rejects custom, modified, or non-Windows 11 ISOs
**Work Session Recovery**:
- Auto-detects incomplete work from previous sessions
- Allows resuming Step 4 (export) without re-running Steps 1-3
- Prevents redundant modifications
**Modification Safety**:
- All registry changes are documented in script (reversible)
- Original ISO never modified; only working copy
- Logged to `WinUtil_Win11ISO.log` for debugging
- DISM handles image dismount with automatic cleanup on error
### Win11 Creator Registry Tweaks
The `Invoke-WinUtilISOScript` function applies **50+ offline registry tweaks**:
**Hardware Bypass**:
- TPM 2.0 check bypass
- Secure Boot requirement bypass
- CPU compatibility bypass
- RAM requirement bypass
- Storage check bypass
**Privacy & Telemetry**:
- Disable advertising ID
- Disable tailored experiences
- Disable input personalization
- Disable speech online privacy
- Disable cloud content suggestions
- Disable app suggestion subscriptions
- Remove CEIP, Appraiser, WaaSMedic, etc.
**OOBE & Setup**:
- Enable local account setup
- Skip Microsoft account requirement
- Dark mode by default
- Empty taskbar and Start Menu
**Post-Setup Installations**:
- Prevent DevHome auto-installation
- Prevent new Outlook Mail app installation
- Prevent Teams auto-installation
**System Features**:
- Disable BitLocker and device encryption
- Disable Chat icon from taskbar
- Disable OneDrive folder backup
- Disable Copilot
- Disable Windows Update during OOBE (re-enabled at first login)
### Driver Injection Feature
**Optional Enhancement**: When enabled, exports all drivers from the running system and injects them into both:
- `install.wim` (main OS image)
- `boot.wim` index 2 (Windows Setup PE environment)
**Use Case**: Enables offline installation on systems with missing drivers.
### Disk Space Requirements
- **Temporary working directory**: ~10-15 GB
- **Original ISO**: 4-6 GB
- **Modified ISO**: 2.5-3.5 GB
- **Total needed**: ~25 GB for safe operation
## Data Flow ## Data Flow
### Application Installation Flow ### Application Installation Flow
@@ -514,6 +662,7 @@ Outputs `winutil.ps1` in the root directory.
- [Contributing Guide](../../contributing/) - How to contribute code - [Contributing Guide](../../contributing/) - How to contribute code
- [User Guide](../../userguide/) - End-user documentation - [User Guide](../../userguide/) - End-user documentation
- [Win11 Creator Guide](../../userguide/win11Creator/) - Building customized Windows 11 ISOs
- [FAQ](../../faq/) - Common questions - [FAQ](../../faq/) - Common questions
## Additional Resources ## Additional Resources

View File

@@ -8,14 +8,14 @@ weight: 5
Winutil includes a built-in **Win11 Creator** tool that lets you take any official Windows 11 ISO and produce a customized, debloated version — with telemetry removed, hardware requirement checks bypassed, and local account setup enabled out of the box. You can export the result as a new ISO file or write it directly to a USB drive. Winutil includes a built-in **Win11 Creator** tool that lets you take any official Windows 11 ISO and produce a customized, debloated version — with telemetry removed, hardware requirement checks bypassed, and local account setup enabled out of the box. You can export the result as a new ISO file or write it directly to a USB drive.
> [!IMPORTANT] > [!IMPORTANT]
> You need a valid Windows 11 ISO before starting. Download one from [Microsoft's official site](https://www.microsoft.com/en-us/software-download/windows11) or use [UUP Dump](https://uupdump.net/). The process uses ~1015 GB of temporary disk space, so make sure you have room. > You need an **official Windows 11 ISO** from [Microsoft's website](https://www.microsoft.com/en-us/software-download/windows11) before starting. Custom, modified, or non-official ISOs are not supported. The process uses ~1015 GB of temporary disk space, so make sure you have room.
--- ---
### Step 1 — Select Your ISO ### Step 1 — Select Your Official Windows 11 ISO
1. Open Winutil and go to the **Win11 Creator** tab. 1. Open Winutil and go to the **Win11 Creator** tab.
2. Click **Browse** and select your Windows 11 ISO file (must be 4 GB or larger). 2. Click **Browse** and select your **official Windows 11 ISO file** from Microsoft (must be 4 GB or larger). Custom or modified ISOs are not supported.
3. The file path and size will appear on screen once selected. 3. The file path and size will appear on screen once selected.
--- ---
@@ -35,14 +35,31 @@ Winutil includes a built-in **Win11 Creator** tool that lets you take any offici
Click **Run Windows ISO Modification and Creator** to start the customization process. Winutil will: Click **Run Windows ISO Modification and Creator** to start the customization process. Winutil will:
**App & Component Removal:**
- **Remove 40+ bloat apps** — Clipchamp, Teams, Copilot, Dev Home, new Outlook, Bing apps, Solitaire, and more - **Remove 40+ bloat apps** — Clipchamp, Teams, Copilot, Dev Home, new Outlook, Bing apps, Solitaire, and more
- **Delete OneDrive setup** from the image - **Delete OneDrive setup** from the image
- **Apply registry tweaks** — disables telemetry, advertising ID, tailored experiences, and cloud content features
**System Customization:**
- **Bypass hardware checks** — removes TPM, Secure Boot, CPU, and RAM requirement enforcement so the ISO installs on unsupported hardware - **Bypass hardware checks** — removes TPM, Secure Boot, CPU, and RAM requirement enforcement so the ISO installs on unsupported hardware
- **Enable local account setup** — injects an `autounattend.xml` that skips the Microsoft account screen during OOBE - **Enable local account setup** — injects an `autounattend.xml` that skips the Microsoft account screen during OOBE
- **Disable BitLocker and device encryption** — removes startup overhead
- **Disable Chat icon** — removes chat taskbar button
- **Strip unused editions** — keeps only your selected edition, saving 12 GB per removed edition - **Strip unused editions** — keeps only your selected edition, saving 12 GB per removed edition
- **Clean the component store** — runs DISM cleanup to reclaim another 300800 MB - **Clean the component store** — runs DISM cleanup to reclaim another 300800 MB
**Privacy & Telemetry Tweaks:**
- **Disable telemetry** — advertising ID, tailored experiences, input personalization, speech online privacy
- **Disable cloud content features** — app suggestions, Microsoft Store recommendations
- **Remove telemetry scheduled tasks** — CEIP, Appraiser, WaaSMedic, and others - **Remove telemetry scheduled tasks** — CEIP, Appraiser, WaaSMedic, and others
- **Disable OneDrive folder backup** — prevents automatic backups to cloud
- **Prevent DevHome and Outlook post-setup installation**
- **Prevent Teams installation** — blocks auto-install after OOBE
- **Prevent new Outlook Mail app installation**
- **Disable Windows Update during OOBE** — re-enabled automatically on first login
- **Disable Copilot and search box suggestions**
**Optional: Driver Injection**
- If enabled, injects all drivers from your current system into the install.wim and boot.wim — useful for offline installations on machines with missing drivers. This is an optional checkbox in Step 3.
A live log shows progress as each step completes. This stage takes **1030 minutes** depending on your disk speed — the WIM dismount near the end is the slowest part, so don't close Winutil while it's running. A live log shows progress as each step completes. This stage takes **1030 minutes** depending on your disk speed — the WIM dismount near the end is the slowest part, so don't close Winutil while it's running.
@@ -94,9 +111,9 @@ When you install Windows 11 from your modified ISO:
- **No Microsoft account required** — create a local account directly during setup - **No Microsoft account required** — create a local account directly during setup
- **No hardware checks** — installs on machines without TPM 2.0, Secure Boot, or supported CPUs - **No hardware checks** — installs on machines without TPM 2.0, Secure Boot, or supported CPUs
- **Dark mode enabled by default** - **Dark mode enabled by default**
- **Empty taskbar and Start Menu** — no pinned apps - **Empty taskbar and Start Menu** — no pinned apps, Chat icon removed
- **Windows Update re-enabled automatically** after first login (it's paused during OOBE to prevent interruption) - **Windows Update disabled during OOBE** — automatically re-enabled on first login to prevent setup interruptions
- **BitLocker disabled**, Recall disabled, desktop shortcuts removed - **BitLocker disabled** — removes startup overhead on first boot
--- ---
@@ -150,4 +167,4 @@ A list of the best free and open source tools for downloading, creating and flas
> Always download Windows ISOs from official Microsoft sources or trusted tools like Rufus/UUP Dump to avoid tampered images. > Always download Windows ISOs from official Microsoft sources or trusted tools like Rufus/UUP Dump to avoid tampered images.
> [!NOTE] > [!NOTE]
> Newer Windows 11 ISOs may not boot correctly on older versions of Ventoy — make sure Ventoy is up to date before use. If issues persist after updating, this is a Ventoy compatibility limitation outside of Winutil's control. > Newer Windows 11 ISOs may not boot correctly on older versions of Ventoy — make sure Ventoy is up to date before use. If issues persist after updating, this is a Ventoy compatibility limitation outside of Winutil's control.

View File

@@ -16,13 +16,20 @@ function Invoke-WinUtilFeatureInstall {
Write-Host "Installing $feature" Write-Host "Installing $feature"
Enable-WindowsOptionalFeature -Online -FeatureName $feature -All -NoRestart Enable-WindowsOptionalFeature -Online -FeatureName $feature -All -NoRestart
} catch { } catch {
if ($CheckBox.Exception.Message -like "*requires elevation*") { if ($_.Exception -and $_.Exception.Message -like "*requires elevation*") {
Write-Warning "Unable to Install $feature due to permissions. Are you running as admin?" Write-Warning "Unable to Install $feature due to permissions. Are you running as admin?"
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" } Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" }
} else { } else {
Write-Warning "Unable to Install $feature due to unhandled exception" Write-Warning "Unable to Install $feature due to unhandled exception"
Write-Warning $CheckBox.Exception.StackTrace if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
if (-not [string]::IsNullOrWhiteSpace($_.Exception.StackTrace)) {
Write-Warning $_.Exception.StackTrace
}
}
} }
} }
} }
@@ -35,13 +42,20 @@ function Invoke-WinUtilFeatureInstall {
Write-Host "Running Script for $CheckBox" Write-Host "Running Script for $CheckBox"
Invoke-Command $scriptblock -ErrorAction stop Invoke-Command $scriptblock -ErrorAction stop
} catch { } catch {
if ($CheckBox.Exception.Message -like "*requires elevation*") { if ($_.Exception -and $_.Exception.Message -like "*requires elevation*") {
Write-Warning "Unable to Install $feature due to permissions. Are you running as admin?" Write-Warning "Unable to Install $feature due to permissions. Are you running as admin?"
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" } Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" }
} else { } else {
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" } Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" }
Write-Warning "Unable to Install $feature due to unhandled exception" Write-Warning "Unable to Install $feature due to unhandled exception"
Write-Warning $CheckBox.Exception.StackTrace if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
if (-not [string]::IsNullOrWhiteSpace($_.Exception.StackTrace)) {
Write-Warning $_.Exception.StackTrace
}
}
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,50 +4,38 @@ function Invoke-WinUtilISOScript {
Applies WinUtil modifications to a mounted Windows 11 install.wim image. Applies WinUtil modifications to a mounted Windows 11 install.wim image.
.DESCRIPTION .DESCRIPTION
Performs the following operations against an already-mounted WIM image: Removes AppX bloatware and OneDrive, optionally injects all drivers exported from
the running system into install.wim and boot.wim (controlled by the
-InjectCurrentSystemDrivers switch), applies offline registry tweaks (hardware
bypass, privacy, OOBE, telemetry, update suppression), deletes CEIP/WU
scheduled-task definition files, and optionally writes autounattend.xml to the ISO
root and removes the support\ folder from the ISO contents directory.
1. Removes provisioned AppX bloatware packages via DISM. All setup scripts embedded in the autounattend.xml <Extensions><File> nodes are
2. Removes OneDriveSetup.exe from the system image. written directly into the WIM at their target paths under C:\Windows\Setup\Scripts\
3. Loads offline registry hives (COMPONENTS, DEFAULT, NTUSER, SOFTWARE, SYSTEM) to ensure they survive Windows Setup stripping unrecognised-namespace XML elements
and applies the following tweaks: from the Panther copy of the answer file.
- Bypasses hardware requirement checks (CPU, RAM, SecureBoot, Storage, TPM).
- Disables sponsored-app delivery and ContentDeliveryManager features.
- Enables local-account OOBE path (BypassNRO).
- Writes autounattend.xml to the Sysprep directory inside the WIM and,
optionally, to the ISO/USB root so Windows Setup picks it up at boot.
- Disables reserved storage.
- Disables BitLocker device encryption.
- Hides the Chat (Teams) taskbar icon.
- Disables OneDrive folder backup (KFM).
- Disables telemetry, advertising ID, and input personalization.
- Blocks post-install delivery of DevHome, Outlook, and Teams.
- Disables Windows Copilot.
- Disables Windows Update during OOBE.
4. Deletes unwanted scheduled-task XML definition files (CEIP, Appraiser, etc.).
5. Removes the support\ folder from the ISO contents directory (if supplied).
Mounting and dismounting the WIM is the responsibility of the caller Mounting/dismounting the WIM is the caller's responsibility (e.g. Invoke-WinUtilISO).
(e.g. Invoke-WinUtilISO).
.PARAMETER ScratchDir .PARAMETER ScratchDir
Mandatory. Full path to the directory where the Windows image is currently mounted. Mandatory. Full path to the directory where the Windows image is currently mounted.
Example: C:\Users\USERNAME\AppData\Local\Temp\WinUtil_Win11ISO_20260222\wim_mount
.PARAMETER ISOContentsDir .PARAMETER ISOContentsDir
Optional. Root directory of the extracted ISO contents. Optional. Root directory of the extracted ISO contents. When supplied,
When supplied, autounattend.xml is also written here so Windows Setup picks it autounattend.xml is written here and the support\ folder is removed.
up automatically at boot, and the support\ folder is deleted from that location.
.PARAMETER AutoUnattendXml .PARAMETER AutoUnattendXml
Optional. Full XML content for autounattend.xml. Optional. Full XML content for autounattend.xml. If empty, the OOBE bypass
In compiled winutil.ps1 this is the embedded $WinUtilAutounattendXml here-string; file is skipped and a warning is logged.
in dev mode it is read from tools\autounattend.xml.
If empty, the OOBE bypass file is skipped and a warning is logged. .PARAMETER InjectCurrentSystemDrivers
Optional. When $true, exports all drivers from the running system and injects
them into install.wim and boot.wim index 2 (Windows Setup PE).
Defaults to $false.
.PARAMETER Log .PARAMETER Log
Optional ScriptBlock used for progress/status logging. Optional ScriptBlock for progress/status logging. Receives a single [string] argument.
Receives a single [string] message argument.
Defaults to { param($m) Write-Output $m } when not supplied.
.EXAMPLE .EXAMPLE
Invoke-WinUtilISOScript -ScratchDir "C:\Temp\wim_mount" Invoke-WinUtilISOScript -ScratchDir "C:\Temp\wim_mount"
@@ -62,24 +50,19 @@ function Invoke-WinUtilISOScript {
.NOTES .NOTES
Author : Chris Titus @christitustech Author : Chris Titus @christitustech
GitHub : https://github.com/ChrisTitusTech GitHub : https://github.com/ChrisTitusTech
Version : 26.02.22 Version : 26.03.02
#> #>
param ( param (
[Parameter(Mandatory)][string]$ScratchDir, [Parameter(Mandatory)][string]$ScratchDir,
# Root directory of the extracted ISO contents. When supplied, autounattend.xml
# is written here so Windows Setup picks it up automatically at boot.
[string]$ISOContentsDir = "", [string]$ISOContentsDir = "",
# Autounattend XML content. In compiled winutil.ps1 this comes from the embedded
# $WinUtilAutounattendXml here-string; in dev mode it is read from tools\autounattend.xml.
[string]$AutoUnattendXml = "", [string]$AutoUnattendXml = "",
[bool]$InjectCurrentSystemDrivers = $false,
[scriptblock]$Log = { param($m) Write-Output $m } [scriptblock]$Log = { param($m) Write-Output $m }
) )
# ── Resolve admin group name (for takeown / icacls) ──────────────────────
$adminSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544') $adminSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
$adminGroup = $adminSID.Translate([System.Security.Principal.NTAccount]) $adminGroup = $adminSID.Translate([System.Security.Principal.NTAccount])
# ── Local helpers ─────────────────────────────────────────────────────────
function Set-ISOScriptReg { function Set-ISOScriptReg {
param ([string]$path, [string]$name, [string]$type, [string]$value) param ([string]$path, [string]$name, [string]$type, [string]$value)
try { try {
@@ -100,15 +83,37 @@ function Invoke-WinUtilISOScript {
} }
} }
# ═════════════════════════════════════════════════════════════════════════ function Add-DriversToImage {
# 1. Remove provisioned AppX packages param ([string]$MountPath, [string]$DriverDir, [string]$Label = "image", [scriptblock]$Logger)
# ═════════════════════════════════════════════════════════════════════════ & dism /English "/image:$MountPath" /Add-Driver "/Driver:$DriverDir" /Recurse 2>&1 |
ForEach-Object { & $Logger " dism[$Label]: $_" }
}
function Invoke-BootWimInject {
param ([string]$BootWimPath, [string]$DriverDir, [scriptblock]$Logger)
Set-ItemProperty -Path $BootWimPath -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue
$mountDir = Join-Path $env:TEMP "WinUtil_BootMount_$(Get-Random)"
New-Item -Path $mountDir -ItemType Directory -Force | Out-Null
try {
& $Logger "Mounting boot.wim (index 2) for driver injection..."
Mount-WindowsImage -ImagePath $BootWimPath -Index 2 -Path $mountDir -ErrorAction Stop | Out-Null
Add-DriversToImage -MountPath $mountDir -DriverDir $DriverDir -Label "boot" -Logger $Logger
& $Logger "Saving boot.wim..."
Dismount-WindowsImage -Path $mountDir -Save -ErrorAction Stop | Out-Null
& $Logger "boot.wim driver injection complete."
} catch {
& $Logger "Warning: boot.wim driver injection failed: $_"
try { Dismount-WindowsImage -Path $mountDir -Discard -ErrorAction SilentlyContinue | Out-Null } catch {}
} finally {
Remove-Item -Path $mountDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
# ── 1. Remove provisioned AppX packages ──────────────────────────────────
& $Log "Removing provisioned AppX packages..." & $Log "Removing provisioned AppX packages..."
$packages = & dism /English "/image:$ScratchDir" /Get-ProvisionedAppxPackages | $packages = & dism /English "/image:$ScratchDir" /Get-ProvisionedAppxPackages |
ForEach-Object { ForEach-Object { if ($_ -match 'PackageName : (.*)') { $matches[1] } }
if ($_ -match 'PackageName : (.*)') { $matches[1] }
}
$packagePrefixes = @( $packagePrefixes = @(
'AppUp.IntelManagementandSecurityStatus', 'AppUp.IntelManagementandSecurityStatus',
@@ -155,25 +160,46 @@ function Invoke-WinUtilISOScript {
'MicrosoftTeams' 'MicrosoftTeams'
) )
$packagesToRemove = $packages | Where-Object { $packages | Where-Object { $pkg = $_; $packagePrefixes | Where-Object { $pkg -like "*$_*" } } |
$pkg = $_ ForEach-Object { & dism /English "/image:$ScratchDir" /Remove-ProvisionedAppxPackage "/PackageName:$_" }
$packagePrefixes | Where-Object { $pkg -like "*$_*" }
} # ── 2. Inject current system drivers (optional) ───────────────────────────
foreach ($package in $packagesToRemove) { if ($InjectCurrentSystemDrivers) {
& dism /English "/image:$ScratchDir" /Remove-ProvisionedAppxPackage "/PackageName:$package" & $Log "Exporting all drivers from running system..."
$driverExportRoot = Join-Path $env:TEMP "WinUtil_DriverExport_$(Get-Random)"
New-Item -Path $driverExportRoot -ItemType Directory -Force | Out-Null
try {
Export-WindowsDriver -Online -Destination $driverExportRoot | Out-Null
& $Log "Injecting current system drivers into install.wim..."
Add-DriversToImage -MountPath $ScratchDir -DriverDir $driverExportRoot -Label "install" -Logger $Log
& $Log "install.wim driver injection complete."
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
$bootWim = Join-Path $ISOContentsDir "sources\boot.wim"
if (Test-Path $bootWim) {
& $Log "Injecting current system drivers into boot.wim..."
Invoke-BootWimInject -BootWimPath $bootWim -DriverDir $driverExportRoot -Logger $Log
} else {
& $Log "Warning: boot.wim not found — skipping boot.wim driver injection."
}
}
} catch {
& $Log "Error during driver export/injection: $_"
} finally {
Remove-Item -Path $driverExportRoot -Recurse -Force -ErrorAction SilentlyContinue
}
} else {
& $Log "Driver injection skipped."
} }
# ═════════════════════════════════════════════════════════════════════════ # ── 3. Remove OneDrive ────────────────────────────────────────────────────
# 2. Remove OneDrive
# ═════════════════════════════════════════════════════════════════════════
& $Log "Removing OneDrive..." & $Log "Removing OneDrive..."
& takeown /f "$ScratchDir\Windows\System32\OneDriveSetup.exe" | Out-Null & takeown /f "$ScratchDir\Windows\System32\OneDriveSetup.exe" | Out-Null
& icacls "$ScratchDir\Windows\System32\OneDriveSetup.exe" /grant "$($adminGroup.Value):(F)" /T /C | Out-Null & icacls "$ScratchDir\Windows\System32\OneDriveSetup.exe" /grant "$($adminGroup.Value):(F)" /T /C | Out-Null
Remove-Item -Path "$ScratchDir\Windows\System32\OneDriveSetup.exe" -Force -ErrorAction SilentlyContinue Remove-Item -Path "$ScratchDir\Windows\System32\OneDriveSetup.exe" -Force -ErrorAction SilentlyContinue
# ═════════════════════════════════════════════════════════════════════════ # ── 4. Registry tweaks ────────────────────────────────────────────────────
# 3. Registry tweaks
# ═════════════════════════════════════════════════════════════════════════
& $Log "Loading offline registry hives..." & $Log "Loading offline registry hives..."
reg load HKLM\zCOMPONENTS "$ScratchDir\Windows\System32\config\COMPONENTS" reg load HKLM\zCOMPONENTS "$ScratchDir\Windows\System32\config\COMPONENTS"
reg load HKLM\zDEFAULT "$ScratchDir\Windows\System32\config\default" reg load HKLM\zDEFAULT "$ScratchDir\Windows\System32\config\default"
@@ -222,14 +248,37 @@ function Invoke-WinUtilISOScript {
Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1' Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1'
if ($AutoUnattendXml) { if ($AutoUnattendXml) {
# ── Place autounattend.xml inside the WIM (Sysprep) ────────────────── try {
$sysprepDest = "$ScratchDir\Windows\System32\Sysprep\autounattend.xml" $xmlDoc = [xml]::new()
Set-Content -Path $sysprepDest -Value $AutoUnattendXml -Encoding UTF8 -Force $xmlDoc.LoadXml($AutoUnattendXml)
& $Log "Written autounattend.xml to Sysprep directory."
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$nsMgr.AddNamespace("sg", "https://schneegans.de/windows/unattend-generator/")
$fileNodes = $xmlDoc.SelectNodes("//sg:File", $nsMgr)
if ($fileNodes -and $fileNodes.Count -gt 0) {
foreach ($fileNode in $fileNodes) {
$absPath = $fileNode.GetAttribute("path")
$relPath = $absPath -replace '^[A-Za-z]:[/\\]', ''
$destPath = Join-Path $ScratchDir $relPath
New-Item -Path (Split-Path $destPath -Parent) -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
$ext = [IO.Path]::GetExtension($destPath).ToLower()
$encoding = switch ($ext) {
{ $_ -in '.ps1', '.xml' } { [System.Text.Encoding]::UTF8 }
{ $_ -in '.reg', '.vbs', '.js' } { [System.Text.UnicodeEncoding]::new($false, $true) }
default { [System.Text.Encoding]::Default }
}
[System.IO.File]::WriteAllBytes($destPath, ($encoding.GetPreamble() + $encoding.GetBytes($fileNode.InnerText.Trim())))
& $Log "Pre-staged setup script: $relPath"
}
} else {
& $Log "Warning: no <Extensions><File> nodes found in autounattend.xml — setup scripts not pre-staged."
}
} catch {
& $Log "Warning: could not pre-stage setup scripts from autounattend.xml: $_"
}
# ── Place autounattend.xml at the ISO / USB root ──────────────────────
# Windows Setup reads this file first (before booting into the OS),
# which is what drives the local-account / OOBE bypass at install time.
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) { if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
$isoDest = Join-Path $ISOContentsDir "autounattend.xml" $isoDest = Join-Path $ISOContentsDir "autounattend.xml"
Set-Content -Path $isoDest -Value $AutoUnattendXml -Encoding UTF8 -Force Set-Content -Path $isoDest -Value $AutoUnattendXml -Encoding UTF8 -Force
@@ -272,8 +321,8 @@ function Invoke-WinUtilISOScript {
Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate' Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate'
& $Log "Disabling Copilot..." & $Log "Disabling Copilot..."
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1' Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1'
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0' Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0'
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Explorer' 'DisableSearchBoxSuggestions' 'REG_DWORD' '1' Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Explorer' 'DisableSearchBoxSuggestions' 'REG_DWORD' '1'
& $Log "Disabling Windows Update during OOBE (re-enabled on first logon via FirstLogon.ps1)..." & $Log "Disabling Windows Update during OOBE (re-enabled on first logon via FirstLogon.ps1)..."
@@ -304,12 +353,9 @@ function Invoke-WinUtilISOScript {
reg unload HKLM\zSOFTWARE reg unload HKLM\zSOFTWARE
reg unload HKLM\zSYSTEM reg unload HKLM\zSYSTEM
# ═════════════════════════════════════════════════════════════════════════ # ── 5. Delete scheduled task definition files ─────────────────────────────
# 4. Delete scheduled task definition files
# ═════════════════════════════════════════════════════════════════════════
& $Log "Deleting scheduled task definition files..." & $Log "Deleting scheduled task definition files..."
$tasksPath = "$ScratchDir\Windows\System32\Tasks" $tasksPath = "$ScratchDir\Windows\System32\Tasks"
Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" -Force -ErrorAction SilentlyContinue
Remove-Item "$tasksPath\Microsoft\Windows\Customer Experience Improvement Program" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\Customer Experience Improvement Program" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\ProgramDataUpdater" -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\ProgramDataUpdater" -Force -ErrorAction SilentlyContinue
@@ -321,12 +367,9 @@ function Invoke-WinUtilISOScript {
Remove-Item "$tasksPath\Microsoft\Windows\WaaSMedic" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\WaaSMedic" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item "$tasksPath\Microsoft\Windows\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item "$tasksPath\Microsoft\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue
& $Log "Scheduled task files deleted." & $Log "Scheduled task files deleted."
# ═════════════════════════════════════════════════════════════════════════ # ── 6. Remove ISO support folder ─────────────────────────────────────────
# 5. Remove ISO support folder (fresh-install only; not needed)
# ═════════════════════════════════════════════════════════════════════════
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) { if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
& $Log "Removing ISO support\ folder..." & $Log "Removing ISO support\ folder..."
Remove-Item -Path (Join-Path $ISOContentsDir "support") -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path (Join-Path $ISOContentsDir "support") -Recurse -Force -ErrorAction SilentlyContinue

View File

@@ -0,0 +1,268 @@
function Invoke-WinUtilISORefreshUSBDrives {
$combo = $sync["WPFWin11ISOUSBDriveComboBox"]
$removable = @(Get-Disk | Where-Object { $_.BusType -eq "USB" } | Sort-Object Number)
$combo.Items.Clear()
if ($removable.Count -eq 0) {
$combo.Items.Add("No USB drives detected")
$combo.SelectedIndex = 0
$sync["Win11ISOUSBDisks"] = @()
Write-Win11ISOLog "No USB drives detected."
return
}
foreach ($disk in $removable) {
$sizeGB = [math]::Round($disk.Size / 1GB, 1)
$combo.Items.Add("Disk $($disk.Number): $($disk.FriendlyName) [$sizeGB GB] - $($disk.PartitionStyle)")
}
$combo.SelectedIndex = 0
Write-Win11ISOLog "Found $($removable.Count) USB drive(s)."
$sync["Win11ISOUSBDisks"] = $removable
}
function Invoke-WinUtilISOWriteUSB {
$contentsDir = $sync["Win11ISOContentsDir"]
$usbDisks = $sync["Win11ISOUSBDisks"]
if (-not $contentsDir -or -not (Test-Path $contentsDir)) {
[System.Windows.MessageBox]::Show("No modified ISO content found. Please complete Steps 1-3 first.", "Not Ready", "OK", "Warning")
return
}
$combo = $sync["WPFWin11ISOUSBDriveComboBox"]
$selectedIndex = $combo.SelectedIndex
$selectedItemText = [string]$combo.SelectedItem
$usbDisks = @($usbDisks)
$targetDisk = $null
if ($selectedIndex -ge 0 -and $selectedIndex -lt $usbDisks.Count) {
$targetDisk = $usbDisks[$selectedIndex]
} elseif ($selectedItemText -match 'Disk\s+(\d+):') {
$selectedDiskNum = [int]$matches[1]
$targetDisk = $usbDisks | Where-Object { $_.Number -eq $selectedDiskNum } | Select-Object -First 1
}
if (-not $targetDisk) {
[System.Windows.MessageBox]::Show("Please select a USB drive from the dropdown.", "No Drive Selected", "OK", "Warning")
return
}
$diskNum = $targetDisk.Number
$sizeGB = [math]::Round($targetDisk.Size / 1GB, 1)
$confirm = [System.Windows.MessageBox]::Show(
"ALL data on Disk $diskNum ($($targetDisk.FriendlyName), $sizeGB GB) will be PERMANENTLY ERASED.`n`nAre you sure you want to continue?",
"Confirm USB Erase", "YesNo", "Warning")
if ($confirm -ne "Yes") {
Write-Win11ISOLog "USB write cancelled by user."
return
}
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false
Write-Win11ISOLog "Starting USB write to Disk $diskNum..."
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$runspace.ApartmentState = "STA"
$runspace.ThreadOptions = "ReuseThread"
$runspace.Open()
$runspace.SessionStateProxy.SetVariable("sync", $sync)
$runspace.SessionStateProxy.SetVariable("diskNum", $diskNum)
$runspace.SessionStateProxy.SetVariable("contentsDir", $contentsDir)
$script = [Management.Automation.PowerShell]::Create()
$script.Runspace = $runspace
$script.AddScript({
function Log($msg) {
$ts = (Get-Date).ToString("HH:mm:ss")
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $msg"
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
})
}
function SetProgress($label, $pct) {
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = $label
$sync.progressBarTextBlock.ToolTip = $label
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
})
}
function Get-FreeDriveLetter {
$used = (Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue).Name
foreach ($c in [char[]](68..90)) {
if ($used -notcontains [string]$c) { return $c }
}
return $null
}
try {
SetProgress "Formatting USB drive..." 10
# Phase 1: Clean disk via diskpart (retry once if the drive is not yet ready)
$dpFile1 = Join-Path $env:TEMP "winutil_diskpart_$(Get-Random).txt"
"select disk $diskNum`nclean`nexit" | Set-Content -Path $dpFile1 -Encoding ASCII
Log "Running diskpart clean on Disk $diskNum..."
$dpCleanOut = diskpart /s $dpFile1 2>&1
$dpCleanOut | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" }
Remove-Item $dpFile1 -Force -ErrorAction SilentlyContinue
if (($dpCleanOut -join ' ') -match 'device is not ready') {
Log "Disk $diskNum was not ready; waiting 5 seconds and retrying clean..."
Start-Sleep -Seconds 5
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
$dpFile1b = Join-Path $env:TEMP "winutil_diskpart_$(Get-Random).txt"
"select disk $diskNum`nclean`nexit" | Set-Content -Path $dpFile1b -Encoding ASCII
diskpart /s $dpFile1b 2>&1 | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" }
Remove-Item $dpFile1b -Force -ErrorAction SilentlyContinue
}
# Phase 2: Initialize as GPT
Start-Sleep -Seconds 2
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
$diskObj = Get-Disk -Number $diskNum -ErrorAction Stop
if ($diskObj.PartitionStyle -eq 'RAW') {
Initialize-Disk -Number $diskNum -PartitionStyle GPT -ErrorAction Stop
Log "Disk $diskNum initialized as GPT."
} else {
Set-Disk -Number $diskNum -PartitionStyle GPT -ErrorAction Stop
Log "Disk $diskNum converted to GPT (was $($diskObj.PartitionStyle))."
}
# Phase 3: Create FAT32 partition via diskpart, then format with Format-Volume
# (diskpart's 'format' command can fail with "no volume selected" on fresh/never-formatted drives)
$volLabel = "W11-" + (Get-Date).ToString('yyMMdd')
$dpFile2 = Join-Path $env:TEMP "winutil_diskpart2_$(Get-Random).txt"
$maxFat32PartitionMB = 32768
$diskSizeMB = [int][Math]::Floor((Get-Disk -Number $diskNum -ErrorAction Stop).Size / 1MB)
$createPartitionCommand = "create partition primary"
if ($diskSizeMB -gt $maxFat32PartitionMB) {
$createPartitionCommand = "create partition primary size=$maxFat32PartitionMB"
Log "Disk $diskNum is $diskSizeMB MB; creating FAT32 partition capped at $maxFat32PartitionMB MB (32 GB)."
}
@(
"select disk $diskNum"
$createPartitionCommand
"exit"
) | Set-Content -Path $dpFile2 -Encoding ASCII
Log "Creating partitions on Disk $diskNum..."
diskpart /s $dpFile2 2>&1 | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" }
Remove-Item $dpFile2 -Force -ErrorAction SilentlyContinue
SetProgress "Formatting USB partition..." 25
Start-Sleep -Seconds 3
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
$partitions = Get-Partition -DiskNumber $diskNum -ErrorAction Stop
Log "Partitions on Disk $diskNum after creation: $($partitions.Count)"
foreach ($p in $partitions) {
Log " Partition $($p.PartitionNumber) Type=$($p.Type) Letter=$($p.DriveLetter) Size=$([math]::Round($p.Size/1MB))MB"
}
$winpePart = $partitions | Where-Object { $_.Type -eq "Basic" } | Select-Object -Last 1
if (-not $winpePart) {
throw "Could not find the Basic partition on Disk $diskNum after creation."
}
# Format using Format-Volume (reliable on fresh drives; diskpart format fails
# with 'no volume selected' when the partition has never been formatted before)
Log "Formatting Partition $($winpePart.PartitionNumber) as FAT32 (label: $volLabel)..."
Get-Partition -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber |
Format-Volume -FileSystem FAT32 -NewFileSystemLabel $volLabel -Force -Confirm:$false | Out-Null
Log "Partition $($winpePart.PartitionNumber) formatted as FAT32."
SetProgress "Assigning drive letters..." 30
Start-Sleep -Seconds 2
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
try { Remove-PartitionAccessPath -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -AccessPath "$($winpePart.DriveLetter):" -ErrorAction SilentlyContinue } catch {}
$usbLetter = Get-FreeDriveLetter
if (-not $usbLetter) { throw "No free drive letters (D-Z) available to assign to the USB data partition." }
Set-Partition -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -NewDriveLetter $usbLetter
Log "Assigned drive letter $usbLetter to WINPE partition (Partition $($winpePart.PartitionNumber))."
Start-Sleep -Seconds 2
$usbDrive = "${usbLetter}:"
$retries = 0
while (-not (Test-Path $usbDrive) -and $retries -lt 6) {
$retries++
Log "Waiting for $usbDrive to become accessible (attempt $retries/6)..."
Start-Sleep -Seconds 2
}
if (-not (Test-Path $usbDrive)) { throw "Drive $usbDrive is not accessible after letter assignment." }
Log "USB data partition: $usbDrive"
$contentSizeBytes = (Get-ChildItem -LiteralPath $contentsDir -File -Recurse -Force -ErrorAction Stop | Measure-Object -Property Length -Sum).Sum
if (-not $contentSizeBytes) { $contentSizeBytes = 0 }
$usbVolume = Get-Volume -DriveLetter $usbLetter -ErrorAction Stop
$partitionCapacityBytes = [int64]$usbVolume.Size
$partitionFreeBytes = [int64]$usbVolume.SizeRemaining
$contentSizeGB = [math]::Round($contentSizeBytes / 1GB, 2)
$partitionCapacityGB = [math]::Round($partitionCapacityBytes / 1GB, 2)
$partitionFreeGB = [math]::Round($partitionFreeBytes / 1GB, 2)
Log "Source content size: $contentSizeGB GB. USB partition capacity: $partitionCapacityGB GB, free: $partitionFreeGB GB."
if ($contentSizeBytes -gt $partitionCapacityBytes) {
throw "ISO content ($contentSizeGB GB) is larger than the USB partition capacity ($partitionCapacityGB GB). Use a larger USB drive or reduce image size."
}
if ($contentSizeBytes -gt $partitionFreeBytes) {
throw "Insufficient free space on USB partition. Required: $contentSizeGB GB, available: $partitionFreeGB GB."
}
SetProgress "Copying Windows 11 files to USB..." 45
# Copy files; split install.wim if > 4 GB (FAT32 limit)
$installWim = Join-Path $contentsDir "sources\install.wim"
if (Test-Path $installWim) {
$wimSizeMB = [math]::Round((Get-Item $installWim).Length / 1MB)
if ($wimSizeMB -gt 3800) {
Log "install.wim is $wimSizeMB MB - splitting for FAT32 compatibility... This will take several minutes."
$splitDest = Join-Path $usbDrive "sources\install.swm"
New-Item -ItemType Directory -Path (Split-Path $splitDest) -Force | Out-Null
Split-WindowsImage -ImagePath $installWim -SplitImagePath $splitDest -FileSize 3800 -CheckIntegrity
Log "install.wim split complete."
Log "Copying remaining files to USB..."
& robocopy $contentsDir $usbDrive /E /XF install.wim /NFL /NDL /NJH /NJS
} else {
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS
}
} else {
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS
}
SetProgress "Finalising USB drive..." 90
Log "Files copied to USB."
SetProgress "USB write complete" 100
Log "USB drive is ready for use."
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
[System.Windows.MessageBox]::Show(
"USB drive created successfully!`n`nYou can now boot from this drive to install Windows 11.",
"USB Ready", "OK", "Info")
})
} catch {
Log "ERROR during USB write: $_"
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
[System.Windows.MessageBox]::Show("USB write failed:`n`n$_", "USB Write Error", "OK", "Error")
})
} finally {
Start-Sleep -Milliseconds 800
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = ""
$sync.progressBarTextBlock.ToolTip = ""
$sync.ProgressBar.Value = 0
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true
})
}
}) | Out-Null
$script.BeginInvoke() | Out-Null
}

View File

@@ -25,20 +25,35 @@ function Invoke-WinUtilScript {
Invoke-Command $scriptblock -ErrorAction Stop Invoke-Command $scriptblock -ErrorAction Stop
} catch [System.Management.Automation.CommandNotFoundException] { } catch [System.Management.Automation.CommandNotFoundException] {
Write-Warning "The specified command was not found." Write-Warning "The specified command was not found."
Write-Warning $PSItem.Exception.message if ($_.Exception -and -not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
} catch [System.Management.Automation.RuntimeException] { } catch [System.Management.Automation.RuntimeException] {
Write-Warning "A runtime exception occurred." Write-Warning "A runtime exception occurred."
Write-Warning $PSItem.Exception.message if ($_.Exception -and -not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
} catch [System.Security.SecurityException] { } catch [System.Security.SecurityException] {
Write-Warning "A security exception occurred." Write-Warning "A security exception occurred."
Write-Warning $PSItem.Exception.message if ($_.Exception -and -not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
} catch [System.UnauthorizedAccessException] { } catch [System.UnauthorizedAccessException] {
Write-Warning "Access denied. You do not have permission to perform this operation." Write-Warning "Access denied. You do not have permission to perform this operation."
Write-Warning $PSItem.Exception.message if ($_.Exception -and -not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
} catch { } catch {
# Generic catch block to handle any other type of exception # Generic catch block to handle any other type of exception
Write-Warning "Unable to run script for $name due to unhandled exception" Write-Warning "Unable to run script for $name due to unhandled exception"
Write-Warning $psitem.Exception.StackTrace if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
if (-not [string]::IsNullOrWhiteSpace($_.Exception.StackTrace)) {
Write-Warning $_.Exception.StackTrace
}
}
} }
} }

View File

@@ -28,6 +28,13 @@ function Set-WinUtilDNS {
} }
} catch { } catch {
Write-Warning "Unable to set DNS Provider due to an unhandled exception" Write-Warning "Unable to set DNS Provider due to an unhandled exception"
Write-Warning $psitem.Exception.StackTrace if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
if (-not [string]::IsNullOrWhiteSpace($_.Exception.StackTrace)) {
Write-Warning $_.Exception.StackTrace
}
}
} }
} }

View File

@@ -46,11 +46,22 @@ function Set-WinUtilRegistry {
} catch [System.Security.SecurityException] { } catch [System.Security.SecurityException] {
Write-Warning "Unable to set $Path\$Name to $Value due to a Security Exception" Write-Warning "Unable to set $Path\$Name to $Value due to a Security Exception"
} catch [System.Management.Automation.ItemNotFoundException] { } catch [System.Management.Automation.ItemNotFoundException] {
Write-Warning $psitem.Exception.ErrorRecord Write-Warning $_.Exception.ErrorRecord
} catch [System.UnauthorizedAccessException] { } catch [System.UnauthorizedAccessException] {
Write-Warning $psitem.Exception.Message if ($_.Exception -and -not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
} else {
Write-Warning "Unauthorized access while setting $Path\$Name"
}
} catch { } catch {
Write-Warning "Unable to set $Name due to unhandled exception" Write-Warning "Unable to set $Name due to unhandled exception"
Write-Warning $psitem.Exception.StackTrace if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
if (-not [string]::IsNullOrWhiteSpace($_.Exception.StackTrace)) {
Write-Warning $_.Exception.StackTrace
}
}
} }
} }

View File

@@ -29,14 +29,23 @@ function Set-WinUtilScheduledTask {
Enable-ScheduledTask -TaskName $Name -ErrorAction Stop Enable-ScheduledTask -TaskName $Name -ErrorAction Stop
} }
} catch [System.Exception] { } catch [System.Exception] {
if($psitem.Exception.Message -like "*The system cannot find the file specified*") { if ($_.Exception -and $_.Exception.Message -like "*The system cannot find the file specified*") {
Write-Warning "Scheduled Task $name was not Found" Write-Warning "Scheduled Task $name was not Found"
} else { } else {
Write-Warning "Unable to set $Name due to unhandled exception" Write-Warning "Unable to set $Name due to unhandled exception"
Write-Warning $psitem.Exception.Message if ($_.Exception -and -not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
} }
} catch { } catch {
Write-Warning "Unable to run script for $name due to unhandled exception" Write-Warning "Unable to run script for $name due to unhandled exception"
Write-Warning $psitem.Exception.StackTrace if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
if (-not [string]::IsNullOrWhiteSpace($_.Exception.StackTrace)) {
Write-Warning $_.Exception.StackTrace
}
}
} }
} }

View File

@@ -34,7 +34,14 @@ Function Set-WinUtilService {
Write-Warning "Service $Name was not found" Write-Warning "Service $Name was not found"
} catch { } catch {
Write-Warning "Unable to set $Name due to unhandled exception" Write-Warning "Unable to set $Name due to unhandled exception"
Write-Warning $_.Exception.Message if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.Exception.Message
}
if (-not [string]::IsNullOrWhiteSpace($_.Exception.StackTrace)) {
Write-Warning $_.Exception.StackTrace
}
}
} }
} }

View File

@@ -1,26 +0,0 @@
function Test-WinUtilInternetConnection {
<#
.SYNOPSIS
Tests if the computer has internet connectivity
.OUTPUTS
Boolean - True if connected, False if offline
#>
try {
# Test multiple reliable endpoints
$testSites = @(
"8.8.8.8", # Google DNS
"1.1.1.1", # Cloudflare DNS
"208.67.222.222" # OpenDNS
)
foreach ($site in $testSites) {
if (Test-Connection -ComputerName $site -Count 1 -Quiet -ErrorAction SilentlyContinue) {
return $true
}
}
return $false
}
catch {
return $false
}
}

View File

@@ -11,7 +11,7 @@ function Invoke-WPFFixesWinget {
try { try {
Set-WinUtilTaskbaritem -state "Indeterminate" -overlay "logo" Set-WinUtilTaskbaritem -state "Indeterminate" -overlay "logo"
Write-Host "==> Starting Winget Repair" Write-Host "==> Starting Winget Repair"
Install-WinUtilWinget -Force Install-WinUtilWinget
} catch { } catch {
Write-Error "Failed to install winget: $_" Write-Error "Failed to install winget: $_"
Set-WinUtilTaskbaritem -state "Error" -overlay "warning" Set-WinUtilTaskbaritem -state "Error" -overlay "warning"

View File

@@ -15,12 +15,14 @@ $maxthreads = [int]$env:NUMBER_OF_PROCESSORS
$hashVars = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync',$sync,$Null $hashVars = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync',$sync,$Null
$debugVar = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'DebugPreference',$DebugPreference,$Null $debugVar = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'DebugPreference',$DebugPreference,$Null
$uiVar = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_NOUI',$PARAM_NOUI,$Null $uiVar = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_NOUI',$PARAM_NOUI,$Null
$offlineVar = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_OFFLINE',$PARAM_OFFLINE,$Null
$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() $InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
# Add the variable to the session state # Add the variable to the session state
$InitialSessionState.Variables.Add($hashVars) $InitialSessionState.Variables.Add($hashVars)
$InitialSessionState.Variables.Add($debugVar) $InitialSessionState.Variables.Add($debugVar)
$InitialSessionState.Variables.Add($uiVar) $InitialSessionState.Variables.Add($uiVar)
$InitialSessionState.Variables.Add($offlineVar)
# Get every private function and add them to the session state # Get every private function and add them to the session state
$functions = Get-ChildItem function:\ | Where-Object { $_.Name -imatch 'winutil|WPF' } $functions = Get-ChildItem function:\ | Where-Object { $_.Name -imatch 'winutil|WPF' }
@@ -350,11 +352,10 @@ $sync["Form"].Add_ContentRendered({
Write-Debug "Unable to retrieve information about the primary monitor." Write-Debug "Unable to retrieve information about the primary monitor."
} }
# Check internet connectivity and disable install tab if offline if ($PARAM_OFFLINE) {
#$isOnline = Test-WinUtilInternetConnection # Show offline banner
$isOnline = $true # Temporarily force online mode until we can resolve false negatives $sync.WPFOfflineBanner.Visibility = [System.Windows.Visibility]::Visible
if (-not $isOnline) {
# Disable the install tab # Disable the install tab
$sync.WPFTab1BT.IsEnabled = $false $sync.WPFTab1BT.IsEnabled = $false
$sync.WPFTab1BT.Opacity = 0.5 $sync.WPFTab1BT.Opacity = 0.5
@@ -540,6 +541,10 @@ $sync["FontScalingApplyButton"].Add_Click({
# ── Win11ISO Tab button handlers ────────────────────────────────────────────── # ── Win11ISO Tab button handlers ──────────────────────────────────────────────
$sync["WPFTab5BT"].Add_Click({
$sync["Form"].Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null
})
$sync["WPFWin11ISOBrowseButton"].Add_Click({ $sync["WPFWin11ISOBrowseButton"].Add_Click({
Write-Debug "WPFWin11ISOBrowseButton clicked" Write-Debug "WPFWin11ISOBrowseButton clicked"
Invoke-WinUtilISOBrowse Invoke-WinUtilISOBrowse

View File

@@ -9,7 +9,8 @@
param ( param (
[string]$Config, [string]$Config,
[switch]$Run, [switch]$Run,
[switch]$Noui [switch]$Noui,
[switch]$Offline
) )
if ($Config) { if ($Config) {
@@ -27,25 +28,10 @@ if ($Noui) {
$PARAM_NOUI = $true $PARAM_NOUI = $true
} }
# Load DLLs $PARAM_OFFLINE = $false
Add-Type -AssemblyName PresentationFramework if ($Offline) {
Add-Type -AssemblyName System.Windows.Forms $PARAM_OFFLINE = $true
}
# Variable to sync between runspaces
$sync = [Hashtable]::Synchronized(@{})
$sync.PSScriptRoot = $PSScriptRoot
$sync.version = "#{replaceme}"
$sync.configs = @{}
$sync.Buttons = [System.Collections.Generic.List[PSObject]]::new()
$sync.preferences = @{}
$sync.ProcessRunning = $false
$sync.selectedApps = [System.Collections.Generic.List[string]]::new()
$sync.selectedTweaks = [System.Collections.Generic.List[string]]::new()
$sync.selectedToggles = [System.Collections.Generic.List[string]]::new()
$sync.selectedFeatures = [System.Collections.Generic.List[string]]::new()
$sync.currentTab = "Install"
$sync.selectedAppsStackPanel
$sync.selectedAppsPopup
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
@@ -80,6 +66,26 @@ if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]:
break break
} }
# Load DLLs
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName System.Windows.Forms
# Variable to sync between runspaces
$sync = [Hashtable]::Synchronized(@{})
$sync.PSScriptRoot = $PSScriptRoot
$sync.version = "#{replaceme}"
$sync.configs = @{}
$sync.Buttons = [System.Collections.Generic.List[PSObject]]::new()
$sync.preferences = @{}
$sync.ProcessRunning = $false
$sync.selectedApps = [System.Collections.Generic.List[string]]::new()
$sync.selectedTweaks = [System.Collections.Generic.List[string]]::new()
$sync.selectedToggles = [System.Collections.Generic.List[string]]::new()
$sync.selectedFeatures = [System.Collections.Generic.List[string]]::new()
$sync.currentTab = "Install"
$sync.selectedAppsStackPanel
$sync.selectedAppsPopup
$dateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" $dateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
# Set the path for the winutil directory # Set the path for the winutil directory

View File

@@ -5,10 +5,6 @@
<settings pass="windowsPE"> <settings pass="windowsPE">
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<UserData> <UserData>
<ProductKey>
<Key>00000-00000-00000-00000-00000</Key>
<WillShowUI>Always</WillShowUI>
</ProductKey>
<AcceptEula>true</AcceptEula> <AcceptEula>true</AcceptEula>
</UserData> </UserData>
<UseConfigurationSet>false</UseConfigurationSet> <UseConfigurationSet>false</UseConfigurationSet>

View File

@@ -273,22 +273,43 @@
<Style TargetType="ComboBox"> <Style TargetType="ComboBox">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundColor}" /> <Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundColor}" />
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundColor}" /> <Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundColor}" />
<Setter Property="MinWidth" Value="{DynamicResource ButtonWidth}" />
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="ComboBox"> <ControlTemplate TargetType="ComboBox">
<Grid> <Grid>
<ToggleButton x:Name="ToggleButton" <Border x:Name="OuterBorder"
Background="{TemplateBinding Background}" BorderBrush="{DynamicResource BorderColor}"
BorderBrush="{TemplateBinding Background}" BorderThickness="1"
BorderThickness="0" CornerRadius="{DynamicResource ButtonCornerRadius}"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Background="{TemplateBinding Background}">
ClickMode="Press"> <ToggleButton x:Name="ToggleButton"
<TextBlock Text="{TemplateBinding SelectionBoxItem}" Background="Transparent"
Foreground="{TemplateBinding Foreground}" BorderThickness="0"
Background="Transparent" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" ClickMode="Press">
/> <Grid>
</ToggleButton> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{TemplateBinding SelectionBoxItem}"
Foreground="{TemplateBinding Foreground}"
Background="Transparent"
HorizontalAlignment="Left" VerticalAlignment="Center"
Margin="6,3,2,3"/>
<Path Grid.Column="1"
Data="M 0,0 L 8,0 L 4,5 Z"
Fill="{TemplateBinding Foreground}"
Width="8" Height="5"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Stretch="Uniform"
Margin="4,0,6,0"/>
</Grid>
</ToggleButton>
</Border>
<Popup x:Name="Popup" <Popup x:Name="Popup"
IsOpen="{TemplateBinding IsDropDownOpen}" IsOpen="{TemplateBinding IsDropDownOpen}"
Placement="Bottom" Placement="Bottom"
@@ -297,11 +318,11 @@
PopupAnimation="Slide"> PopupAnimation="Slide">
<Border x:Name="DropDownBorder" <Border x:Name="DropDownBorder"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding Foreground}" BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1" BorderThickness="1"
CornerRadius="4"> CornerRadius="4">
<ScrollViewer> <ScrollViewer>
<ItemsPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2"/> <ItemsPresenter HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4,2"/>
</ScrollViewer> </ScrollViewer>
</Border> </Border>
</Popup> </Popup>
@@ -922,13 +943,19 @@
</Window.Resources> </Window.Resources>
<Grid Background="{DynamicResource MainBackgroundColor}" ShowGridLines="False" Name="WPFMainGrid" Width="Auto" Height="Auto" HorizontalAlignment="Stretch"> <Grid Background="{DynamicResource MainBackgroundColor}" ShowGridLines="False" Name="WPFMainGrid" Width="Auto" Height="Auto" HorizontalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Row="0" Background="{DynamicResource MainBackgroundColor}"> <!-- Offline banner -->
<Border Name="WPFOfflineBanner" Grid.Row="0" Background="#8B0000" Visibility="Collapsed" Padding="6,4">
<TextBlock Text="&#x26A0; Offline Mode - No Internet Connection" Foreground="White" FontWeight="Bold"
HorizontalAlignment="Center" FontSize="13" Background="Transparent"/>
</Border>
<Grid Grid.Row="1" Background="{DynamicResource MainBackgroundColor}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <!-- Navigation buttons --> <ColumnDefinition Width="Auto"/> <!-- Navigation buttons -->
<ColumnDefinition Width="*"/> <!-- Search bar and buttons --> <ColumnDefinition Width="*"/> <!-- Search bar and buttons -->
@@ -1171,7 +1198,7 @@
</Grid> </Grid>
</Grid> </Grid>
<TabControl Name="WPFTabNav" Background="Transparent" Width="Auto" Height="Auto" BorderBrush="Transparent" BorderThickness="0" Grid.Row="1" Grid.Column="0" Padding="-1"> <TabControl Name="WPFTabNav" Background="Transparent" Width="Auto" Height="Auto" BorderBrush="Transparent" BorderThickness="0" Grid.Row="2" Grid.Column="0" Padding="-1">
<TabItem Header="Install" Visibility="Collapsed" Name="WPFTab1"> <TabItem Header="Install" Visibility="Collapsed" Name="WPFTab1">
<Grid Background="Transparent" > <Grid Background="Transparent" >
@@ -1340,21 +1367,17 @@
</ScrollViewer> </ScrollViewer>
</TabItem> </TabItem>
<TabItem Header="Win11ISO" Visibility="Collapsed" Name="WPFTab5"> <TabItem Header="Win11ISO" Visibility="Collapsed" Name="WPFTab5">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="{DynamicResource TabContentMargin}"> <Grid Name="Win11ISOPanel" Margin="{DynamicResource TabContentMargin}" Background="Transparent">
<Grid Background="Transparent" Name="Win11ISOPanel"> <Grid.RowDefinitions>
<Grid.RowDefinitions> <RowDefinition Height="Auto"/> <!-- Steps 1-4 -->
<RowDefinition Height="Auto"/> <!-- Step 1: Select ISO --> <RowDefinition Height="*"/> <!-- Log / Status -->
<RowDefinition Height="Auto"/> <!-- Step 2: Mount & Verify --> </Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Step 3: Modify install.wim -->
<RowDefinition Height="Auto"/> <!-- Step 4: Output Options -->
<RowDefinition Height="Auto"/> <!-- Log / Status -->
</Grid.RowDefinitions>
<!-- ═══════════════════════════════════════════════════════════ --> <!-- Steps 1-4 -->
<!-- STEP 1 : Select Windows 11 ISO --> <StackPanel Grid.Row="0">
<!-- ═══════════════════════════════════════════════════════════ -->
<Border Grid.Row="0" Name="WPFWin11ISOSelectSection" Style="{StaticResource BorderStyle}"> <!-- ─── STEP 1 : Select Windows 11 ISO ─────────────── -->
<Grid Margin="5"> <Grid Name="WPFWin11ISOSelectSection" Margin="5" HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -1441,16 +1464,12 @@
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>
</Border>
<!-- ═══════════════════════════════════════════════════════════ --> <!-- ─── STEP 2 : Mount & Verify ISO ──────────────────── -->
<!-- STEP 2 : Mount & Verify ISO --> <Grid Name="WPFWin11ISOMountSection"
<!-- ═══════════════════════════════════════════════════════════ --> Margin="5"
<Border Grid.Row="1" Visibility="Collapsed"
Name="WPFWin11ISOMountSection" HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
Style="{StaticResource BorderStyle}"
Visibility="Collapsed">
<Grid Margin="5">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -1472,6 +1491,13 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
Width="Auto" Padding="12,0" Width="Auto" Padding="12,0"
Height="{DynamicResource ButtonHeight}"/> Height="{DynamicResource ButtonHeight}"/>
<CheckBox Name="WPFWin11ISOInjectDrivers"
Content="Inject current system drivers"
FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}"
IsChecked="False"
Margin="0,8,0,0"
ToolTip="Exports all drivers from this machine and injects them into install.wim and boot.wim. Recommended for systems with unsupported NVMe or network controllers."/>
</StackPanel> </StackPanel>
<!-- Verification results panel --> <!-- Verification results panel -->
@@ -1500,22 +1526,17 @@
FontSize="{DynamicResource FontSize}" FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}" Foreground="{DynamicResource MainForegroundColor}"
Background="{DynamicResource MainBackgroundColor}" Background="{DynamicResource MainBackgroundColor}"
BorderBrush="{DynamicResource BorderColor}" HorizontalAlignment="Left"
HorizontalAlignment="Stretch"
Margin="0,0,0,0"/> Margin="0,0,0,0"/>
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>
</Border>
<!-- ═══════════════════════════════════════════════════════════ --> <!-- ─── STEP 3 : Modify install.wim ───────────────────── -->
<!-- STEP 3 : Modify install.wim --> <StackPanel Name="WPFWin11ISOModifySection"
<!-- ═══════════════════════════════════════════════════════════ --> Margin="5"
<Border Grid.Row="2" Visibility="Collapsed"
Name="WPFWin11ISOModifySection" HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
Style="{StaticResource BorderStyle}"
Visibility="Collapsed">
<StackPanel Margin="5">
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold" <TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8"> Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8">
Step 3 - Modify install.wim Step 3 - Modify install.wim
@@ -1534,15 +1555,12 @@
Width="Auto" Padding="12,0" Width="Auto" Padding="12,0"
Height="{DynamicResource ButtonHeight}"/> Height="{DynamicResource ButtonHeight}"/>
</StackPanel> </StackPanel>
</Border>
<!-- ═══════════════════════════════════════════════════════════ --> <!-- ─── STEP 4 : Output Options ───────────────────────── -->
<!-- STEP 4 : Output Options --> <StackPanel Name="WPFWin11ISOOutputSection"
<!-- ═══════════════════════════════════════════════════════════ --> Margin="5"
<Border Grid.Row="3" Visibility="Collapsed"
Name="WPFWin11ISOOutputSection" HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
Style="{StaticResource BorderStyle}">
<StackPanel Margin="5">
<!-- Header row: title + Clean & Reset button --> <!-- Header row: title + Clean & Reset button -->
<Grid Margin="0,0,0,12"> <Grid Margin="0,0,0,12">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -1579,7 +1597,7 @@
Height="{DynamicResource ButtonHeight}"/> Height="{DynamicResource ButtonHeight}"/>
<Button Grid.Column="2" <Button Grid.Column="2"
Name="WPFWin11ISOChooseUSBButton" Name="WPFWin11ISOChooseUSBButton"
Content="Write Directly to a USB Drive (erases drive)" Content="Write Directly to a USB Drive (ERASES DRIVE)"
Foreground="OrangeRed" Foreground="OrangeRed"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Width="Auto" Padding="12,0" Width="Auto" Padding="12,0"
@@ -1613,7 +1631,7 @@
Margin="0,0,6,0"/> Margin="0,0,6,0"/>
<Button Grid.Column="1" <Button Grid.Column="1"
Name="WPFWin11ISORefreshUSBButton" Name="WPFWin11ISORefreshUSBButton"
Content="Refresh" Content="Refresh"
Width="Auto" Padding="8,0" Width="Auto" Padding="8,0"
Height="{DynamicResource ButtonHeight}"/> Height="{DynamicResource ButtonHeight}"/>
</Grid> </Grid>
@@ -1626,34 +1644,37 @@
Margin="0,0,0,10"/> Margin="0,0,0,10"/>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>
</Border>
<!-- ═══════════════════════════════════════════════════════════ --> </StackPanel>
<!-- Status / Log Output -->
<!-- ═══════════════════════════════════════════════════════════ -->
<Border Grid.Row="4" Style="{StaticResource BorderStyle}">
<StackPanel>
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,6">
Status Log
</TextBlock>
<TextBox Name="WPFWin11ISOStatusLog"
IsReadOnly="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Height="140" Padding="6"
Background="{DynamicResource MainBackgroundColor}"
Foreground="{DynamicResource MainForegroundColor}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Text="Ready. Please select a Windows 11 ISO to begin."/>
</StackPanel>
</Border>
<!-- Status Log (fills remaining height) -->
<Grid Grid.Row="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}"
Margin="0,0,0,4">
Status Log
</TextBlock>
<TextBox Grid.Row="1"
Name="WPFWin11ISOStatusLog"
IsReadOnly="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Visible"
VerticalAlignment="Stretch"
Padding="6"
Background="{DynamicResource MainBackgroundColor}"
Foreground="{DynamicResource MainForegroundColor}"
BorderBrush="{DynamicResource BorderColor}"
BorderThickness="1"
Text="Ready. Please select a Windows 11 ISO to begin."/>
</Grid> </Grid>
</ScrollViewer>
</Grid>
</TabItem> </TabItem>
</TabControl> </TabControl>
</Grid> </Grid>