winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

AppxModuleHelper.cs (35555B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="AppxModuleHelper.cs" company="Microsoft Corporation">
      3 //     Copyright (c) Microsoft Corporation. Licensed under the MIT License.
      4 // </copyright>
      5 // -----------------------------------------------------------------------------
      6 
      7 namespace Microsoft.WinGet.Client.Engine.Helpers
      8 {
      9     using System;
     10     using System.Collections.Generic;
     11     using System.Collections.ObjectModel;
     12     using System.IO;
     13     using System.IO.Compression;
     14     using System.Linq;
     15     using System.Management.Automation;
     16     using System.Runtime.InteropServices;
     17     using System.Threading.Tasks;
     18     using Microsoft.WinGet.Client.Engine.Common;
     19     using Microsoft.WinGet.Client.Engine.Exceptions;
     20     using Microsoft.WinGet.Client.Engine.Extensions;
     21     using Microsoft.WinGet.Common.Command;
     22     using Newtonsoft.Json;
     23     using Octokit;
     24     using Semver;
     25     using static Microsoft.WinGet.Client.Engine.Common.Constants;
     26 
     27     /// <summary>
     28     /// Helper to make calls to the Appx module.
     29     /// </summary>
     30     internal class AppxModuleHelper
     31     {
     32         // Cmdlets
     33         private const string ImportModule = "Import-Module";
     34         private const string GetAppxPackage = "Get-AppxPackage";
     35         private const string AddAppxPackage = "Add-AppxPackage";
     36         private const string AddAppxProvisionedPackage = "Add-AppxProvisionedPackage";
     37         private const string GetCommand = "Get-Command";
     38 
     39         // Parameters name
     40         private const string Name = "Name";
     41         private const string Path = "Path";
     42         private const string ErrorAction = "ErrorAction";
     43         private const string WarningAction = "WarningAction";
     44         private const string PackagePath = "PackagePath";
     45         private const string LicensePath = "LicensePath";
     46         private const string Module = "Module";
     47         private const string StubPackageOption = "StubPackageOption";
     48         private const string PackageTypeFilter = "PackageTypeFilter";
     49 
     50         // Parameter Values
     51         private const string Appx = "Appx";
     52         private const string Stop = "Stop";
     53         private const string SilentlyContinue = "SilentlyContinue";
     54         private const string Online = "Online";
     55         private const string UsePreference = "UsePreference";
     56         private const string Framework = "Framework";
     57 
     58         // Options
     59         private const string UseWindowsPowerShell = "UseWindowsPowerShell";
     60         private const string ForceUpdateFromAnyVersion = "ForceUpdateFromAnyVersion";
     61         private const string Register = "Register";
     62         private const string DisableDevelopmentMode = "DisableDevelopmentMode";
     63         private const string ForceTargetApplicationShutdown = "ForceTargetApplicationShutdown";
     64 
     65         private const string AppInstallerName = "Microsoft.DesktopAppInstaller";
     66         private const string AppxManifest = "AppxManifest.xml";
     67         private const string PackageFullName = "PackageFullName";
     68         private const string Version = "Version";
     69 
     70         private const string DependencyArchitectureEnvironmentVariable = "WINGET_PACKAGE_MANAGER_REPAIR_DEPENDENCY_ARCHITECTURES";
     71 
     72         // Assets
     73         private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle";
     74         private const string DependenciesJsonName = "DesktopAppInstaller_Dependencies.json";
     75         private const string DependenciesZipName = "DesktopAppInstaller_Dependencies.zip";
     76         private const string License = "License1.xml";
     77 
     78         // Format of a dependency package such as 'x64\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64.appx'
     79         private const string ExtractedDependencyPath = "{0}\\{1}_{2}_{0}.appx";
     80 
     81         // Dependencies
     82         // VCLibs
     83         private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop";
     84         private const string VCLibsUWPDesktopVersion = "14.0.30704.0";
     85         private const string VCLibsUWPDesktopX64 = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx";
     86         private const string VCLibsUWPDesktopX86 = "https://aka.ms/Microsoft.VCLibs.x86.14.00.Desktop.appx";
     87         private const string VCLibsUWPDesktopArm64 = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx";
     88 
     89         // Xaml
     90         private const string XamlPackage28 = "Microsoft.UI.Xaml.2.8";
     91         private const string XamlReleaseTag286 = "v2.8.6";
     92         private const string MinimumWinGetReleaseTagForXaml28 = "v1.7.10514";
     93 
     94         private const string XamlPackage27 = "Microsoft.UI.Xaml.2.7";
     95         private const string XamlReleaseTag273 = "v2.7.3";
     96 
     97         private readonly PowerShellCmdlet pwshCmdlet;
     98         private readonly HttpClientHelper httpClientHelper;
     99         private Lazy<HashSet<Architecture>> frameworkArchitectures;
    100 
    101         /// <summary>
    102         /// Initializes a new instance of the <see cref="AppxModuleHelper"/> class.
    103         /// </summary>
    104         /// <param name="pwshCmdlet">The calling cmdlet.</param>
    105         public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
    106         {
    107             this.pwshCmdlet = pwshCmdlet;
    108             this.httpClientHelper = new HttpClientHelper();
    109             this.frameworkArchitectures = new Lazy<HashSet<Architecture>>(() => this.InitFrameworkArchitectures());
    110         }
    111 
    112         /// <summary>
    113         /// Calls Get-AppxPackage Microsoft.DesktopAppInstaller.
    114         /// </summary>
    115         /// <returns>Result of Get-AppxPackage.</returns>
    116         public PSObject? GetAppInstallerObject()
    117         {
    118             return this.GetAppxObject(AppInstallerName);
    119         }
    120 
    121         /// <summary>
    122         /// Gets the string value a property from the Get-AppxPackage object of AppInstaller.
    123         /// </summary>
    124         /// <param name="propertyName">Property name.</param>
    125         /// <returns>Value, null if doesn't exist.</returns>
    126         public string? GetAppInstallerPropertyValue(string propertyName)
    127         {
    128             string? result = null;
    129             var packageObj = this.GetAppInstallerObject();
    130             if (packageObj is not null)
    131             {
    132                 var property = packageObj.Properties.Where(p => p.Name == propertyName).FirstOrDefault();
    133                 if (property is not null)
    134                 {
    135                     result = property.Value as string;
    136                 }
    137             }
    138 
    139             return result;
    140         }
    141 
    142         /// <summary>
    143         /// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml.
    144         /// </summary>
    145         /// <param name="releaseTag">Release tag of GitHub release.</param>
    146         public void RegisterAppInstaller(string releaseTag)
    147         {
    148             if (string.IsNullOrEmpty(releaseTag))
    149             {
    150                 string? versionFromLocalPackage = this.GetAppInstallerPropertyValue(Version);
    151 
    152                 if (versionFromLocalPackage == null)
    153                 {
    154                     throw new ArgumentNullException(Version);
    155                 }
    156 
    157                 var packageVersion = new Version(versionFromLocalPackage);
    158                 if (packageVersion.Major == 1 && packageVersion.Minor > 15)
    159                 {
    160                     releaseTag = $"1.{packageVersion.Minor - 15}.{packageVersion.Build}";
    161                 }
    162                 else
    163                 {
    164                     releaseTag = $"{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}";
    165                 }
    166             }
    167 
    168             // Ensure that all dependencies are present when attempting to register.
    169             // If dependencies are missing, a provisioned package can appear to only need registration,
    170             // but will fail to register. `InstallDependenciesAsync` checks for the packages before
    171             // acting, so it should be mostly a no-op if they are already available.
    172             this.InstallDependenciesAsync(releaseTag).Wait();
    173 
    174             string? packageFullName = this.GetAppInstallerPropertyValue(PackageFullName);
    175 
    176             if (packageFullName == null)
    177             {
    178                 throw new ArgumentNullException(PackageFullName);
    179             }
    180 
    181             string appxManifestPath = System.IO.Path.Combine(
    182                 Utilities.ProgramFilesWindowsAppPath,
    183                 packageFullName,
    184                 AppxManifest);
    185 
    186             _ = this.ExecuteAppxCmdlet(
    187                 AddAppxPackage,
    188                 new Dictionary<string, object>
    189                 {
    190                     { Path, appxManifestPath },
    191                 },
    192                 new List<string>
    193                 {
    194                     Register,
    195                     DisableDevelopmentMode,
    196                 });
    197         }
    198 
    199         /// <summary>
    200         /// Install AppInstaller's bundle from a GitHub release.
    201         /// </summary>
    202         /// <param name="releaseTag">Release tag of GitHub release.</param>
    203         /// <param name="allUsers">If install for all users is needed.</param>
    204         /// <param name="isDowngrade">Is downgrade.</param>
    205         /// <param name="force">Force application shutdown.</param>
    206         /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
    207         public async Task InstallFromGitHubReleaseAsync(string releaseTag, bool allUsers, bool isDowngrade, bool force)
    208         {
    209             await this.InstallDependenciesAsync(releaseTag);
    210 
    211             if (isDowngrade)
    212             {
    213                 // Add-AppxProvisionedPackage doesn't support downgrade.
    214                 await this.AddAppInstallerBundleAsync(releaseTag, true, force);
    215 
    216                 if (allUsers)
    217                 {
    218                     await this.AddProvisionPackageAsync(releaseTag);
    219                 }
    220             }
    221             else
    222             {
    223                 if (allUsers)
    224                 {
    225                     await this.AddProvisionPackageAsync(releaseTag);
    226                 }
    227                 else
    228                 {
    229                     await this.AddAppInstallerBundleAsync(releaseTag, false, force);
    230                 }
    231             }
    232         }
    233 
    234         /// <summary>
    235         /// Gets the Xaml dependency package name and release tag based on the provided WinGet release tag.
    236         /// </summary>
    237         /// <param name="releaseTag">WinGet release tag.</param>
    238         /// <returns>A tuple in the format of (XamlPackageName, XamlReleaseTag).</returns>
    239         private static Tuple<string, string> GetXamlDependencyVersionInfo(string releaseTag)
    240         {
    241             var targetVersion = SemVersion.Parse(releaseTag, SemVersionStyles.AllowLowerV);
    242 
    243             if (targetVersion.CompareSortOrderTo(SemVersion.Parse(MinimumWinGetReleaseTagForXaml28, SemVersionStyles.AllowLowerV)) >= 0)
    244             {
    245                 return Tuple.Create(XamlPackage28, XamlReleaseTag286);
    246             }
    247             else
    248             {
    249                 return Tuple.Create(XamlPackage27, XamlReleaseTag273);
    250             }
    251         }
    252 
    253         private async Task AddProvisionPackageAsync(string releaseTag)
    254         {
    255             var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
    256             var release = await githubClient.GetReleaseAsync(releaseTag);
    257 
    258             var bundleAsset = release.GetAsset(MsixBundleName);
    259             using var bundleFile = new TempFile(fileName: MsixBundleName);
    260             await this.httpClientHelper.DownloadUrlWithProgressAsync(
    261                 bundleAsset.BrowserDownloadUrl, bundleFile.FullPath, this.pwshCmdlet);
    262 
    263             var licenseAsset = release.GetAssetEndsWith(License);
    264             using var licenseFile = new TempFile(fileName: licenseAsset.Name);
    265             await this.httpClientHelper.DownloadUrlWithProgressAsync(
    266                 licenseAsset.BrowserDownloadUrl, licenseFile.FullPath, this.pwshCmdlet);
    267 
    268             try
    269             {
    270                 this.pwshCmdlet.ExecuteInPowerShellThread(
    271                     () =>
    272                     {
    273                         var ps = PowerShell.Create(RunspaceMode.CurrentRunspace);
    274                         ps.AddCommand(AddAppxProvisionedPackage)
    275                           .AddParameter(Online)
    276                           .AddParameter(PackagePath, bundleFile.FullPath)
    277                           .AddParameter(LicensePath, licenseFile.FullPath)
    278                           .AddParameter(ErrorAction, Stop)
    279                           .Invoke();
    280                     });
    281             }
    282             catch (RuntimeException e)
    283             {
    284                 this.pwshCmdlet.Write(StreamType.Verbose, $"Failed installing bundle via Add-AppxProvisionedPackage {e}");
    285                 throw;
    286             }
    287         }
    288 
    289         private async Task AddAppInstallerBundleAsync(string releaseTag, bool downgrade, bool force)
    290         {
    291             var options = new List<string>();
    292             if (downgrade)
    293             {
    294                 options.Add(ForceUpdateFromAnyVersion);
    295             }
    296 
    297             if (force)
    298             {
    299                 options.Add(ForceTargetApplicationShutdown);
    300             }
    301 
    302             var parameters = new Dictionary<string, object>();
    303             if (this.IsStubPackageOptionPresent())
    304             {
    305                 parameters.Add(StubPackageOption, UsePreference);
    306             }
    307 
    308             try
    309             {
    310                 var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
    311                 var release = await githubClient.GetReleaseAsync(releaseTag);
    312 
    313                 var bundleAsset = release.GetAsset(MsixBundleName);
    314                 await this.AddAppxPackageAsUriAsync(bundleAsset.BrowserDownloadUrl, MsixBundleName, parameters, options);
    315             }
    316             catch (RuntimeException e)
    317             {
    318                 this.pwshCmdlet.Write(StreamType.Verbose, $"Failed installing bundle via Add-AppxPackage {e}");
    319                 throw;
    320             }
    321         }
    322 
    323         private PSObject? GetAppxObject(string packageName)
    324         {
    325             return this.ExecuteAppxCmdlet(
    326                 GetAppxPackage,
    327                 new Dictionary<string, object>
    328                 {
    329                     { Name, packageName },
    330                 })
    331                 .FirstOrDefault();
    332         }
    333 
    334         private async Task InstallDependenciesAsync(string releaseTag)
    335         {
    336             bool result = await this.InstallDependenciesFromGitHubArchive(releaseTag);
    337 
    338             if (!result)
    339             {
    340                 // A better implementation would use Add-AppxPackage with -DependencyPath, but
    341                 // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter
    342                 // gets deserialized from Core the result is a single string which breaks Add-AppxPackage.
    343                 // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath
    344                 // if we are in Core, then start powershell.exe and run the same command. Right now, we just
    345                 // do Add-AppxPackage for each one.
    346                 // This method no longer works for versions >1.9 as the vclibs url has been deprecated.
    347                 await this.InstallVCLibsDependenciesFromUriAsync();
    348                 await this.InstallUiXamlAsync(releaseTag);
    349             }
    350         }
    351 
    352         /// <summary>
    353         /// Extracts all of the architectures used by framework packages.
    354         /// </summary>
    355         /// <returns>The set of architectures used by installed framework packages.</returns>
    356         private HashSet<Architecture> InitFrameworkArchitectures()
    357         {
    358             HashSet<Architecture> architectures = new HashSet<Architecture>();
    359 
    360             // Read the override from the environment variable if it exists.
    361             string? environmentVariable = Environment.GetEnvironmentVariable(DependencyArchitectureEnvironmentVariable);
    362             if (environmentVariable != null)
    363             {
    364                 this.pwshCmdlet.Write(StreamType.Verbose, $"Using environment variable {DependencyArchitectureEnvironmentVariable} for frameworks: {environmentVariable}");
    365 
    366                 foreach (string architectureString in environmentVariable.Split(',', ';'))
    367                 {
    368                     Architecture architecture;
    369                     if (Enum.TryParse(architectureString, true, out architecture))
    370                     {
    371                         if (architectures.Add(architecture))
    372                         {
    373                             this.pwshCmdlet.Write(StreamType.Verbose, $"Framework architecture from environment variable: {architectureString}");
    374                         }
    375                     }
    376                 }
    377 
    378                 return architectures;
    379             }
    380 
    381             // If there are any framework packages already installed, use the same architecture as them.
    382             var result = this.ExecuteAppxCmdlet(
    383                 GetAppxPackage,
    384                 new Dictionary<string, object>
    385                 {
    386                     { PackageTypeFilter, Framework },
    387                 });
    388 
    389             if (result != null &&
    390                 result.Count > 0)
    391             {
    392                 foreach (dynamic psobject in result)
    393                 {
    394                     string? architectureString = psobject?.Architecture?.ToString();
    395                     if (architectureString == null)
    396                     {
    397                         continue;
    398                     }
    399 
    400                     Architecture architecture;
    401                     if (Enum.TryParse(architectureString, true, out architecture))
    402                     {
    403                         if (architectures.Add(architecture))
    404                         {
    405                             this.pwshCmdlet.Write(StreamType.Verbose, $"Found framework architecture: {architectureString}");
    406                         }
    407                     }
    408                 }
    409             }
    410 
    411             // Fall back to guessing from the current OS architecture.
    412             // This may have issues on ARM64 because RuntimeInformation.OSArchitecture seems to just lie sometimes.
    413             // See https://github.com/microsoft/winget-cli/issues/5020
    414             if (architectures.Count == 0)
    415             {
    416                 var arch = RuntimeInformation.OSArchitecture;
    417                 this.pwshCmdlet.Write(StreamType.Verbose, $"OS architecture: {arch.ToString()}");
    418 
    419                 if (arch == Architecture.X64)
    420                 {
    421                     architectures.Add(Architecture.X64);
    422                 }
    423                 else if (arch == Architecture.X86)
    424                 {
    425                     architectures.Add(Architecture.X86);
    426                 }
    427                 else if (arch == Architecture.Arm64)
    428                 {
    429                     // Let deployment figure it out
    430                     architectures.Add(Architecture.Arm64);
    431                     architectures.Add(Architecture.X64);
    432                     architectures.Add(Architecture.X86);
    433                 }
    434             }
    435 
    436             return architectures;
    437         }
    438 
    439         private Dictionary<string, string> GetDependenciesByArch(PackageDependency dependencies)
    440         {
    441             Dictionary<string, string> appxPackages = new Dictionary<string, string>();
    442 
    443             foreach (var architecture in this.frameworkArchitectures.Value)
    444             {
    445                 switch (architecture)
    446                 {
    447                     case Architecture.X86:
    448                         appxPackages.Add("x86", string.Format(ExtractedDependencyPath, "x86", dependencies.Name, dependencies.Version));
    449                         break;
    450                     case Architecture.X64:
    451                         appxPackages.Add("x64", string.Format(ExtractedDependencyPath, "x64", dependencies.Name, dependencies.Version));
    452                         break;
    453                     case Architecture.Arm64:
    454                         appxPackages.Add("arm64", string.Format(ExtractedDependencyPath, "arm64", dependencies.Name, dependencies.Version));
    455                         break;
    456                     default:
    457                         this.pwshCmdlet.Write(StreamType.Verbose, $"GetDependenciesByArch: Ignoring {architecture}");
    458                         break;
    459                 }
    460             }
    461 
    462             return appxPackages;
    463         }
    464 
    465         private void FindMissingDependencies(Dictionary<string, string> dependencies, string packageName, string requiredVersion)
    466         {
    467             var result = this.ExecuteAppxCmdlet(
    468                 GetAppxPackage,
    469                 new Dictionary<string, object>
    470                 {
    471                     { Name, packageName },
    472                 });
    473 
    474             Version minimumVersion = new Version(requiredVersion);
    475 
    476             if (result != null &&
    477                 result.Count > 0)
    478             {
    479                 foreach (dynamic psobject in result)
    480                 {
    481                     string? versionString = psobject?.Version?.ToString();
    482                     if (versionString == null)
    483                     {
    484                         continue;
    485                     }
    486 
    487                     Version packageVersion = new Version(versionString);
    488 
    489                     if (packageVersion >= minimumVersion)
    490                     {
    491                         string? architectureString = psobject?.Architecture?.ToString();
    492                         if (architectureString == null)
    493                         {
    494                             this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} dependency has no architecture value: {psobject?.PackageFullName ?? "<null>"}");
    495                             continue;
    496                         }
    497 
    498                         architectureString = architectureString.ToLower();
    499 
    500                         if (dependencies.ContainsKey(architectureString))
    501                         {
    502                             this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} {architectureString} dependency satisfied by: {psobject?.PackageFullName ?? "<null>"}");
    503                             dependencies.Remove(architectureString);
    504                         }
    505                     }
    506                     else
    507                     {
    508                         this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} is lower than minimum required version [{minimumVersion}]: {psobject?.PackageFullName ?? "<null>"}");
    509                     }
    510                 }
    511             }
    512         }
    513 
    514         private async Task InstallVCLibsDependenciesFromUriAsync()
    515         {
    516             Dictionary<string, string> vcLibsDependencies = this.GetVCLibsDependencies();
    517             this.FindMissingDependencies(vcLibsDependencies, VCLibsUWPDesktop, VCLibsUWPDesktopVersion);
    518 
    519             if (vcLibsDependencies.Count != 0)
    520             {
    521                 this.pwshCmdlet.Write(StreamType.Verbose, "Couldn't find required VCLibs packages");
    522 
    523                 foreach (var vclibPair in vcLibsDependencies)
    524                 {
    525                     string vclib = vclibPair.Value;
    526                     await this.AddAppxPackageAsUriAsync(vclib, vclib.Substring(vclib.LastIndexOf('/') + 1));
    527                 }
    528             }
    529             else
    530             {
    531                 this.pwshCmdlet.Write(StreamType.Verbose, $"VCLibs are updated.");
    532             }
    533         }
    534 
    535         // Returns a boolean value indicating whether dependencies were successfully installed from the GitHub release assets.
    536         private async Task<bool> InstallDependenciesFromGitHubArchive(string releaseTag)
    537         {
    538             var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
    539             var release = await githubClient.GetReleaseAsync(releaseTag);
    540 
    541             ReleaseAsset? dependenciesJsonAsset = release.TryGetAsset(DependenciesJsonName);
    542             if (dependenciesJsonAsset is null)
    543             {
    544                 return false;
    545             }
    546 
    547             using var dependenciesJsonFile = new TempFile();
    548             await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesJsonAsset.BrowserDownloadUrl, dependenciesJsonFile.FullPath, this.pwshCmdlet);
    549 
    550             using StreamReader r = new StreamReader(dependenciesJsonFile.FullPath);
    551             string json = r.ReadToEnd();
    552             WingetDependencies? wingetDependencies = JsonConvert.DeserializeObject<WingetDependencies>(json);
    553 
    554             if (wingetDependencies is null)
    555             {
    556                 this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to deserialize dependencies json file.");
    557                 return false;
    558             }
    559 
    560             List<string> missingDependencies = new List<string>();
    561             foreach (var dependency in wingetDependencies.Dependencies)
    562             {
    563                 Dictionary<string, string> dependenciesByArch = this.GetDependenciesByArch(dependency);
    564                 this.FindMissingDependencies(dependenciesByArch, dependency.Name, dependency.Version);
    565 
    566                 foreach (var pair in dependenciesByArch)
    567                 {
    568                     missingDependencies.Add(pair.Value);
    569                 }
    570             }
    571 
    572             if (missingDependencies.Count != 0)
    573             {
    574                 using var dependenciesZipFile = new TempFile();
    575                 using var extractedDirectory = new TempDirectory();
    576 
    577                 ReleaseAsset? dependenciesZipAsset = release.TryGetAsset(DependenciesZipName);
    578                 if (dependenciesZipAsset is null)
    579                 {
    580                     this.pwshCmdlet.Write(StreamType.Verbose, $"Dependencies zip asset not found on GitHub asset.");
    581                     return false;
    582                 }
    583 
    584                 await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesZipAsset.BrowserDownloadUrl, dependenciesZipFile.FullPath, this.pwshCmdlet);
    585                 ZipFile.ExtractToDirectory(dependenciesZipFile.FullPath, extractedDirectory.FullDirectoryPath);
    586 
    587                 foreach (var entry in missingDependencies)
    588                 {
    589                     string fullPath = System.IO.Path.Combine(extractedDirectory.FullDirectoryPath, entry);
    590                     if (!File.Exists(fullPath))
    591                     {
    592                         this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}");
    593                         return false;
    594                     }
    595 
    596                     _ = this.ExecuteAppxCmdlet(
    597                             AddAppxPackage,
    598                             new Dictionary<string, object>
    599                             {
    600                             { Path, fullPath },
    601                             { ErrorAction, Stop },
    602                             });
    603                 }
    604             }
    605 
    606             return true;
    607         }
    608 
    609         private Dictionary<string, string> GetVCLibsDependencies()
    610         {
    611             Dictionary<string, string> vcLibsDependencies = new Dictionary<string, string>();
    612 
    613             foreach (var architecture in this.frameworkArchitectures.Value)
    614             {
    615                 switch (architecture)
    616                 {
    617                     case Architecture.X86:
    618                         vcLibsDependencies.Add("x86", VCLibsUWPDesktopX86);
    619                         break;
    620                     case Architecture.X64:
    621                         vcLibsDependencies.Add("x64", VCLibsUWPDesktopX64);
    622                         break;
    623                     case Architecture.Arm64:
    624                         vcLibsDependencies.Add("arm64", VCLibsUWPDesktopArm64);
    625                         break;
    626                     default:
    627                         this.pwshCmdlet.Write(StreamType.Verbose, $"GetVCLibsDependencies: Ignoring {architecture}");
    628                         break;
    629                 }
    630             }
    631 
    632             return vcLibsDependencies;
    633         }
    634 
    635         private async Task InstallUiXamlAsync(string releaseTag)
    636         {
    637             (string xamlPackageName, string xamlReleaseTag) = GetXamlDependencyVersionInfo(releaseTag);
    638             string xamlAssetX64 = string.Format("{0}.x64.appx", xamlPackageName);
    639             string xamlAssetX86 = string.Format("{0}.x86.appx", xamlPackageName);
    640             string xamlAssetArm64 = string.Format("{0}.arm64.appx", xamlPackageName);
    641 
    642             var uiXamlObjs = this.GetAppxObject(xamlPackageName);
    643             if (uiXamlObjs is null)
    644             {
    645                 var githubRelease = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.UiXaml);
    646 
    647                 var xamlRelease = await githubRelease.GetReleaseAsync(xamlReleaseTag);
    648 
    649                 var packagesToInstall = new List<ReleaseAsset>();
    650 
    651                 foreach (var architecture in this.frameworkArchitectures.Value)
    652                 {
    653                     switch (architecture)
    654                     {
    655                         case Architecture.X86:
    656                             packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetX86));
    657                             break;
    658                         case Architecture.X64:
    659                             packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetX64));
    660                             break;
    661                         case Architecture.Arm64:
    662                             packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetArm64));
    663                             break;
    664                         default:
    665                             this.pwshCmdlet.Write(StreamType.Verbose, $"InstallUiXamlAsync: Ignoring {architecture}");
    666                             break;
    667                     }
    668                 }
    669 
    670                 foreach (var package in packagesToInstall)
    671                 {
    672                     await this.AddAppxPackageAsUriAsync(package.BrowserDownloadUrl, package.Name);
    673                 }
    674             }
    675         }
    676 
    677         private async Task AddAppxPackageAsUriAsync(string packageUri, string fileName, Dictionary<string, object>? parameters = null, IList<string>? options = null)
    678         {
    679             try
    680             {
    681                 var thisParams = new Dictionary<string, object>
    682                 {
    683                     { Path, packageUri },
    684                     { ErrorAction, Stop },
    685                 };
    686 
    687                 if (parameters != null)
    688                 {
    689                     foreach (var param in parameters)
    690                     {
    691                         thisParams.Add(param.Key, param.Value);
    692                     }
    693                 }
    694 
    695                 _ = this.ExecuteAppxCmdlet(
    696                         AddAppxPackage,
    697                         thisParams,
    698                         options);
    699             }
    700             catch (RuntimeException e)
    701             {
    702                 // If we couldn't install it via URI, try download and install.
    703                 if (e.ErrorRecord.CategoryInfo.Category == ErrorCategory.OpenError)
    704                 {
    705                     this.pwshCmdlet.Write(StreamType.Verbose, $"Failed adding package [{packageUri}]. Retrying downloading it.");
    706                     await this.DownloadPackageAndAddAsync(packageUri, fileName, options);
    707                 }
    708                 else
    709                 {
    710                     this.pwshCmdlet.Write(StreamType.Error, e.ErrorRecord);
    711                     throw;
    712                 }
    713             }
    714         }
    715 
    716         private async Task DownloadPackageAndAddAsync(string packageUrl, string fileName, IList<string>? options)
    717         {
    718             using var tempFile = new TempFile(fileName: fileName);
    719 
    720             await this.httpClientHelper.DownloadUrlWithProgressAsync(packageUrl, tempFile.FullPath, this.pwshCmdlet);
    721 
    722             _ = this.ExecuteAppxCmdlet(
    723                     AddAppxPackage,
    724                     new Dictionary<string, object>
    725                     {
    726                         { Path, tempFile.FullPath },
    727                         { ErrorAction, Stop },
    728                     },
    729                     options);
    730         }
    731 
    732         private Collection<PSObject> ExecuteAppxCmdlet(string cmdlet, Dictionary<string, object>? parameters = null, IList<string>? options = null)
    733         {
    734             Collection<PSObject> result = new Collection<PSObject>();
    735 
    736             this.pwshCmdlet.ExecuteInPowerShellThread(
    737                 () =>
    738                 {
    739                     var ps = PowerShell.Create(RunspaceMode.CurrentRunspace);
    740 
    741                     // There's a bug in the Appx Module that it can't be loaded from Core in pre 10.0.22453.0 builds without
    742                     // the -UseWindowsPowerShell option. In post 10.0.22453.0 builds there's really no difference between
    743                     // using or not -UseWindowsPowerShell as it will automatically get loaded using WinPSCompatSession remoting session.
    744                     // https://github.com/PowerShell/PowerShell/issues/13138.
    745                     // Set warning action to silently continue to avoid the console with
    746                     // 'Module Appx is loaded in Windows PowerShell using WinPSCompatSession remoting session'
    747 #if !POWERSHELL_WINDOWS
    748                     ps.AddCommand(ImportModule)
    749                       .AddParameter(Name, Appx)
    750                       .AddParameter(UseWindowsPowerShell)
    751                       .AddParameter(WarningAction, SilentlyContinue)
    752                       .AddStatement();
    753 #endif
    754 
    755                     string cmd = cmdlet;
    756                     ps.AddCommand(cmdlet);
    757 
    758                     if (parameters != null)
    759                     {
    760                         foreach (var p in parameters)
    761                         {
    762                             cmd += $" -{p.Key} {p.Value}";
    763                         }
    764 
    765                         ps.AddParameters(parameters);
    766                     }
    767 
    768                     if (options != null)
    769                     {
    770                         foreach (var option in options)
    771                         {
    772                             cmd += $" -{option}";
    773                             ps.AddParameter(option);
    774                         }
    775                     }
    776 
    777                     this.pwshCmdlet.Write(StreamType.Verbose, $"Executing Appx cmdlet {cmd}");
    778                     result = ps.Invoke();
    779                 });
    780 
    781             return result;
    782         }
    783 
    784         private bool IsStubPackageOptionPresent()
    785         {
    786             bool result = false;
    787             this.pwshCmdlet.ExecuteInPowerShellThread(
    788                 () =>
    789                 {
    790                     var ps = PowerShell.Create(RunspaceMode.CurrentRunspace);
    791 
    792 #if !POWERSHELL_WINDOWS
    793                     ps.AddCommand(ImportModule)
    794                       .AddParameter(Name, Appx)
    795                       .AddParameter(UseWindowsPowerShell)
    796                       .AddParameter(WarningAction, SilentlyContinue)
    797                       .AddStatement();
    798 #endif
    799 
    800                     var cmdInfo = ps.AddCommand(GetCommand)
    801                                     .AddParameter(Name, AddAppxPackage)
    802                                     .AddParameter(Module, Appx)
    803                                     .Invoke<CommandInfo>()
    804                                     .FirstOrDefault();
    805 
    806                     result = cmdInfo != null && cmdInfo.Parameters.ContainsKey(StubPackageOption);
    807                 });
    808 
    809             return result;
    810         }
    811     }
    812 }