Compare commits

...

7 Commits

Author SHA1 Message Date
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
8 changed files with 67 additions and 106 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

@@ -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,29 +148,38 @@ function Invoke-WinUtilISOWriteUSB {
@( @(
"select disk $diskNum" "select disk $diskNum"
$createPartitionCommand $createPartitionCommand
"select partition 1"
"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." }
@@ -167,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

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

@@ -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,6 +28,11 @@ if ($Noui) {
$PARAM_NOUI = $true $PARAM_NOUI = $true
} }
$PARAM_OFFLINE = $false
if ($Offline) {
$PARAM_OFFLINE = $true
}
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)) {
Write-Output "Winutil needs to be run as Administrator. Attempting to relaunch." Write-Output "Winutil needs to be run as Administrator. Attempting to relaunch."

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>Never</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" >