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 }