mirror of
https://github.com/ChrisTitusTech/winutil
synced 2026-04-06 14:48:31 +00:00
Compare commits
14 Commits
7ceb303f00
...
nullsafe-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b481064437 | ||
|
|
d2dbf03572 | ||
|
|
88c68fd411 | ||
|
|
e82aaf03f0 | ||
|
|
9a77fed09b | ||
|
|
336ac6fb8f | ||
|
|
72ad35bbd8 | ||
|
|
df17ca4695 | ||
|
|
7f46e8d60d | ||
|
|
9769cafa7d | ||
|
|
42dfc8c82b | ||
|
|
d13295bdd8 | ||
|
|
5fc566b46f | ||
|
|
b493737982 |
32
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
32
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -6,9 +6,6 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
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**
|
||||
- 🛠️ **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).
|
||||
@@ -24,11 +21,16 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
- type: dropdown
|
||||
id: affected_part
|
||||
attributes:
|
||||
label: 📜 What part of Winutil are you having issues with?
|
||||
placeholder: "e.g., Tweaks, etc."
|
||||
label: What part of Winutil are you having issues with?
|
||||
options:
|
||||
- Program Install Tab
|
||||
- Tweaks Tab
|
||||
- Config Tab
|
||||
- Updates Tab
|
||||
- Win11 Creator Tab
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -39,24 +41,8 @@ body:
|
||||
validations:
|
||||
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
|
||||
id: error_output
|
||||
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."
|
||||
|
||||
- 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
|
||||
|
||||
37
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
37
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@@ -13,45 +13,10 @@ body:
|
||||
- 🛠️ **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).
|
||||
|
||||
- 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
|
||||
id: proposed_solution
|
||||
attributes:
|
||||
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:
|
||||
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
|
||||
|
||||
@@ -128,6 +128,154 @@ winutil/
|
||||
- CheckBoxes for options
|
||||
- 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
|
||||
|
||||
### Application Installation Flow
|
||||
@@ -514,6 +662,7 @@ Outputs `winutil.ps1` in the root directory.
|
||||
|
||||
- [Contributing Guide](../../contributing/) - How to contribute code
|
||||
- [User Guide](../../userguide/) - End-user documentation
|
||||
- [Win11 Creator Guide](../../userguide/win11Creator/) - Building customized Windows 11 ISOs
|
||||
- [FAQ](../../faq/) - Common questions
|
||||
|
||||
## Additional Resources
|
||||
|
||||
@@ -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.
|
||||
|
||||
> [!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 ~10–15 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 ~10–15 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.
|
||||
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.
|
||||
|
||||
---
|
||||
@@ -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:
|
||||
|
||||
**App & Component Removal:**
|
||||
- **Remove 40+ bloat apps** — Clipchamp, Teams, Copilot, Dev Home, new Outlook, Bing apps, Solitaire, and more
|
||||
- **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
|
||||
- **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 1–2 GB per removed edition
|
||||
- **Clean the component store** — runs DISM cleanup to reclaim another 300–800 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
|
||||
- **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 **10–30 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 hardware checks** — installs on machines without TPM 2.0, Secure Boot, or supported CPUs
|
||||
- **Dark mode enabled by default**
|
||||
- **Empty taskbar and Start Menu** — no pinned apps
|
||||
- **Windows Update re-enabled automatically** after first login (it's paused during OOBE to prevent interruption)
|
||||
- **BitLocker disabled**, Recall disabled, desktop shortcuts removed
|
||||
- **Empty taskbar and Start Menu** — no pinned apps, Chat icon removed
|
||||
- **Windows Update disabled during OOBE** — automatically re-enabled on first login to prevent setup interruptions
|
||||
- **BitLocker disabled** — removes startup overhead on first boot
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,13 +16,20 @@ function Invoke-WinUtilFeatureInstall {
|
||||
Write-Host "Installing $feature"
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName $feature -All -NoRestart
|
||||
} 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?"
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" }
|
||||
} else {
|
||||
|
||||
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"
|
||||
Invoke-Command $scriptblock -ErrorAction stop
|
||||
} 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?"
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" }
|
||||
} else {
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" }
|
||||
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
@@ -4,50 +4,38 @@ function Invoke-WinUtilISOScript {
|
||||
Applies WinUtil modifications to a mounted Windows 11 install.wim image.
|
||||
|
||||
.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.
|
||||
2. Removes OneDriveSetup.exe from the system image.
|
||||
3. Loads offline registry hives (COMPONENTS, DEFAULT, NTUSER, SOFTWARE, SYSTEM)
|
||||
and applies the following tweaks:
|
||||
- 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).
|
||||
All setup scripts embedded in the autounattend.xml <Extensions><File> nodes are
|
||||
written directly into the WIM at their target paths under C:\Windows\Setup\Scripts\
|
||||
to ensure they survive Windows Setup stripping unrecognised-namespace XML elements
|
||||
from the Panther copy of the answer file.
|
||||
|
||||
Mounting and dismounting the WIM is the responsibility of the caller
|
||||
(e.g. Invoke-WinUtilISO).
|
||||
Mounting/dismounting the WIM is the caller's responsibility (e.g. Invoke-WinUtilISO).
|
||||
|
||||
.PARAMETER ScratchDir
|
||||
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
|
||||
Optional. Root directory of the extracted ISO contents.
|
||||
When supplied, autounattend.xml is also written here so Windows Setup picks it
|
||||
up automatically at boot, and the support\ folder is deleted from that location.
|
||||
Optional. Root directory of the extracted ISO contents. When supplied,
|
||||
autounattend.xml is written here and the support\ folder is removed.
|
||||
|
||||
.PARAMETER AutoUnattendXml
|
||||
Optional. Full XML content for autounattend.xml.
|
||||
In compiled winutil.ps1 this is the embedded $WinUtilAutounattendXml here-string;
|
||||
in dev mode it is read from tools\autounattend.xml.
|
||||
If empty, the OOBE bypass file is skipped and a warning is logged.
|
||||
Optional. Full XML content for 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
|
||||
Optional ScriptBlock used for progress/status logging.
|
||||
Receives a single [string] message argument.
|
||||
Defaults to { param($m) Write-Output $m } when not supplied.
|
||||
Optional ScriptBlock for progress/status logging. Receives a single [string] argument.
|
||||
|
||||
.EXAMPLE
|
||||
Invoke-WinUtilISOScript -ScratchDir "C:\Temp\wim_mount"
|
||||
@@ -62,24 +50,19 @@ function Invoke-WinUtilISOScript {
|
||||
.NOTES
|
||||
Author : Chris Titus @christitustech
|
||||
GitHub : https://github.com/ChrisTitusTech
|
||||
Version : 26.02.22
|
||||
Version : 26.03.02
|
||||
#>
|
||||
param (
|
||||
[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 = "",
|
||||
# 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 = "",
|
||||
[bool]$InjectCurrentSystemDrivers = $false,
|
||||
[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')
|
||||
$adminGroup = $adminSID.Translate([System.Security.Principal.NTAccount])
|
||||
|
||||
# ── Local helpers ─────────────────────────────────────────────────────────
|
||||
function Set-ISOScriptReg {
|
||||
param ([string]$path, [string]$name, [string]$type, [string]$value)
|
||||
try {
|
||||
@@ -100,15 +83,37 @@ function Invoke-WinUtilISOScript {
|
||||
}
|
||||
}
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# 1. Remove provisioned AppX packages
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
function Add-DriversToImage {
|
||||
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..."
|
||||
|
||||
$packages = & dism /English "/image:$ScratchDir" /Get-ProvisionedAppxPackages |
|
||||
ForEach-Object {
|
||||
if ($_ -match 'PackageName : (.*)') { $matches[1] }
|
||||
}
|
||||
ForEach-Object { if ($_ -match 'PackageName : (.*)') { $matches[1] } }
|
||||
|
||||
$packagePrefixes = @(
|
||||
'AppUp.IntelManagementandSecurityStatus',
|
||||
@@ -155,25 +160,46 @@ function Invoke-WinUtilISOScript {
|
||||
'MicrosoftTeams'
|
||||
)
|
||||
|
||||
$packagesToRemove = $packages | Where-Object {
|
||||
$pkg = $_
|
||||
$packagePrefixes | Where-Object { $pkg -like "*$_*" }
|
||||
}
|
||||
foreach ($package in $packagesToRemove) {
|
||||
& dism /English "/image:$ScratchDir" /Remove-ProvisionedAppxPackage "/PackageName:$package"
|
||||
$packages | Where-Object { $pkg = $_; $packagePrefixes | Where-Object { $pkg -like "*$_*" } } |
|
||||
ForEach-Object { & dism /English "/image:$ScratchDir" /Remove-ProvisionedAppxPackage "/PackageName:$_" }
|
||||
|
||||
# ── 2. Inject current system drivers (optional) ───────────────────────────
|
||||
if ($InjectCurrentSystemDrivers) {
|
||||
& $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."
|
||||
}
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# 2. Remove OneDrive
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# ── 3. Remove OneDrive ────────────────────────────────────────────────────
|
||||
& $Log "Removing OneDrive..."
|
||||
& takeown /f "$ScratchDir\Windows\System32\OneDriveSetup.exe" | 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
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# 3. Registry tweaks
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# ── 4. Registry tweaks ────────────────────────────────────────────────────
|
||||
& $Log "Loading offline registry hives..."
|
||||
reg load HKLM\zCOMPONENTS "$ScratchDir\Windows\System32\config\COMPONENTS"
|
||||
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'
|
||||
|
||||
if ($AutoUnattendXml) {
|
||||
# ── Place autounattend.xml inside the WIM (Sysprep) ──────────────────
|
||||
$sysprepDest = "$ScratchDir\Windows\System32\Sysprep\autounattend.xml"
|
||||
Set-Content -Path $sysprepDest -Value $AutoUnattendXml -Encoding UTF8 -Force
|
||||
& $Log "Written autounattend.xml to Sysprep directory."
|
||||
try {
|
||||
$xmlDoc = [xml]::new()
|
||||
$xmlDoc.LoadXml($AutoUnattendXml)
|
||||
|
||||
$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)) {
|
||||
$isoDest = Join-Path $ISOContentsDir "autounattend.xml"
|
||||
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'
|
||||
|
||||
& $Log "Disabling Copilot..."
|
||||
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\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1'
|
||||
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0'
|
||||
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)..."
|
||||
@@ -304,12 +353,9 @@ function Invoke-WinUtilISOScript {
|
||||
reg unload HKLM\zSOFTWARE
|
||||
reg unload HKLM\zSYSTEM
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# 4. Delete scheduled task definition files
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# ── 5. Delete scheduled task definition files ─────────────────────────────
|
||||
& $Log "Deleting scheduled task definition files..."
|
||||
$tasksPath = "$ScratchDir\Windows\System32\Tasks"
|
||||
|
||||
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\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\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "$tasksPath\Microsoft\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
& $Log "Scheduled task files deleted."
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# 5. Remove ISO support folder (fresh-install only; not needed)
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# ── 6. Remove ISO support folder ─────────────────────────────────────────
|
||||
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||
& $Log "Removing ISO support\ folder..."
|
||||
Remove-Item -Path (Join-Path $ISOContentsDir "support") -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
268
functions/private/Invoke-WinUtilISOUSB.ps1
Normal file
268
functions/private/Invoke-WinUtilISOUSB.ps1
Normal 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
|
||||
}
|
||||
@@ -25,20 +25,35 @@ function Invoke-WinUtilScript {
|
||||
Invoke-Command $scriptblock -ErrorAction Stop
|
||||
} catch [System.Management.Automation.CommandNotFoundException] {
|
||||
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] {
|
||||
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] {
|
||||
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] {
|
||||
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 {
|
||||
# Generic catch block to handle any other type of 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,13 @@ function Set-WinUtilDNS {
|
||||
}
|
||||
} catch {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,22 @@ function Set-WinUtilRegistry {
|
||||
} catch [System.Security.SecurityException] {
|
||||
Write-Warning "Unable to set $Path\$Name to $Value due to a Security Exception"
|
||||
} catch [System.Management.Automation.ItemNotFoundException] {
|
||||
Write-Warning $psitem.Exception.ErrorRecord
|
||||
Write-Warning $_.Exception.ErrorRecord
|
||||
} 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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,23 @@ function Set-WinUtilScheduledTask {
|
||||
Enable-ScheduledTask -TaskName $Name -ErrorAction Stop
|
||||
}
|
||||
} 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"
|
||||
} else {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,14 @@ Function Set-WinUtilService {
|
||||
Write-Warning "Service $Name was not found"
|
||||
} catch {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ function Invoke-WPFFixesWinget {
|
||||
try {
|
||||
Set-WinUtilTaskbaritem -state "Indeterminate" -overlay "logo"
|
||||
Write-Host "==> Starting Winget Repair"
|
||||
Install-WinUtilWinget -Force
|
||||
Install-WinUtilWinget
|
||||
} catch {
|
||||
Write-Error "Failed to install winget: $_"
|
||||
Set-WinUtilTaskbaritem -state "Error" -overlay "warning"
|
||||
|
||||
@@ -15,12 +15,14 @@ $maxthreads = [int]$env:NUMBER_OF_PROCESSORS
|
||||
$hashVars = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync',$sync,$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
|
||||
$offlineVar = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_OFFLINE',$PARAM_OFFLINE,$Null
|
||||
$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
|
||||
|
||||
# Add the variable to the session state
|
||||
$InitialSessionState.Variables.Add($hashVars)
|
||||
$InitialSessionState.Variables.Add($debugVar)
|
||||
$InitialSessionState.Variables.Add($uiVar)
|
||||
$InitialSessionState.Variables.Add($offlineVar)
|
||||
|
||||
# Get every private function and add them to the session state
|
||||
$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."
|
||||
}
|
||||
|
||||
# Check internet connectivity and disable install tab if offline
|
||||
#$isOnline = Test-WinUtilInternetConnection
|
||||
$isOnline = $true # Temporarily force online mode until we can resolve false negatives
|
||||
if ($PARAM_OFFLINE) {
|
||||
# Show offline banner
|
||||
$sync.WPFOfflineBanner.Visibility = [System.Windows.Visibility]::Visible
|
||||
|
||||
if (-not $isOnline) {
|
||||
# Disable the install tab
|
||||
$sync.WPFTab1BT.IsEnabled = $false
|
||||
$sync.WPFTab1BT.Opacity = 0.5
|
||||
@@ -540,6 +541,10 @@ $sync["FontScalingApplyButton"].Add_Click({
|
||||
|
||||
# ── 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({
|
||||
Write-Debug "WPFWin11ISOBrowseButton clicked"
|
||||
Invoke-WinUtilISOBrowse
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
param (
|
||||
[string]$Config,
|
||||
[switch]$Run,
|
||||
[switch]$Noui
|
||||
[switch]$Noui,
|
||||
[switch]$Offline
|
||||
)
|
||||
|
||||
if ($Config) {
|
||||
@@ -27,25 +28,10 @@ if ($Noui) {
|
||||
$PARAM_NOUI = $true
|
||||
}
|
||||
|
||||
# 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
|
||||
$PARAM_OFFLINE = $false
|
||||
if ($Offline) {
|
||||
$PARAM_OFFLINE = $true
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
# 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"
|
||||
|
||||
# Set the path for the winutil directory
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
<settings pass="windowsPE">
|
||||
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||
<UserData>
|
||||
<ProductKey>
|
||||
<Key>00000-00000-00000-00000-00000</Key>
|
||||
<WillShowUI>Always</WillShowUI>
|
||||
</ProductKey>
|
||||
<AcceptEula>true</AcceptEula>
|
||||
</UserData>
|
||||
<UseConfigurationSet>false</UseConfigurationSet>
|
||||
|
||||
@@ -273,22 +273,43 @@
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundColor}" />
|
||||
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundColor}" />
|
||||
<Setter Property="MinWidth" Value="{DynamicResource ButtonWidth}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
<Grid>
|
||||
<ToggleButton x:Name="ToggleButton"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding Background}"
|
||||
BorderThickness="0"
|
||||
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ClickMode="Press">
|
||||
<TextBlock Text="{TemplateBinding SelectionBoxItem}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2"
|
||||
/>
|
||||
</ToggleButton>
|
||||
<Border x:Name="OuterBorder"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{DynamicResource ButtonCornerRadius}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<ToggleButton x:Name="ToggleButton"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ClickMode="Press">
|
||||
<Grid>
|
||||
<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"
|
||||
IsOpen="{TemplateBinding IsDropDownOpen}"
|
||||
Placement="Bottom"
|
||||
@@ -297,11 +318,11 @@
|
||||
PopupAnimation="Slide">
|
||||
<Border x:Name="DropDownBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding Foreground}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<ScrollViewer>
|
||||
<ItemsPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2"/>
|
||||
<ItemsPresenter HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4,2"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
@@ -922,13 +943,19 @@
|
||||
</Window.Resources>
|
||||
<Grid Background="{DynamicResource MainBackgroundColor}" ShowGridLines="False" Name="WPFMainGrid" Width="Auto" Height="Auto" HorizontalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</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="⚠ 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>
|
||||
<ColumnDefinition Width="Auto"/> <!-- Navigation buttons -->
|
||||
<ColumnDefinition Width="*"/> <!-- Search bar and buttons -->
|
||||
@@ -1171,7 +1198,7 @@
|
||||
</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">
|
||||
<Grid Background="Transparent" >
|
||||
|
||||
@@ -1340,21 +1367,17 @@
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="Win11ISO" Visibility="Collapsed" Name="WPFTab5">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="{DynamicResource TabContentMargin}">
|
||||
<Grid Background="Transparent" Name="Win11ISOPanel">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- Step 1: Select ISO -->
|
||||
<RowDefinition Height="Auto"/> <!-- Step 2: Mount & Verify -->
|
||||
<RowDefinition Height="Auto"/> <!-- Step 3: Modify install.wim -->
|
||||
<RowDefinition Height="Auto"/> <!-- Step 4: Output Options -->
|
||||
<RowDefinition Height="Auto"/> <!-- Log / Status -->
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Name="Win11ISOPanel" Margin="{DynamicResource TabContentMargin}" Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- Steps 1-4 -->
|
||||
<RowDefinition Height="*"/> <!-- Log / Status -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- STEP 1 : Select Windows 11 ISO -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<Border Grid.Row="0" Name="WPFWin11ISOSelectSection" Style="{StaticResource BorderStyle}">
|
||||
<Grid Margin="5">
|
||||
<!-- Steps 1-4 -->
|
||||
<StackPanel Grid.Row="0">
|
||||
|
||||
<!-- ─── STEP 1 : Select Windows 11 ISO ─────────────── -->
|
||||
<Grid Name="WPFWin11ISOSelectSection" Margin="5" HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -1441,16 +1464,12 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- STEP 2 : Mount & Verify ISO -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<Border Grid.Row="1"
|
||||
Name="WPFWin11ISOMountSection"
|
||||
Style="{StaticResource BorderStyle}"
|
||||
Visibility="Collapsed">
|
||||
<Grid Margin="5">
|
||||
<!-- ─── STEP 2 : Mount & Verify ISO ──────────────────── -->
|
||||
<Grid Name="WPFWin11ISOMountSection"
|
||||
Margin="5"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -1472,6 +1491,13 @@
|
||||
HorizontalAlignment="Left"
|
||||
Width="Auto" Padding="12,0"
|
||||
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>
|
||||
|
||||
<!-- Verification results panel -->
|
||||
@@ -1500,22 +1526,17 @@
|
||||
FontSize="{DynamicResource FontSize}"
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
Background="{DynamicResource MainBackgroundColor}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- STEP 3 : Modify install.wim -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<Border Grid.Row="2"
|
||||
Name="WPFWin11ISOModifySection"
|
||||
Style="{StaticResource BorderStyle}"
|
||||
Visibility="Collapsed">
|
||||
<StackPanel Margin="5">
|
||||
<!-- ─── STEP 3 : Modify install.wim ───────────────────── -->
|
||||
<StackPanel Name="WPFWin11ISOModifySection"
|
||||
Margin="5"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
|
||||
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8">
|
||||
Step 3 - Modify install.wim
|
||||
@@ -1534,15 +1555,12 @@
|
||||
Width="Auto" Padding="12,0"
|
||||
Height="{DynamicResource ButtonHeight}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- STEP 4 : Output Options -->
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<Border Grid.Row="3"
|
||||
Name="WPFWin11ISOOutputSection"
|
||||
Style="{StaticResource BorderStyle}">
|
||||
<StackPanel Margin="5">
|
||||
<!-- ─── STEP 4 : Output Options ───────────────────────── -->
|
||||
<StackPanel Name="WPFWin11ISOOutputSection"
|
||||
Margin="5"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||
<!-- Header row: title + Clean & Reset button -->
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -1579,7 +1597,7 @@
|
||||
Height="{DynamicResource ButtonHeight}"/>
|
||||
<Button Grid.Column="2"
|
||||
Name="WPFWin11ISOChooseUSBButton"
|
||||
Content="Write Directly to a USB Drive (erases drive)"
|
||||
Content="Write Directly to a USB Drive (ERASES DRIVE)"
|
||||
Foreground="OrangeRed"
|
||||
HorizontalAlignment="Stretch"
|
||||
Width="Auto" Padding="12,0"
|
||||
@@ -1613,7 +1631,7 @@
|
||||
Margin="0,0,6,0"/>
|
||||
<Button Grid.Column="1"
|
||||
Name="WPFWin11ISORefreshUSBButton"
|
||||
Content="↻ Refresh"
|
||||
Content="Refresh"
|
||||
Width="Auto" Padding="8,0"
|
||||
Height="{DynamicResource ButtonHeight}"/>
|
||||
</Grid>
|
||||
@@ -1626,34 +1644,37 @@
|
||||
Margin="0,0,0,10"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||
<!-- 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>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 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>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user