11use crate :: writer:: { FlashOptions , FlashProgress , FlashResult } ;
22#[ cfg( target_os = "windows" ) ]
33use std:: process:: { Command , Stdio } ;
4+ #[ cfg( target_os = "windows" ) ]
5+ use std:: os:: windows:: process:: CommandExt ;
46use std:: sync:: atomic:: { AtomicBool , Ordering } ;
57use std:: time:: Instant ;
68use tauri:: AppHandle ;
79use tauri:: Emitter ;
810
11+ // CREATE_NO_WINDOW: prevents PowerShell/diskpart windows from popping up
12+ #[ cfg( target_os = "windows" ) ]
13+ const CREATE_NO_WINDOW : u32 = 0x08000000 ;
14+
915static CANCEL_FLAG : AtomicBool = AtomicBool :: new ( false ) ;
1016
1117pub fn cancel_flash ( ) {
@@ -163,6 +169,7 @@ fn mount_iso(iso_path: &str) -> Result<String, String> {
163169
164170 let output = Command :: new ( "powershell" )
165171 . args ( [ "-NoProfile" , "-Command" , & script] )
172+ . creation_flags ( CREATE_NO_WINDOW )
166173 . output ( )
167174 . map_err ( |e| format ! ( "Failed to execute Mount PowerShell: {}" , e) ) ?;
168175
@@ -186,6 +193,7 @@ fn unmount_iso(iso_path: &str) -> Result<(), String> {
186193 let script = format ! ( r#"Dismount-DiskImage -ImagePath "{}""# , iso_path) ;
187194 Command :: new ( "powershell" )
188195 . args ( [ "-NoProfile" , "-Command" , & script] )
196+ . creation_flags ( CREATE_NO_WINDOW )
189197 . output ( )
190198 . map_err ( |e| format ! ( "Failed to dismount ISO: {}" , e) ) ?;
191199 Ok ( ( ) )
@@ -207,113 +215,125 @@ fn format_usb_drive(disk_number: u32, options: &FlashOptions) -> Result<(String,
207215 let is_gpt = options. partition_scheme . as_deref ( ) . unwrap_or ( "mbr" ) . eq_ignore_ascii_case ( "gpt" ) ;
208216 let label = options. volume_label . as_deref ( ) . unwrap_or ( "BOOTISO" ) ;
209217
218+ // Create a temporary file for the diskpart script
219+ let temp_dir = std:: env:: temp_dir ( ) ;
220+ let script_path = temp_dir. join ( format ! ( "bootiso_diskpart_{}.txt" , disk_number) ) ;
221+
210222 if is_gpt {
211- // ===== GPT: Use PowerShell for reliable GPT partitioning =====
212- let ps_script = format ! (
213- r#"
214- $ErrorActionPreference = 'Stop'
215- # 1. Clean and initialize disk as GPT
216- Clear-Disk -Number {disk} -RemoveData -RemoveOEM -Confirm:$false -ErrorAction SilentlyContinue
217- Initialize-Disk -Number {disk} -PartitionStyle GPT -Confirm:$false -ErrorAction SilentlyContinue
218-
219- # 2. Create main partition (leave 1MB at end for UEFI:NTFS)
220- $mainPart = New-Partition -DiskNumber {disk} -UseMaximumSize -AssignDriveLetter
221-
222- while (!(Get-Volume -DriveLetter $mainPart.DriveLetter -ErrorAction SilentlyContinue)) {{
223- Start-Sleep -Milliseconds 500
224- }}
225- Format-Volume -Partition $mainPart -FileSystem {fs} -NewFileSystemLabel "{label}" -Confirm:$false | Out-Null
226-
227- # 3. Shrink main partition by 1MB to make room for UEFI:NTFS
228- $newSize = $mainPart.Size - 1MB
229- Resize-Partition -DiskNumber {disk} -PartitionNumber $mainPart.PartitionNumber -Size $newSize
230-
231- # 4. Create small FAT partition in the freed space
232- $fatPart = New-Partition -DiskNumber {disk} -UseMaximumSize -AssignDriveLetter
233-
234- # 5. Wait for the volume to be available before formatting to avoid race conditions
235- while (!(Get-Volume -DriveLetter $fatPart.DriveLetter -ErrorAction SilentlyContinue)) {{
236- Start-Sleep -Milliseconds 500
237- }}
238- Format-Volume -Partition $fatPart -FileSystem FAT -NewFileSystemLabel "UEFI_NTFS" -Confirm:$false | Out-Null
239-
240- # 6. Output both drive letters (main first, fat second)
241- Write-Output $mainPart.DriveLetter
242- Write-Output $fatPart.DriveLetter
243- "# ,
244- disk = disk_number,
245- fs = fs_cmd,
246- label = label
223+ let diskpart_script = format ! (
224+ "select disk {}\n clean\n convert gpt\n create partition primary\n shrink desired=2 minimum=2\n format fs={} label=\" {}\" quick\n assign\n create partition primary\n format fs=fat label=\" UEFI_NTFS\" quick\n assign\n exit\n " ,
225+ disk_number, fs_cmd, label
247226 ) ;
248227
249- let output = Command :: new ( "powershell" )
250- . args ( [ "-NoProfile" , "-Command" , & ps_script] )
228+ std:: fs:: write ( & script_path, diskpart_script)
229+ . map_err ( |e| format ! ( "Failed to write diskpart script: {}" , e) ) ?;
230+
231+ let output = Command :: new ( "diskpart" )
232+ . args ( [ "/s" , script_path. to_str ( ) . unwrap ( ) ] )
233+ . creation_flags ( CREATE_NO_WINDOW )
251234 . output ( )
252- . map_err ( |e| format ! ( "Failed to execute PowerShell GPT format: {}" , e) ) ?;
235+ . map_err ( |e| format ! ( "Failed to execute diskpart (GPT): {}" , e) ) ?;
236+
237+ let _ = std:: fs:: remove_file ( & script_path) ;
253238
254239 if !output. status . success ( ) {
255240 return Err ( format ! (
256- "Failed to format USB drive (GPT) via PowerShell : {}" ,
257- String :: from_utf8_lossy( & output. stderr )
241+ "Diskpart GPT format failed : {}" ,
242+ String :: from_utf8_lossy( & output. stdout ) // diskpart usually outputs errors to stdout
258243 ) ) ;
259244 }
260245
261- let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
262- let lines: Vec < & str > = stdout. trim ( ) . lines ( ) . collect ( ) ;
263- if lines. len ( ) >= 2 {
264- let main_letter = lines[ lines. len ( ) - 2 ] . trim ( ) . to_string ( ) ;
265- let fat_letter = lines[ lines. len ( ) - 1 ] . trim ( ) . to_string ( ) ;
246+ // Retrieve the two assigned drive letters using PowerShell since diskpart output parsing is frail
247+ // We look for volumes on this specific disk
248+ let get_letters_script = format ! (
249+ r#"
250+ $letters = Get-Partition -DiskNumber {} | Where-Object DriveLetter | Select-Object -ExpandProperty DriveLetter
251+ $letters -join "`n"
252+ "# ,
253+ disk_number
254+ ) ;
255+
256+ let letters_output = Command :: new ( "powershell" )
257+ . args ( [ "-NoProfile" , "-Command" , & get_letters_script] )
258+ . creation_flags ( CREATE_NO_WINDOW )
259+ . output ( )
260+ . map_err ( |e| format ! ( "Failed to get drive letters: {}" , e) ) ?;
261+
262+ let letters_str = String :: from_utf8_lossy ( & letters_output. stdout ) ;
263+ let mut letters: Vec < & str > = letters_str. trim ( ) . lines ( ) . collect ( ) ;
264+ letters. sort ( ) ; // Main partition is usually larger/first, but let's assume alphabetical if we must, or we can check labels.
265+
266+ // Better: get letters by Label to be 100% sure
267+ let get_letters_by_label = format ! (
268+ r#"
269+ $main = Get-Partition -DiskNumber {0} | Get-Volume | Where-Object FileSystemLabel -eq '{1}' | Select-Object -ExpandProperty DriveLetter
270+ $fat = Get-Partition -DiskNumber {0} | Get-Volume | Where-Object FileSystemLabel -eq 'UEFI_NTFS' | Select-Object -ExpandProperty DriveLetter
271+ Write-Output $main
272+ Write-Output $fat
273+ "# ,
274+ disk_number, label
275+ ) ;
276+
277+ let precise_output = Command :: new ( "powershell" )
278+ . args ( [ "-NoProfile" , "-Command" , & get_letters_by_label] )
279+ . creation_flags ( CREATE_NO_WINDOW )
280+ . output ( )
281+ . map_err ( |e| format ! ( "Failed to get precise drive letters: {}" , e) ) ?;
282+
283+ let precise_str = String :: from_utf8_lossy ( & precise_output. stdout ) ;
284+ let precise_lines: Vec < & str > = precise_str. trim ( ) . lines ( ) . collect ( ) ;
285+
286+ if precise_lines. len ( ) >= 2 {
287+ let main_letter = precise_lines[ 0 ] . trim ( ) . to_string ( ) ;
288+ let fat_letter = precise_lines[ 1 ] . trim ( ) . to_string ( ) ;
266289 Ok ( ( format ! ( "{}:\\ " , main_letter) , Some ( format ! ( "{}:\\ " , fat_letter) ) ) )
267290 } else {
268291 Err ( format ! (
269- "GPT format succeeded but could not find two drive letters. Output: {}" ,
270- stdout
292+ "GPT format succeeded but could not verify both partition letters via WMI . Output: {}" ,
293+ precise_str
271294 ) )
272295 }
296+
273297 } else {
274- // ===== MBR: Use PowerShell (more reliable than diskpart) =====
275- let ps_script = format ! (
276- r#"
277- $ErrorActionPreference = 'Stop'
278- # 1. Clean disk
279- Clear-Disk -Number {disk} -RemoveData -RemoveOEM -Confirm:$false -ErrorAction SilentlyContinue
280-
281- # 2. Force MBR partition style
282- Set-Disk -Number {disk} -PartitionStyle MBR -ErrorAction SilentlyContinue
283- Initialize-Disk -Number {disk} -PartitionStyle MBR -ErrorAction SilentlyContinue
284-
285- # 3. Create main partition and make it active
286- $mainPart = New-Partition -DiskNumber {disk} -UseMaximumSize -IsActive -AssignDriveLetter
287- Format-Volume -Partition $mainPart -FileSystem {fs} -NewFileSystemLabel "{label}" -Confirm:$false | Out-Null
288-
289- # 4. Output drive letter
290- Write-Output $mainPart.DriveLetter
291- "# ,
292- disk = disk_number,
293- fs = fs_cmd,
294- label = label
298+ // ===== MBR: Use diskpart =====
299+ let diskpart_script = format ! (
300+ "select disk {}\n clean\n convert mbr\n create partition primary\n active\n format fs={} label=\" {}\" quick\n assign\n exit\n " ,
301+ disk_number, fs_cmd, label
295302 ) ;
296303
297- let output = Command :: new ( "powershell" )
298- . args ( [ "-NoProfile" , "-Command" , & ps_script] )
304+ std:: fs:: write ( & script_path, diskpart_script)
305+ . map_err ( |e| format ! ( "Failed to write diskpart script: {}" , e) ) ?;
306+
307+ let output = Command :: new ( "diskpart" )
308+ . args ( [ "/s" , script_path. to_str ( ) . unwrap ( ) ] )
309+ . creation_flags ( CREATE_NO_WINDOW )
299310 . output ( )
300- . map_err ( |e| format ! ( "Failed to execute PowerShell MBR format: {}" , e) ) ?;
311+ . map_err ( |e| format ! ( "Failed to execute diskpart (MBR): {}" , e) ) ?;
312+
313+ let _ = std:: fs:: remove_file ( & script_path) ;
301314
302315 if !output. status . success ( ) {
303316 return Err ( format ! (
304- "Failed to format USB drive (MBR) via PowerShell : {}" ,
305- String :: from_utf8_lossy( & output. stderr )
317+ "Diskpart MBR format failed : {}" ,
318+ String :: from_utf8_lossy( & output. stdout )
306319 ) ) ;
307320 }
308321
309- let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
310- let letter = stdout. trim ( ) . to_string ( ) ;
322+ let get_letter_script = format ! (
323+ "(Get-Partition -DiskNumber {} | Where-Object DriveLetter | Select-Object -First 1).DriveLetter" ,
324+ disk_number
325+ ) ;
326+
327+ let letter_output = Command :: new ( "powershell" )
328+ . args ( [ "-NoProfile" , "-Command" , & get_letter_script] )
329+ . creation_flags ( CREATE_NO_WINDOW )
330+ . output ( )
331+ . map_err ( |e| format ! ( "Failed to get MBR drive letter: {}" , e) ) ?;
332+
333+ let letter = String :: from_utf8_lossy ( & letter_output. stdout ) . trim ( ) . to_string ( ) ;
311334
312335 if letter. is_empty ( ) {
313- return Err ( format ! (
314- "MBR format succeeded but could not find drive letter. Output: {}" ,
315- stdout
316- ) ) ;
336+ return Err ( "MBR format succeeded but could not find drive letter." . to_string ( ) ) ;
317337 }
318338
319339 Ok ( ( format ! ( "{}:\\ " , letter) , None ) )
@@ -435,6 +455,7 @@ fn copy_files_robocopy(app: &AppHandle, source: &str, dest: &str, total_bytes: u
435455 // We use /E instead of /MIR. We removed /NP to allow percentage tracking.
436456 let mut child = Command :: new ( "robocopy" )
437457 . args ( [ source, dest, "/E" , "/MT:16" , "/J" , "/R:0" , "/W:0" , "/BYTES" , "/NDL" , "/NJH" , "/NJS" , "/FP" ] )
458+ . creation_flags ( CREATE_NO_WINDOW )
438459 . stdout ( Stdio :: piped ( ) )
439460 . spawn ( )
440461 . map_err ( |e| format ! ( "Failed to start robocopy: {}" , e) ) ?;
@@ -569,6 +590,7 @@ fn install_bootloader(iso_letter: &str, usb_letter: &str) -> Result<(), String>
569590
570591 let output = Command :: new ( & bootsect_path)
571592 . args ( [ "/nt60" , & target, "/force" , "/mbr" ] )
593+ . creation_flags ( CREATE_NO_WINDOW )
572594 . output ( ) ;
573595
574596 match output {
0 commit comments