winget-cli

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

Deployment.cpp (16699B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Public/AppInstallerDeployment.h"
      5 #include "Public/AppInstallerLogging.h"
      6 #include "Public/AppInstallerMsixInfo.h"
      7 #include "Public/AppInstallerRuntime.h"
      8 #include "Public/AppInstallerStrings.h"
      9 
     10 namespace AppInstaller::Deployment
     11 {
     12     using namespace winrt::Windows::Foundation;
     13     using namespace winrt::Windows::Management::Deployment;
     14 
     15     namespace
     16     {
     17         size_t GetDeploymentOperationId()
     18         {
     19             static std::atomic_size_t s_deploymentId = 0;
     20             return s_deploymentId.fetch_add(1);
     21         }
     22 
     23         HRESULT WaitForDeployment(
     24             IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress>& deployOperation,
     25             size_t id,
     26             IProgressCallback& callback,
     27             bool throwOnError = true)
     28         {
     29             AICLI_LOG(Core, Info, << "Begin waiting for operation #" << id);
     30 
     31             AsyncOperationProgressHandler<DeploymentResult, DeploymentProgress> progressCallback(
     32                 [&callback](const IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress>&, DeploymentProgress progress)
     33                 {
     34                     callback.OnProgress(progress.percentage, 100, ProgressType::Percent);
     35                 }
     36             );
     37 
     38             // Set progress callback.
     39             deployOperation.Progress(progressCallback);
     40 
     41             auto removeCancel = callback.SetCancellationFunction([&]() { deployOperation.Cancel(); });
     42 
     43             AICLI_LOG(Core, Info, << "Begin blocking for operation #" << id);
     44 
     45             auto deployResult = deployOperation.get();
     46 
     47             if (!SUCCEEDED(deployResult.ExtendedErrorCode()))
     48             {
     49                 AICLI_LOG(Core, Error, << "Deployment operation #" << id << ": " << Utility::ConvertToUTF8(deployResult.ErrorText()));
     50 
     51                 // Note that while the format string is char*, it gets converted to wchar before being used.
     52                 if (throwOnError)
     53                 {
     54                     THROW_HR_MSG(deployResult.ExtendedErrorCode(), "Operation failed: %ws", deployResult.ErrorText().c_str());
     55                 }
     56                 else
     57                 {
     58                     // Simple return because this path is generally used for recovery cases
     59                     return deployResult.ExtendedErrorCode();
     60                 }
     61             }
     62             else
     63             {
     64                 AICLI_LOG(Core, Info, << "Successfully completed #" << id);
     65             }
     66 
     67             return S_OK;
     68         }
     69 
     70         bool ShouldUseReputationCheck(const Options& options)
     71         {
     72             return options.ExpectedDigests.empty() && !options.SkipReputationCheck;
     73         }
     74 
     75         IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> StartAddPackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options)
     76         {
     77             if (!options.ExpectedDigests.empty())
     78             {
     79                 // Must use API that supports digests
     80                 THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported());
     81 
     82                 AddPackageOptions addPackageOptions;
     83 
     84                 for (const auto& digest : options.ExpectedDigests)
     85                 {
     86                     addPackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second);
     87                 }
     88 
     89                 return packageManager.AddPackageByUriAsync(uri, addPackageOptions);
     90             }
     91             else if (options.SkipReputationCheck)
     92             {
     93                 return packageManager.AddPackageAsync(
     94                     uri,
     95                     nullptr, /*dependencyPackageUris*/
     96                     DeploymentOptions::None,
     97                     nullptr, /*targetVolume*/
     98                     nullptr, /*optionalAndRelatedPackageFamilyNames*/
     99                     nullptr, /*optionalPackageUris*/
    100                     nullptr /*relatedPackageUris*/);
    101             }
    102             else
    103             {
    104                 return packageManager.RequestAddPackageAsync(
    105                     uri,
    106                     nullptr, /*dependencyPackageUris*/
    107                     DeploymentOptions::None,
    108                     nullptr, /*targetVolume*/
    109                     nullptr, /*optionalAndRelatedPackageFamilyNames*/
    110                     nullptr /*relatedPackageUris*/);
    111             }
    112         }
    113 
    114         IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> StartStagePackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options)
    115         {
    116             if (!options.ExpectedDigests.empty())
    117             {
    118                 // Must use API that supports digests
    119                 THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported());
    120 
    121                 StagePackageOptions stagePackageOptions;
    122 
    123                 for (const auto& digest : options.ExpectedDigests)
    124                 {
    125                     stagePackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second);
    126                 }
    127 
    128                 return packageManager.StagePackageByUriAsync(uri, stagePackageOptions);
    129             }
    130             else
    131             {
    132                 return packageManager.StagePackageAsync(
    133                     uri,
    134                     nullptr /*dependencyPackageUris*/);
    135             }
    136         }
    137     }
    138 
    139     std::ostream& operator<<(std::ostream& out, const Options& options)
    140     {
    141         out << " { SkipReputationCheck = " << options.SkipReputationCheck << ", ExpectedDigests = {";
    142 
    143         for (const auto& digest : options.ExpectedDigests)
    144         {
    145             out << " { URI = " << digest.first << ", Digest = " << Utility::ConvertToUTF8(digest.second) << " } ";
    146         }
    147 
    148         out << "} }";
    149 
    150         return out;
    151     }
    152 
    153     void AddPackage(
    154         const winrt::Windows::Foundation::Uri& uri,
    155         const Options& options,
    156         IProgressCallback& callback)
    157     {
    158         size_t id = GetDeploymentOperationId();
    159         AICLI_LOG(Core, Info, << "Starting AddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str()) << " Options: " << options);
    160 
    161         PackageManager packageManager;
    162 
    163         IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deployOperation = StartAddPackage(packageManager, uri, options);
    164 
    165         WaitForDeployment(deployOperation, id, callback);
    166     }
    167 
    168     bool AddPackageWithDeferredFallback(
    169         std::string_view uri,
    170         const Options& options,
    171         IProgressCallback& callback)
    172     {
    173         PackageManager packageManager;
    174 
    175         // In the event of a failure we want to ensure that the package is not left on the system.
    176         // No need for proxy as Deployment won't use it anyways.
    177         Msix::MsixInfo packageInfo{ uri };
    178         std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide();
    179         std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide);
    180         auto removePackage = wil::scope_exit([&]() {
    181             try
    182             {
    183                 ProgressCallback cb;
    184                 RemovePackage(packageFullName, RemovalOptions::None, cb);
    185             }
    186             CATCH_LOG();
    187             });
    188 
    189         Uri uriObject(Utility::ConvertToUTF16(uri));
    190 
    191         if (ShouldUseReputationCheck(options))
    192         {
    193             // The only way to get SmartScreen is to use RequestAddPackageAsync, so we will have to start with that.
    194             size_t id = GetDeploymentOperationId();
    195             AICLI_LOG(Core, Info, << "Starting RequestAddPackageAsync operation #" << id << ": " << uri);
    196 
    197             DeploymentOptions deploymentOptions = DeploymentOptions::None;
    198             // Optimization to keep files if the package is in use. Only available in a newer OS per:
    199             // https://docs.microsoft.com/en-us/uwp/api/Windows.Management.Deployment.DeploymentOptions
    200             if (Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.18362.0" }))
    201             {
    202                 deploymentOptions = DeploymentOptions::RetainFilesOnFailure;
    203             }
    204 
    205             IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deployOperation = packageManager.RequestAddPackageAsync(
    206                 uriObject,
    207                 nullptr, /*dependencyPackageUris*/
    208                 deploymentOptions,
    209                 nullptr, /*targetVolume*/
    210                 nullptr, /*optionalAndRelatedPackageFamilyNames*/
    211                 nullptr /*relatedPackageUris*/);
    212 
    213             HRESULT hr = WaitForDeployment(deployOperation, id, callback, false);
    214 
    215             if (SUCCEEDED(hr))
    216             {
    217                 removePackage.release();
    218                 return false;
    219             }
    220 
    221             THROW_HR_IF(hr, FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE));
    222         }
    223 
    224         // If we are skipping SmartScreen or the package was in use, stage then register the package.
    225         PartialPercentProgressCallback progress{ callback, 100 };
    226         progress.SetRange(0, 95);
    227         {
    228             size_t id = GetDeploymentOperationId();
    229             AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options);
    230 
    231             IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> stageOperation = StartStagePackage(packageManager, uriObject, options);
    232             WaitForDeployment(stageOperation, id, progress);
    233         }
    234 
    235         bool registrationDeferred = false;
    236         progress.SetRange(95, 100);
    237         {
    238             size_t id = GetDeploymentOperationId();
    239             AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName);
    240 
    241             IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> registerOperation =
    242                 packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None);
    243             HRESULT hr = WaitForDeployment(registerOperation, id, progress, false);
    244 
    245             if (hr == HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE))
    246             {
    247                 registrationDeferred = true;
    248             }
    249             else
    250             {
    251                 THROW_IF_FAILED(hr);
    252             }
    253         }
    254 
    255         removePackage.release();
    256         return registrationDeferred;
    257     }
    258 
    259     void RemovePackage(
    260         std::string_view packageFullName,
    261         RemovalOptions options,
    262         IProgressCallback& callback)
    263     {
    264         size_t id = GetDeploymentOperationId();
    265         AICLI_LOG(Core, Info, << "Starting RemovePackage operation #" << id << ": " << packageFullName);
    266 
    267         PackageManager packageManager;
    268         winrt::hstring fullName = Utility::ConvertToUTF16(packageFullName).c_str();
    269         auto deployOperation = packageManager.RemovePackageAsync(fullName, options);
    270 
    271         WaitForDeployment(deployOperation, id, callback);
    272     }
    273 
    274     bool AddPackageMachineScope(
    275         std::string_view uri,
    276         const Options& options,
    277         IProgressCallback& callback)
    278     {
    279         PackageManager packageManager;
    280 
    281         // In the event of a failure we want to ensure that the package is not left on the system.
    282         // No need for proxy as Deployment won't use it anyways.
    283         Msix::MsixInfo packageInfo{ uri };
    284         std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide();
    285         std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide);
    286         std::string packageFamilyName = Msix::GetPackageFamilyNameFromFullName(packageFullName);
    287         auto removePackage = wil::scope_exit([&]() {
    288             try
    289             {
    290                 ProgressCallback cb;
    291                 RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, cb);
    292             }
    293             CATCH_LOG();
    294             });
    295 
    296         Uri uriObject(Utility::ConvertToUTF16(uri));
    297         PartialPercentProgressCallback progress{ callback, 100 };
    298 
    299         // First stage package contents
    300         progress.SetRange(0, 90);
    301         {
    302             size_t id = GetDeploymentOperationId();
    303             AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options);
    304 
    305             IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> stageOperation = StartStagePackage(packageManager, uriObject, options);
    306             WaitForDeployment(stageOperation, id, progress);
    307         }
    308 
    309         // Provision for all users
    310         progress.SetRange(90, 95);
    311         {
    312             size_t id = GetDeploymentOperationId();
    313             AICLI_LOG(Core, Info, << "Starting ProvisionPackage operation #" << id << ": " << packageFamilyName);
    314 
    315             winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str();
    316             auto deployOperation = packageManager.ProvisionPackageForAllUsersAsync(familyName);
    317 
    318             WaitForDeployment(deployOperation, id, progress);
    319         }
    320 
    321         // Try registration as best effort, operation is considered successful as long as provisioning is successful.
    322         progress.SetRange(95, 100);
    323         bool registrationDeferred = false;
    324         if (Runtime::IsRunningAsSystem())
    325         {
    326             // Packages cannot be registered under local system, just return registration deferred
    327             registrationDeferred = true;
    328         }
    329         else
    330         {
    331             try
    332             {
    333                 size_t id = GetDeploymentOperationId();
    334                 AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName);
    335 
    336                 IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> registerOperation =
    337                     packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None);
    338                 WaitForDeployment(registerOperation, id, progress);
    339             }
    340             catch (...)
    341             {
    342                 registrationDeferred = true;
    343             }
    344         }
    345 
    346         progress.OnProgress(100, 100, ProgressType::Percent);
    347         removePackage.release();
    348         return registrationDeferred;
    349     }
    350 
    351     void RemovePackageMachineScope(
    352         std::string_view packageFamilyName,
    353         std::string_view packageFullName,
    354         IProgressCallback& callback)
    355     {
    356         PartialPercentProgressCallback progress{ callback, 100 };
    357 
    358         // Deprovision first
    359         progress.SetRange(0, 5);
    360         {
    361             size_t id = GetDeploymentOperationId();
    362             AICLI_LOG(Core, Info, << "Starting DeprovisionPackage operation #" << id << ": " << packageFamilyName);
    363 
    364             PackageManager packageManager;
    365             winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str();
    366             auto deployOperation = packageManager.DeprovisionPackageForAllUsersAsync(familyName);
    367 
    368             WaitForDeployment(deployOperation, id, progress);
    369         }
    370 
    371         // Remove for all users
    372         progress.SetRange(5, 100);
    373         {
    374             RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, progress);
    375         }
    376     }
    377 
    378     bool IsRegistered(std::string_view packageFamilyName)
    379     {
    380         std::wstring wideFamilyName = Utility::ConvertToUTF16(packageFamilyName);
    381 
    382         PackageManager packageManager;
    383         auto packages = packageManager.FindPackagesForUser({}, wideFamilyName);
    384 
    385         return packages.begin() != packages.end();
    386     }
    387 
    388     void RegisterPackage(
    389         std::string_view packageFamilyName,
    390         IProgressCallback& callback)
    391     {
    392         size_t id = GetDeploymentOperationId();
    393         AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFamilyName);
    394 
    395         PackageManager packageManager;
    396         winrt::hstring packageFamilyNameWide = Utility::ConvertToUTF16(packageFamilyName).c_str();
    397         auto deployOperation = packageManager.RegisterPackageByFamilyNameAsync(packageFamilyNameWide, nullptr, DeploymentOptions::None, nullptr, nullptr);
    398 
    399         WaitForDeployment(deployOperation, id, callback);
    400     }
    401 
    402     bool IsExpectedDigestsSupported()
    403     {
    404         static bool s_IsExpectedDigestsSupported = Metadata::ApiInformation::IsPropertyPresent(winrt::name_of<AddPackageOptions>(), L"ExpectedDigests");
    405         return s_IsExpectedDigestsSupported;
    406     }
    407 }