1919
2020from PyQt6 import QtCore , QtWidgets , QtGui
2121
22- VERSION = "V1.0.4 "
22+ VERSION = "V1.1.0 "
2323LOGFILE = Path (os .getenv ("TEMP" ) or tempfile .gettempdir ()) / f"mtkclient_installer_{ VERSION } .log"
2424TIMEOUT_WINGET = 60 * 30
2525TIMEOUT_VS = 60 * 60
@@ -458,28 +458,19 @@ def step_ensure_winget(self) -> Tuple[bool, str]:
458458 self .log_summary .emit ("winget not found. Installing required dependencies...\n " )
459459 temp_dir = Path (os .getenv ("TEMP" ) or tempfile .gettempdir ())
460460
461+
461462 self .log_summary .emit ("Step 1/3: Installing VCLibs dependency...\n " )
462463 vclibs_url = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"
463464 vclibs = temp_dir / "Microsoft.VCLibs.x64.14.00.Desktop.appx"
464465
465- vclibs_url_q = _ps_single_quote_escape (vclibs_url )
466- vclibs_q = _ps_single_quote_escape (str (vclibs ))
467-
468466 def attempt_vclibs_download ():
469- ps_script = f"Invoke-WebRequest -Uri '{ vclibs_url_q } ' -OutFile '{ vclibs_q } ' -UseBasicParsing"
470- encoded_ps = _ps_encode_command (ps_script )
471- res = self ._run_proc (
472- ["powershell" , "-NoProfile" , "-ExecutionPolicy" , "Bypass" , "-EncodedCommand" , encoded_ps ],
473- None , timeout = TIMEOUT_DOWNLOAD
474- )
475- if res .exitcode == 0 and vclibs .exists ():
476- return True , "Downloaded"
477- return False , f"Exit { res .exitcode } "
467+ return self ._download_with_progress (vclibs_url , vclibs , timeout = TIMEOUT_DOWNLOAD )
478468
479469 success , msg = _download_with_retry (attempt_vclibs_download , max_attempts = 3 , base_delay = 1.5 )
480470
481471 if success :
482472 self .log_summary .emit (f"VCLibs downloaded ({ vclibs .stat ().st_size / 1024 :.0f} KB)\n " )
473+ vclibs_q = _ps_single_quote_escape (str (vclibs ))
483474 ps_script = f"Add-AppxPackage -Path '{ vclibs_q } ' -ErrorAction SilentlyContinue"
484475 encoded_ps = _ps_encode_command (ps_script )
485476 res_install = self ._run_proc (
@@ -495,30 +486,21 @@ def attempt_vclibs_download():
495486 except :
496487 pass
497488 else :
498- self .log_summary .emit (f"VCLibs download failed after retries : { msg } (may already be present, continuing)\n " )
489+ self .log_summary .emit (f"VCLibs download failed: { msg } (may already be present, continuing)\n " )
499490
491+
500492 self .log_summary .emit ("Step 2/3: Installing UI.Xaml dependency...\n " )
501493 xaml_url = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx"
502494 xaml = temp_dir / "Microsoft.UI.Xaml.2.8.x64.appx"
503495
504- xaml_url_q = _ps_single_quote_escape (xaml_url )
505- xaml_q = _ps_single_quote_escape (str (xaml ))
506-
507496 def attempt_xaml_download ():
508- ps_script = f"Invoke-WebRequest -Uri '{ xaml_url_q } ' -OutFile '{ xaml_q } ' -UseBasicParsing"
509- encoded_ps = _ps_encode_command (ps_script )
510- res = self ._run_proc (
511- ["powershell" , "-NoProfile" , "-ExecutionPolicy" , "Bypass" , "-EncodedCommand" , encoded_ps ],
512- None , timeout = TIMEOUT_DOWNLOAD
513- )
514- if res .exitcode == 0 and xaml .exists ():
515- return True , "Downloaded"
516- return False , f"Exit { res .exitcode } "
497+ return self ._download_with_progress (xaml_url , xaml , timeout = TIMEOUT_DOWNLOAD )
517498
518499 success , msg = _download_with_retry (attempt_xaml_download , max_attempts = 3 , base_delay = 1.5 )
519500
520501 if success :
521502 self .log_summary .emit (f"UI.Xaml downloaded ({ xaml .stat ().st_size / 1024 :.0f} KB)\n " )
503+ xaml_q = _ps_single_quote_escape (str (xaml ))
522504 ps_script = f"Add-AppxPackage -Path '{ xaml_q } ' -ErrorAction SilentlyContinue"
523505 encoded_ps = _ps_encode_command (ps_script )
524506 res_install = self ._run_proc (
@@ -534,90 +516,142 @@ def attempt_xaml_download():
534516 except :
535517 pass
536518 else :
537- self .log_summary .emit (f"UI.Xaml download failed after retries : { msg } (may already be present, continuing)\n " )
519+ self .log_summary .emit (f"UI.Xaml download failed: { msg } (may already be present, continuing)\n " )
538520
521+
539522 self .log_summary .emit ("Step 3/3: Installing App Installer (winget)...\n " )
540523 msix = temp_dir / "Microsoft.DesktopAppInstaller.msixbundle"
541- url = "https://aka.ms/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
542524
543- msix_q = _ps_single_quote_escape (str (msix ))
544- url_q = _ps_single_quote_escape (url )
525+ urls = [
526+ "https://aka.ms/getwinget" ,
527+ "https://github.com/microsoft/winget-cli/releases/download/v1.7.10861/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" ,
528+ ]
545529
546530 self .log_summary .emit ("Cleaning up any conflicting installations...\n " )
547531 ps_script = "Get-AppxPackage Microsoft.DesktopAppInstaller | Remove-AppxPackage -ErrorAction SilentlyContinue"
548532 encoded_ps = _ps_encode_command (ps_script )
549- cleanup = self ._run_proc (
533+ self ._run_proc (
550534 ["powershell" , "-NoProfile" , "-ExecutionPolicy" , "Bypass" , "-EncodedCommand" , encoded_ps ],
551535 None , timeout = 30
552536 )
553537
554- self .log_summary .emit (f"Downloading from { url } (up to 3 retries)...\n " )
538+ download_success = False
539+ res2 = None
555540
556- def attempt_winget_download ():
557- ps_script = f"Invoke-WebRequest -Uri '{ url_q } ' -OutFile '{ msix_q } ' -UseBasicParsing"
558- encoded_ps = _ps_encode_command (ps_script )
559- res = self ._run_proc (
560- ["powershell" , "-NoProfile" , "-ExecutionPolicy" , "Bypass" , "-EncodedCommand" , encoded_ps ],
561- None , timeout = TIMEOUT_DOWNLOAD
562- )
563- if res .exitcode == 0 and msix .exists ():
564- return True , "Downloaded"
565- return False , f"Exit { res .exitcode } "
566-
567- success , msg = _download_with_retry (attempt_winget_download , max_attempts = 3 , base_delay = 2.0 )
568-
569- if not success :
570- self .log_summary .emit (f"Download failed after all retries: { msg } \n " )
571- return False , f"Failed to download winget installer: { msg } "
572-
573- if not msix .exists ():
574- return False , f"Download reported success but file not found at { msix } "
575-
576- file_size = msix .stat ().st_size / (1024 * 1024 )
577- self .log_summary .emit (f"Downloaded { file_size :.2f} MB\n " )
578-
579- self .log_summary .emit ("Installing App Installer package...\n " )
580-
581- for attempt in range (2 ):
582- ps_script = f"Add-AppxPackage -Path '{ msix_q } ' -ErrorAction Stop"
583- encoded_ps = _ps_encode_command (ps_script )
584- res2 = self ._run_proc (
585- ["powershell" , "-NoProfile" , "-ExecutionPolicy" , "Bypass" , "-EncodedCommand" , encoded_ps ],
586- None , timeout = TIMEOUT_WINGET
587- )
541+ for url_idx , url in enumerate (urls ):
542+ self .log_summary .emit (f"Download source { url_idx + 1 } /{ len (urls )} : { url } \n " )
588543
589- self .log_summary .emit (f"Installation attempt { attempt + 1 } exit code: { res2 .exitcode } \n " )
544+ try :
545+ if msix .exists ():
546+ msix .unlink ()
547+ except :
548+ pass
590549
591- if res2 .exitcode == 0 :
592- break
550+ def attempt_winget_download ():
551+ # Use the progress-tracking download method
552+ success , msg = self ._download_with_progress (url , msix , timeout = TIMEOUT_DOWNLOAD )
553+
554+ if not success :
555+ return False , msg
556+
557+ # Validate download
558+ if not msix .exists ():
559+ return False , "File not found after download"
560+
561+ file_size_mb = msix .stat ().st_size / (1024 * 1024 )
562+ self .log_summary .emit (f"Downloaded { file_size_mb :.2f} MB\n " )
563+
564+ # Safety check
565+ if file_size_mb < 100 :
566+ self .log_summary .emit (f"[WARNING] File too small ({ file_size_mb :.2f} MB), likely corrupted (should be ~200MB)\n " )
567+ try :
568+ msix .unlink ()
569+ except :
570+ pass
571+ return False , f"File too small ({ file_size_mb :.2f} MB)"
572+
573+ # Validate file format
574+ try :
575+ with open (msix , 'rb' ) as f :
576+ header = f .read (4 )
577+ if header [:2 ] != b'PK' :
578+ self .log_summary .emit ("[WARNING] Invalid file format (not a valid bundle)\n " )
579+ try :
580+ msix .unlink ()
581+ except :
582+ pass
583+ return False , "Invalid file format"
584+ except Exception as e :
585+ return False , f"Cannot validate file: { e } "
586+
587+ return True , "Downloaded and validated"
588+
589+ success , msg = _download_with_retry (attempt_winget_download , max_attempts = 3 , base_delay = 2.0 )
590+
591+ if not success :
592+ self .log_summary .emit (f"Source { url_idx + 1 } failed: { msg } \n " )
593+ continue
594+
595+ # Installation attempts
596+ self .log_summary .emit ("Installing App Installer package...\n " )
597+ msix_q = _ps_single_quote_escape (str (msix ))
593598
594- if res2 .stderr :
595- self .log_summary .emit (f"Error output: { res2 .stderr [:500 ]} \n " )
599+ for attempt in range (2 ):
600+ ps_script = f"Add-AppxPackage -Path '{ msix_q } ' -ErrorAction Stop"
601+ encoded_ps = _ps_encode_command (ps_script )
602+ res2 = self ._run_proc (
603+ ["powershell" , "-NoProfile" , "-ExecutionPolicy" , "Bypass" , "-EncodedCommand" , encoded_ps ],
604+ None , timeout = TIMEOUT_WINGET
605+ )
596606
597- if attempt == 0 :
598- self .log_summary .emit ("First attempt failed, retrying after cleanup...\n " )
599- time .sleep (2 )
607+ self .log_summary .emit (f"Installation attempt { attempt + 1 } exit code: { res2 .exitcode } \n " )
608+
609+ if res2 .exitcode == 0 :
610+ download_success = True
611+ break
612+
613+ if "0x80073CF0" in (res2 .stderr or "" ) or "0x80070570" in (res2 .stderr or "" ):
614+ self .log_summary .emit ("[ERROR] Package corrupted, trying next source...\n " )
615+ try :
616+ msix .unlink ()
617+ except :
618+ pass
619+ break
620+
621+ if res2 .stderr :
622+ self .log_summary .emit (f"Error: { res2 .stderr [:500 ]} \n " )
623+
624+ if attempt == 0 :
625+ self .log_summary .emit ("Retrying after cleanup...\n " )
626+ time .sleep (2 )
627+
628+ if download_success :
629+ break
600630
601631 try :
602- os .remove (msix )
632+ if msix .exists ():
633+ os .remove (msix )
603634 except :
604635 pass
605-
606- if res2 .exitcode != 0 :
607- if "sideload" in (res2 .stderr or "" ).lower () or "policy" in (res2 .stderr or "" ).lower ():
608- return False , "App sideloading disabled. Enable in Settings > Update & Security > For Developers."
609- if "0x80073CF3" in (res2 .stderr or "" ):
610- return False , "Package conflict detected. Please manually uninstall 'App Installer' from Settings > Apps, then retry."
611- return False , f"Installation failed (exit { res2 .exitcode } ). See output above for details."
612-
636+
637+ if not download_success or (res2 and res2 .exitcode != 0 ):
638+ if res2 :
639+ if "sideload" in (res2 .stderr or "" ).lower () or "policy" in (res2 .stderr or "" ).lower ():
640+ return False , "Enable app sideloading in Settings > Update & Security > For Developers."
641+ if "0x80073CF3" in (res2 .stderr or "" ):
642+ return False , "Uninstall 'App Installer' from Settings > Apps, then retry."
643+ if "0x80073CF0" in (res2 .stderr or "" ) or "0x80070570" in (res2 .stderr or "" ):
644+ return False , "All sources corrupted. Check disk health or download manually from https://aka.ms/getwinget"
645+ return False , "All attempts failed. Try manual installation from https://aka.ms/getwinget"
646+
613647 self .log_summary .emit ("Waiting for package registration...\n " )
614648 time .sleep (3 )
615649
616650 refresh_environment_path ()
617651
618652 for attempt in range (3 ):
619653 if shutil .which ("winget" ):
620- self .log_summary .emit (f "winget found in PATH\n " )
654+ self .log_summary .emit ("winget found in PATH\n " )
621655 return True , "winget installed successfully"
622656 if attempt < 2 :
623657 self .log_summary .emit (f"Waiting for PATH update (attempt { attempt + 1 } /3)...\n " )
@@ -1293,7 +1327,7 @@ def show_about(self):
12931327 about_text = f"""
12941328 <h3>MTKClient Windows Installer</h3>
12951329 <p>
1296- <b>Version:</b> { VERSION } (270120262308 )<br>
1330+ <b>Version:</b> { VERSION } (290120261511 )<br>
12971331 <b>Developer:</b>
12981332 <a href="https://github.com/codefl0w">fl0w</a>
12991333 </p>
0 commit comments