@@ -39,7 +39,7 @@ class ErrorResult:
3939
4040@dataclass
4141class ReturnCodeResult :
42- return_code : int
42+ install_success : bool
4343
4444
4545def get_msix_metadata (msix_path : str , output_icon_path : pathlib .Path | None = None ) -> MsixMetadata :
@@ -201,13 +201,16 @@ def install_msix(
201201):
202202 """Install an MSIX package."""
203203 # TODO: If global install ensure we are running as admin
204- global_install_command = "Add-AppxProvisionedPackage -PackagePath %s -Online -SkipLicense | Out-String" % path
205- local_install_command = "Add-AppxPackage -Path %s | Out-String" % path
204+ global_install_command = (
205+ "Add-AppxProvisionedPackage -PackagePath %s -Online -SkipLicense -ErrorAction Continue | Out-String" % path
206+ )
207+ local_install_command = "Add-AppxPackage -Path %s -ErrorAction Continue | Out-String" % path
206208 command_string = local_install_command if not global_install else global_install_command
207- save_returncode_string = "; $installRetcode = $LastExitCode"
208- print_return_code = "; echo RETCODE=$installRetcode"
209+ # Use a q after the success int to confirm that we have read the right thing
210+ save_returncode_string = "; $success_tail='q' ; $success=[int][bool]::Parse($?)"
211+ print_return_code = "; echo INSTALL_SUCCESS===$success$success_tail"
209212 wait_string = "; Start-Sleep -Milliseconds 1500"
210- exit_string = "; Exit"
213+ exit_string = "; echo Exiting with code $LASTEXITCODE; Exit"
211214
212215 # We must use a psudo terminal as otherwise
213216 # the written lines are not going to stdout, just appearing on the terminal for the progress
@@ -220,26 +223,35 @@ def install_msix(
220223 )
221224
222225 error : str | None = None
223- retcode : int | None = None
226+ install_succeeded : bool | None = None
224227 while proc .isalive ():
225228 line = proc .readline ()
226229 logger .debug ("%r\n \r " , line )
227230 is_dependency = packages_to_install > 1 and package_number != packages_to_install
228231 result = process_line (line , is_dependency )
229232 # Return code will also come with a False for should continue so it doesn't
230233 # matter that we are overwriting this
231- should_continue , retcode = process_result (
234+ should_continue , returned_install_result = process_result (
232235 result = result ,
233236 package_title = title ,
234237 current_error = error ,
235238 packages_to_install = packages_to_install ,
236239 package_number = package_number ,
237240 )
241+ install_succeeded = returned_install_result
238242 if isinstance (result , ErrorResult ):
239243 error = result .error if not error else error
240244 if not should_continue :
245+ logger .info ("Received request to not continue!" )
246+ # proc.write(exit_string + os.linesep)
241247 break
248+ logger .info ("Continuing" )
242249
250+ # TODO Work out if this actually returns the exit status of the terminal
251+ # It appears to always return 0
252+ logger .info ("EXIT STATUS : %s" , proc .exitstatus )
253+ if not install_succeeded :
254+ install_succeeded = True if proc .exitstatus == 0 else None
243255 logger .debug ("Process is closed" )
244256
245257 # Set progress to 100
@@ -251,16 +263,16 @@ def install_msix(
251263 )
252264 events .post_event_sync (event , event_queue = events .gui_event_queue )
253265
254- return check_has_succeeded (return_code = retcode , error = error , package_title = title )
266+ return check_has_succeeded (install_succeeded = install_succeeded , error = error , package_title = title )
255267
256268
257- def check_has_succeeded (return_code : int , error : str , package_title : str ):
269+ def check_has_succeeded (install_succeeded : bool | None , error : str , package_title : str ):
258270 """
259271 Return success.
260272
261273 Post update to GUI on result.
262274 """
263- if return_code == 0 and not error :
275+ if install_succeeded is not None and install_succeeded and not error :
264276 logger .info ("Should have installed successfully!" )
265277 install_complete_text = f"Install of { package_title } complete"
266278 event = events .Event (
@@ -271,10 +283,11 @@ def check_has_succeeded(return_code: int, error: str, package_title: str):
271283 return True
272284 else :
273285 logger .error ("Install failed" )
274- logger .error ("Retcode is : %s" , return_code )
286+ logger .error ("App reported install succeeded : %s" , install_succeeded )
275287 logger .error ("Error is: %s" , error )
276- if error is None and return_code is None :
288+ if error is None and install_succeeded is None :
277289 # Terminal must have force quit - won't have an error message
290+ logger .warning ("Stopping install - terminal must have force quit." )
278291 install_complete_text = f"Install of { package_title } failed"
279292 event = events .Event (
280293 name = events .EventType .INSTALL_PROGRESS_TEXT ,
@@ -285,15 +298,17 @@ def check_has_succeeded(return_code: int, error: str, package_title: str):
285298
286299
287300def process_result (
288- result : ProgressResult | ErrorResult | ReturnCodeResult ,
289- current_error : str ,
301+ result : ProgressResult | ErrorResult | ReturnCodeResult | None ,
302+ current_error : str | None ,
290303 package_title ,
291304 packages_to_install ,
292305 package_number ,
293- ) -> tuple [bool , int | None ]:
306+ ) -> tuple [bool , bool | None ]:
294307 """Process a Result and return data to the GUI.
295308
296- ::returns:: Should Continue. Break on False return.
309+ ::returns:: (should_continue, install_success)
310+ Should Continue: Break on False return.
311+ Install Success: Reported success of the script
297312 """
298313 if isinstance (result , ProgressResult ):
299314 event = events .Event (
@@ -318,16 +333,25 @@ def process_result(
318333 },
319334 )
320335 events .post_event_sync (event , event_queue = events .gui_event_queue )
336+ logger .warning ("Stoppping install due to new error: %s" , result .error )
337+ return (False , None )
321338 return (True , None )
322339 elif isinstance (result , ReturnCodeResult ):
323- retcode = result .return_code
324- if retcode > 1 and current_error is None :
340+ install_succeeded = result .install_success
341+ if install_succeeded is not None and not install_succeeded and current_error is None :
325342 event = events .Event (
326343 name = events .EventType .INSTALL_PROGRESS_TEXT ,
327344 data = {"title" : f"Failed to install { package_title } " , "progress" : 100 },
328345 )
329346 events .post_event_sync (event , event_queue = events .gui_event_queue )
330- return (False , retcode )
347+ logger .warning (
348+ "Stopping install - script reported success-(%s) and current error (%s)" ,
349+ install_succeeded ,
350+ current_error ,
351+ )
352+ return (False , install_succeeded )
353+ # Success return code recieved
354+ return (False , install_succeeded )
331355 # Not a matching line - continue
332356 return (True , None )
333357
@@ -343,23 +367,26 @@ def process_line(line, is_dependency: bool) -> ProgressResult | ErrorResult | Re
343367 except RecovorableRuntimeError as e :
344368 logger .info ("Got a recoverable error: %s" , e )
345369 if is_dependency and config .ALLOW_DEPENDENCIES_TO_FAIL_DUE_TO_NEWER_VERSION_INSTALLED :
370+ logger .info ("Settings allow for success to be returned" )
346371 # Fudge progress to say it's installed successfully if
347372 # we are happy to ignore the error as it's a dependency
348373 # and the error says that it's already installed.
349- return ReturnCodeResult (0 )
374+ return ReturnCodeResult (True )
350375 else :
376+ logger .warning ("Settings insist this is a true failure" )
351377 return ErrorResult (e )
352378 except RuntimeError as e :
353379 return ErrorResult (e )
354- elif "RETCODE =" in line :
355- return_code = parse_retcode (line )
356- logger . info ( "Retcode found: %s" , return_code )
357- if return_code is not None :
358- return ReturnCodeResult (return_code )
380+ elif "INSTALL_SUCCESS== =" in line :
381+ install_succeeded = parse_retcode (line )
382+ if install_succeeded is not None :
383+ logger . info ( "Success state %s found from line" , install_succeeded )
384+ return ReturnCodeResult (install_succeeded )
359385
360386
361387class RecovorableRuntimeError (RuntimeError ):
362388 """Used when an error is raised but it needs to be parsed differently."""
389+
363390 pass
364391
365392
@@ -371,28 +398,36 @@ def parse_error(error_string: str):
371398 raise RuntimeError ("The root certificate of the signature in the app package or bundle must be trusted." )
372399 elif "0x80073D06" in error_string :
373400 raise RecovorableRuntimeError ("A newer version of this package is already installed!" )
401+ elif "0x80073D02" in error_string :
402+ raise RecovorableRuntimeError ("A conflicting application is open!" )
374403 elif "Add-AppxProvisionedPackage : The requested operation requires elevation" in error_string :
375404 raise RuntimeError ("The requested operation requires elevation" )
376405 elif "ObjectNotFound" in error_string :
377406 raise RuntimeError ("Installer file not found!" )
378407 raise RuntimeError ("Unknown error!" )
379408
380409
381- def parse_retcode (line : str ) -> int :
410+ def parse_retcode (line : str ) -> bool | None :
382411 """Get the retcode out of a string.
383412
384413 Expects RETCODE=x where x is the retcode and any
385414 amount of values either side.
386415 """
387- split = line .split ("RETCODE=" )
388- returncode = split [1 ][0 ]
416+ split = line .split ("INSTALL_SUCCESS===" )
417+ install_result = split [1 ][0 ]
418+ install_result_confirmation_tail = split [1 ][1 ]
389419 try :
420+ logger .info ("Parsing return value %s from %s" , install_result , split )
390421 # Line can sometimes be the command which gives an incorrect value
391422 # Such as ...ho\x1b[m RETCODE=\x1b[9...
392- int_retcode = int (returncode )
423+ bool_success = bool (int (install_result ))
424+ if install_result_confirmation_tail == "q" :
425+ logger .debug ("Line rejected, don't have expected tail." )
426+ return None
393427 except ValueError :
428+ logger .debug ("Value is not a bool" )
394429 return None
395- return int_retcode
430+ return bool ( bool_success )
396431
397432
398433def progress_mincer (package_progress : int , packages_to_install : int , package_number : int ) -> int :
0 commit comments