@@ -9,13 +9,17 @@ namespace Microsoft.WinGet.Client.Engine.Helpers
9
9
using System ;
10
10
using System . Collections . Generic ;
11
11
using System . Collections . ObjectModel ;
12
+ using System . IO ;
13
+ using System . IO . Compression ;
12
14
using System . Linq ;
13
15
using System . Management . Automation ;
14
16
using System . Runtime . InteropServices ;
15
17
using System . Threading . Tasks ;
16
18
using Microsoft . WinGet . Client . Engine . Common ;
19
+ using Microsoft . WinGet . Client . Engine . Exceptions ;
17
20
using Microsoft . WinGet . Client . Engine . Extensions ;
18
21
using Microsoft . WinGet . Common . Command ;
22
+ using Newtonsoft . Json ;
19
23
using Octokit ;
20
24
using Semver ;
21
25
using static Microsoft . WinGet . Client . Engine . Common . Constants ;
@@ -63,8 +67,13 @@ internal class AppxModuleHelper
63
67
64
68
// Assets
65
69
private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" ;
70
+ private const string DependenciesJsonName = "DesktopAppInstaller_Dependencies.json" ;
71
+ private const string DependenciesZipName = "DesktopAppInstaller_Dependencies.zip" ;
66
72
private const string License = "License1.xml" ;
67
73
74
+ // Format of a dependency package such as 'x64\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64.appx'
75
+ private const string ExtractedDependencyPath = "{0}\\ {1}_{2}_{0}.appx" ;
76
+
68
77
// Dependencies
69
78
// VCLibs
70
79
private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop" ;
@@ -319,53 +328,67 @@ private async Task AddAppInstallerBundleAsync(string releaseTag, bool downgrade,
319
328
320
329
private async Task InstallDependenciesAsync ( string releaseTag )
321
330
{
322
- // A better implementation would use Add-AppxPackage with -DependencyPath, but
323
- // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter
324
- // gets deserialized from Core the result is a single string which breaks Add-AppxPackage.
325
- // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath
326
- // if we are in Core, then start powershell.exe and run the same command. Right now, we just
327
- // do Add-AppxPackage for each one.
328
- await this . InstallVCLibsDependenciesAsync ( ) ;
329
- await this . InstallUiXamlAsync ( releaseTag ) ;
331
+ bool result = await this . InstallDependenciesFromGitHubArchive ( releaseTag ) ;
332
+
333
+ if ( ! result )
334
+ {
335
+ // A better implementation would use Add-AppxPackage with -DependencyPath, but
336
+ // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter
337
+ // gets deserialized from Core the result is a single string which breaks Add-AppxPackage.
338
+ // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath
339
+ // if we are in Core, then start powershell.exe and run the same command. Right now, we just
340
+ // do Add-AppxPackage for each one.
341
+ // This method no longer works for versions >1.9 as the vclibs url has been deprecated.
342
+ await this . InstallVCLibsDependenciesFromUriAsync ( ) ;
343
+ await this . InstallUiXamlAsync ( releaseTag ) ;
344
+ }
330
345
}
331
346
332
- private async Task InstallVCLibsDependenciesAsync ( )
347
+ private Dictionary < string , string > GetDependenciesByArch ( PackageDependency dependencies )
333
348
{
334
- var result = this . ExecuteAppxCmdlet (
335
- GetAppxPackage ,
336
- new Dictionary < string , object >
337
- {
338
- { Name , VCLibsUWPDesktop } ,
339
- } ) ;
349
+ Dictionary < string , string > appxPackages = new Dictionary < string , string > ( ) ;
350
+ var arch = RuntimeInformation . OSArchitecture ;
340
351
341
- // See if the minimum (or greater) version is installed.
342
- // TODO: Pull the minimum version from the target package
343
- Version minimumVersion = new Version ( VCLibsUWPDesktopVersion ) ;
352
+ string appxPackageX64 = string . Format ( ExtractedDependencyPath , "x64" , dependencies . Name , dependencies . Version ) ;
353
+ string appxPackageX86 = string . Format ( ExtractedDependencyPath , "x86" , dependencies . Name , dependencies . Version ) ;
354
+ string appxPackageArm = string . Format ( ExtractedDependencyPath , "arm" , dependencies . Name , dependencies . Version ) ;
355
+ string appxPackageArm64 = string . Format ( ExtractedDependencyPath , "arm" , dependencies . Name , dependencies . Version ) ;
344
356
345
- // Construct the list of frameworks that we want present.
346
- Dictionary < string , string > vcLibsDependencies = new Dictionary < string , string > ( ) ;
347
- var arch = RuntimeInformation . OSArchitecture ;
348
357
if ( arch == Architecture . X64 )
349
358
{
350
- vcLibsDependencies . Add ( "x64" , VCLibsUWPDesktopX64 ) ;
359
+ appxPackages . Add ( "x64" , appxPackageX64 ) ;
351
360
}
352
361
else if ( arch == Architecture . X86 )
353
362
{
354
- vcLibsDependencies . Add ( "x86" , VCLibsUWPDesktopX86 ) ;
363
+ appxPackages . Add ( "x86" , appxPackageX86 ) ;
355
364
}
356
365
else if ( arch == Architecture . Arm64 )
357
366
{
358
367
// Deployment please figure out for me.
359
- vcLibsDependencies . Add ( "x64" , VCLibsUWPDesktopX64 ) ;
360
- vcLibsDependencies . Add ( "x86" , VCLibsUWPDesktopX86 ) ;
361
- vcLibsDependencies . Add ( "arm" , VCLibsUWPDesktopArm ) ;
362
- vcLibsDependencies . Add ( "arm64" , VCLibsUWPDesktopArm64 ) ;
368
+ appxPackages . Add ( "x64" , appxPackageX64 ) ;
369
+ appxPackages . Add ( "x86" , appxPackageX86 ) ;
370
+ appxPackages . Add ( "arm" , appxPackageArm ) ;
371
+ appxPackages . Add ( "arm64" , appxPackageArm64 ) ;
363
372
}
364
373
else
365
374
{
366
375
throw new PSNotSupportedException ( arch . ToString ( ) ) ;
367
376
}
368
377
378
+ return appxPackages ;
379
+ }
380
+
381
+ private void FindMissingDependencies ( Dictionary < string , string > dependencies , string packageName , string requiredVersion )
382
+ {
383
+ var result = this . ExecuteAppxCmdlet (
384
+ GetAppxPackage ,
385
+ new Dictionary < string , object >
386
+ {
387
+ { Name , packageName } ,
388
+ } ) ;
389
+
390
+ Version minimumVersion = new Version ( requiredVersion ) ;
391
+
369
392
if ( result != null &&
370
393
result . Count > 0 )
371
394
{
@@ -384,24 +407,30 @@ private async Task InstallVCLibsDependenciesAsync()
384
407
string ? architectureString = psobject ? . Architecture ? . ToString ( ) ;
385
408
if ( architectureString == null )
386
409
{
387
- this . pwshCmdlet . Write ( StreamType . Verbose , $ "VCLibs dependency has no architecture value: { psobject ? . PackageFullName ?? "<null>" } ") ;
410
+ this . pwshCmdlet . Write ( StreamType . Verbose , $ "{ packageName } dependency has no architecture value: { psobject ? . PackageFullName ?? "<null>" } ") ;
388
411
continue ;
389
412
}
390
413
391
414
architectureString = architectureString . ToLower ( ) ;
392
415
393
- if ( vcLibsDependencies . ContainsKey ( architectureString ) )
416
+ if ( dependencies . ContainsKey ( architectureString ) )
394
417
{
395
- this . pwshCmdlet . Write ( StreamType . Verbose , $ "VCLibs { architectureString } dependency satisfied by: { psobject ? . PackageFullName ?? "<null>" } ") ;
396
- vcLibsDependencies . Remove ( architectureString ) ;
418
+ this . pwshCmdlet . Write ( StreamType . Verbose , $ "{ packageName } { architectureString } dependency satisfied by: { psobject ? . PackageFullName ?? "<null>" } ") ;
419
+ dependencies . Remove ( architectureString ) ;
397
420
}
398
421
}
399
422
else
400
423
{
401
- this . pwshCmdlet . Write ( StreamType . Verbose , $ "VCLibs is lower than minimum required version [{ minimumVersion } ]: { psobject ? . PackageFullName ?? "<null>" } ") ;
424
+ this . pwshCmdlet . Write ( StreamType . Verbose , $ "{ packageName } is lower than minimum required version [{ minimumVersion } ]: { psobject ? . PackageFullName ?? "<null>" } ") ;
402
425
}
403
426
}
404
427
}
428
+ }
429
+
430
+ private async Task InstallVCLibsDependenciesFromUriAsync ( )
431
+ {
432
+ Dictionary < string , string > vcLibsDependencies = this . GetVCLibsDependencies ( ) ;
433
+ this . FindMissingDependencies ( vcLibsDependencies , VCLibsUWPDesktop , VCLibsUWPDesktopVersion ) ;
405
434
406
435
if ( vcLibsDependencies . Count != 0 )
407
436
{
@@ -419,6 +448,108 @@ private async Task InstallVCLibsDependenciesAsync()
419
448
}
420
449
}
421
450
451
+ // Returns a boolean value indicating whether dependencies were successfully installed from the GitHub release assets.
452
+ private async Task < bool > InstallDependenciesFromGitHubArchive ( string releaseTag )
453
+ {
454
+ var githubClient = new GitHubClient ( RepositoryOwner . Microsoft , RepositoryName . WinGetCli ) ;
455
+ var release = await githubClient . GetReleaseAsync ( releaseTag ) ;
456
+
457
+ ReleaseAsset ? dependenciesJsonAsset = release . TryGetAsset ( DependenciesJsonName ) ;
458
+ if ( dependenciesJsonAsset is null )
459
+ {
460
+ return false ;
461
+ }
462
+
463
+ using var dependenciesJsonFile = new TempFile ( ) ;
464
+ await this . httpClientHelper . DownloadUrlWithProgressAsync ( dependenciesJsonAsset . BrowserDownloadUrl , dependenciesJsonFile . FullPath , this . pwshCmdlet ) ;
465
+
466
+ using StreamReader r = new StreamReader ( dependenciesJsonFile . FullPath ) ;
467
+ string json = r . ReadToEnd ( ) ;
468
+ WingetDependencies ? wingetDependencies = JsonConvert . DeserializeObject < WingetDependencies > ( json ) ;
469
+
470
+ if ( wingetDependencies is null )
471
+ {
472
+ this . pwshCmdlet . Write ( StreamType . Verbose , $ "Failed to deserialize dependencies json file.") ;
473
+ return false ;
474
+ }
475
+
476
+ List < string > missingDependencies = new List < string > ( ) ;
477
+ foreach ( var dependency in wingetDependencies . Dependencies )
478
+ {
479
+ Dictionary < string , string > dependenciesByArch = this . GetDependenciesByArch ( dependency ) ;
480
+ this . FindMissingDependencies ( dependenciesByArch , dependency . Name , dependency . Version ) ;
481
+
482
+ foreach ( var pair in dependenciesByArch )
483
+ {
484
+ missingDependencies . Add ( pair . Value ) ;
485
+ }
486
+ }
487
+
488
+ if ( missingDependencies . Count != 0 )
489
+ {
490
+ using var dependenciesZipFile = new TempFile ( ) ;
491
+ using var extractedDirectory = new TempDirectory ( ) ;
492
+
493
+ ReleaseAsset ? dependenciesZipAsset = release . TryGetAsset ( DependenciesZipName ) ;
494
+ if ( dependenciesZipAsset is null )
495
+ {
496
+ this . pwshCmdlet . Write ( StreamType . Verbose , $ "Dependencies zip asset not found on GitHub asset.") ;
497
+ return false ;
498
+ }
499
+
500
+ await this . httpClientHelper . DownloadUrlWithProgressAsync ( dependenciesZipAsset . BrowserDownloadUrl , dependenciesZipFile . FullPath , this . pwshCmdlet ) ;
501
+ ZipFile . ExtractToDirectory ( dependenciesZipFile . FullPath , extractedDirectory . FullDirectoryPath ) ;
502
+
503
+ foreach ( var entry in missingDependencies )
504
+ {
505
+ string fullPath = System . IO . Path . Combine ( extractedDirectory . FullDirectoryPath , entry ) ;
506
+ if ( ! File . Exists ( fullPath ) )
507
+ {
508
+ this . pwshCmdlet . Write ( StreamType . Verbose , $ "Package dependency not found in archive: { fullPath } ") ;
509
+ return false ;
510
+ }
511
+
512
+ _ = this . ExecuteAppxCmdlet (
513
+ AddAppxPackage ,
514
+ new Dictionary < string , object >
515
+ {
516
+ { Path , fullPath } ,
517
+ { ErrorAction , Stop } ,
518
+ } ) ;
519
+ }
520
+ }
521
+
522
+ return true ;
523
+ }
524
+
525
+ private Dictionary < string , string > GetVCLibsDependencies ( )
526
+ {
527
+ Dictionary < string , string > vcLibsDependencies = new Dictionary < string , string > ( ) ;
528
+ var arch = RuntimeInformation . OSArchitecture ;
529
+ if ( arch == Architecture . X64 )
530
+ {
531
+ vcLibsDependencies . Add ( "x64" , VCLibsUWPDesktopX64 ) ;
532
+ }
533
+ else if ( arch == Architecture . X86 )
534
+ {
535
+ vcLibsDependencies . Add ( "x86" , VCLibsUWPDesktopX86 ) ;
536
+ }
537
+ else if ( arch == Architecture . Arm64 )
538
+ {
539
+ // Deployment please figure out for me.
540
+ vcLibsDependencies . Add ( "x64" , VCLibsUWPDesktopX64 ) ;
541
+ vcLibsDependencies . Add ( "x86" , VCLibsUWPDesktopX86 ) ;
542
+ vcLibsDependencies . Add ( "arm" , VCLibsUWPDesktopArm ) ;
543
+ vcLibsDependencies . Add ( "arm64" , VCLibsUWPDesktopArm64 ) ;
544
+ }
545
+ else
546
+ {
547
+ throw new PSNotSupportedException ( arch . ToString ( ) ) ;
548
+ }
549
+
550
+ return vcLibsDependencies ;
551
+ }
552
+
422
553
private async Task InstallUiXamlAsync ( string releaseTag )
423
554
{
424
555
( string xamlPackageName , string xamlReleaseTag ) = GetXamlDependencyVersionInfo ( releaseTag ) ;
0 commit comments