Compare commits

..

13 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
17 changed files with 342 additions and 151 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

@@ -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
--- ---

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
}
}
} }
} }
} }

View File

@@ -103,13 +103,24 @@ function Invoke-WinUtilISOWriteUSB {
try { try {
SetProgress "Formatting USB drive..." 10 SetProgress "Formatting USB drive..." 10
# Phase 1: Clean disk via diskpart # 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" $dpFile1 = Join-Path $env:TEMP "winutil_diskpart_$(Get-Random).txt"
"select disk $diskNum`nclean`nexit" | Set-Content -Path $dpFile1 -Encoding ASCII "select disk $diskNum`nclean`nexit" | Set-Content -Path $dpFile1 -Encoding ASCII
Log "Running diskpart clean on Disk $diskNum..." Log "Running diskpart clean on Disk $diskNum..."
diskpart /s $dpFile1 2>&1 | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" } $dpCleanOut = diskpart /s $dpFile1 2>&1
$dpCleanOut | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" }
Remove-Item $dpFile1 -Force -ErrorAction SilentlyContinue 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 # Phase 2: Initialize as GPT
Start-Sleep -Seconds 2 Start-Sleep -Seconds 2
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
@@ -122,7 +133,8 @@ function Invoke-WinUtilISOWriteUSB {
Log "Disk $diskNum converted to GPT (was $($diskObj.PartitionStyle))." Log "Disk $diskNum converted to GPT (was $($diskObj.PartitionStyle))."
} }
# Phase 3: Create FAT32 partition via diskpart # 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') $volLabel = "W11-" + (Get-Date).ToString('yyMMdd')
$dpFile2 = Join-Path $env:TEMP "winutil_diskpart2_$(Get-Random).txt" $dpFile2 = Join-Path $env:TEMP "winutil_diskpart2_$(Get-Random).txt"
$maxFat32PartitionMB = 32768 $maxFat32PartitionMB = 32768
@@ -136,28 +148,38 @@ function Invoke-WinUtilISOWriteUSB {
@( @(
"select disk $diskNum" "select disk $diskNum"
$createPartitionCommand $createPartitionCommand
"format quick fs=fat32 label=`"$volLabel`""
"exit" "exit"
) | Set-Content -Path $dpFile2 -Encoding ASCII ) | Set-Content -Path $dpFile2 -Encoding ASCII
Log "Creating partitions on Disk $diskNum..." Log "Creating partitions on Disk $diskNum..."
diskpart /s $dpFile2 2>&1 | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" } diskpart /s $dpFile2 2>&1 | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" }
Remove-Item $dpFile2 -Force -ErrorAction SilentlyContinue Remove-Item $dpFile2 -Force -ErrorAction SilentlyContinue
SetProgress "Assigning drive letters..." 30 SetProgress "Formatting USB partition..." 25
Start-Sleep -Seconds 3 Start-Sleep -Seconds 3
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
$partitions = Get-Partition -DiskNumber $diskNum -ErrorAction Stop $partitions = Get-Partition -DiskNumber $diskNum -ErrorAction Stop
Log "Partitions on Disk $diskNum after format: $($partitions.Count)" Log "Partitions on Disk $diskNum after creation: $($partitions.Count)"
foreach ($p in $partitions) { foreach ($p in $partitions) {
Log " Partition $($p.PartitionNumber) Type=$($p.Type) Letter=$($p.DriveLetter) Size=$([math]::Round($p.Size/1MB))MB" 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 $winpePart = $partitions | Where-Object { $_.Type -eq "Basic" } | Select-Object -Last 1
if (-not $winpePart) { if (-not $winpePart) {
throw "Could not find the WINPE (Basic) partition on Disk $diskNum after format." 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 {} try { Remove-PartitionAccessPath -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -AccessPath "$($winpePart.DriveLetter):" -ErrorAction SilentlyContinue } catch {}
$usbLetter = Get-FreeDriveLetter $usbLetter = Get-FreeDriveLetter
if (-not $usbLetter) { throw "No free drive letters (D-Z) available to assign to the USB data partition." } if (-not $usbLetter) { throw "No free drive letters (D-Z) available to assign to the USB data partition." }
@@ -166,6 +188,12 @@ function Invoke-WinUtilISOWriteUSB {
Start-Sleep -Seconds 2 Start-Sleep -Seconds 2
$usbDrive = "${usbLetter}:" $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." } if (-not (Test-Path $usbDrive)) { throw "Drive $usbDrive is not accessible after letter assignment." }
Log "USB data partition: $usbDrive" Log "USB data partition: $usbDrive"

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"
if ($_.Exception) {
if (-not [string]::IsNullOrWhiteSpace($_.Exception.Message)) {
Write-Warning $_.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

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

@@ -943,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 -->
@@ -1192,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" >