InstallFlow.cpp (61710B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "WorkflowCommon.h" 5 #include "TestHooks.h" 6 #include <AppInstallerFileLogger.h> 7 #include <AppInstallerStrings.h> 8 #include <AppInstallerSynchronization.h> 9 #include <Commands/InstallCommand.h> 10 #include <Commands/UninstallCommand.h> 11 #include <winget/AdminSettings.h> 12 #include <winget/PathVariable.h> 13 #include <winget/Settings.h> 14 #include <winget/ManifestYamlParser.h> 15 #include <Workflows/ArchiveFlow.h> 16 #include <Workflows/DownloadFlow.h> 17 #include <Workflows/MsiInstallFlow.h> 18 #include <Workflows/ShellExecuteInstallerHandler.h> 19 20 using namespace winrt::Windows::Foundation; 21 using namespace TestCommon; 22 using namespace AppInstaller::CLI; 23 using namespace AppInstaller::CLI::Execution; 24 using namespace AppInstaller::CLI::Workflow; 25 using namespace AppInstaller::Logging; 26 using namespace AppInstaller::Manifest; 27 using namespace AppInstaller::Settings; 28 using namespace AppInstaller::Utility; 29 using namespace AppInstaller::Utility::literals; 30 31 void OverrideForDirectMsi(TestContext& context) 32 { 33 OverrideForCheckExistingInstaller(context); 34 35 context.Override({ DownloadInstallerFile, [](TestContext& context) 36 { 37 context.Add<Data::DownloadHashInfo>({ {}, {} }); 38 // We don't have an msi installer for tests, but we won't execute it anyway 39 context.Add<Data::InstallerPath>(TestDataFile("AppInstallerTestExeInstaller.exe")); 40 } }); 41 42 context.Override({ RenameDownloadedInstaller, [](TestContext&) 43 { 44 } }); 45 46 OverrideForUpdateInstallerMotw(context); 47 48 context.Override({ DirectMSIInstallImpl, [](TestContext& context) 49 { 50 // Write out the install command 51 std::filesystem::path temp = std::filesystem::temp_directory_path(); 52 temp /= "TestMsiInstalled.txt"; 53 std::ofstream file(temp, std::ofstream::out); 54 file << context.Get<Execution::Data::InstallerArgs>(); 55 file.close(); 56 57 context.Add<Execution::Data::OperationReturnCode>(0); 58 } }); 59 } 60 61 TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow][workflow]") 62 { 63 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 64 65 std::ostringstream installOutput; 66 TestContext context{ installOutput, std::cin }; 67 auto previousThreadGlobals = context.SetForCurrentThread(); 68 OverrideForShellExecute(context); 69 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); 70 71 InstallCommand install({}); 72 install.Execute(context); 73 INFO(installOutput.str()); 74 75 // Verify Installer is called and parameters are passed in. 76 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 77 std::ifstream installResultFile(installResultPath.GetPath()); 78 REQUIRE(installResultFile.is_open()); 79 std::string installResultStr; 80 std::getline(installResultFile, installResultStr); 81 REQUIRE(installResultStr.find("/custom") != std::string::npos); 82 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 83 } 84 85 TEST_CASE("InstallFlow_RenameFromEncodedUrl", "[InstallFlow][workflow]") 86 { 87 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 88 89 std::ostringstream installOutput; 90 TestContext context{ installOutput, std::cin }; 91 auto previousThreadGlobals = context.SetForCurrentThread(); 92 OverrideForCheckExistingInstaller(context); 93 context.Override({ DownloadInstallerFile, [](TestContext& context) 94 { 95 context.Add<Data::DownloadHashInfo>({ {}, {} }); 96 auto installerPath = std::filesystem::temp_directory_path(); 97 installerPath /= "EncodedUrlTest.exe"; 98 std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); 99 context.Add<Data::InstallerPath>(installerPath); 100 } }); 101 OverrideForUpdateInstallerMotw(context); 102 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_EncodedUrl.yaml").GetPath().u8string()); 103 104 InstallCommand install({}); 105 install.Execute(context); 106 INFO(installOutput.str()); 107 108 // Verify Installer is called and parameters are passed in. 109 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 110 std::ifstream installResultFile(installResultPath.GetPath()); 111 REQUIRE(installResultFile.is_open()); 112 std::string installResultStr; 113 std::getline(installResultFile, installResultStr); 114 REQUIRE(installResultStr.find("/encodedUrl") != std::string::npos); 115 } 116 117 TEST_CASE("InstallFlow_RenameFromInvalidFileCharacterUrl", "[InstallFlow][workflow]") 118 { 119 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 120 121 std::ostringstream installOutput; 122 TestContext context{ installOutput, std::cin }; 123 auto previousThreadGlobals = context.SetForCurrentThread(); 124 OverrideForCheckExistingInstaller(context); 125 context.Override({ DownloadInstallerFile, [](TestContext& context) 126 { 127 context.Add<Data::DownloadHashInfo>({ {}, {} }); 128 auto installerPath = std::filesystem::temp_directory_path(); 129 installerPath /= "InvalidFileCharacterUrlTest.exe"; 130 std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); 131 context.Add<Data::InstallerPath>(installerPath); 132 } }); 133 OverrideForUpdateInstallerMotw(context); 134 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InvalidFileCharacterUrl.yaml").GetPath().u8string()); 135 136 InstallCommand install({}); 137 install.Execute(context); 138 INFO(installOutput.str()); 139 140 // Verify Installer is called and parameters are passed in. 141 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 142 std::ifstream installResultFile(installResultPath.GetPath()); 143 REQUIRE(installResultFile.is_open()); 144 std::string installResultStr; 145 std::getline(installResultFile, installResultStr); 146 REQUIRE(installResultStr.find("/invalidFileCharacterUrl") != std::string::npos); 147 } 148 149 TEST_CASE("InstallFlowNonZeroExitCode", "[InstallFlow][workflow]") 150 { 151 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 152 153 std::ostringstream installOutput; 154 TestContext context{ installOutput, std::cin }; 155 auto previousThreadGlobals = context.SetForCurrentThread(); 156 OverrideForShellExecute(context); 157 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NonZeroExitCode.yaml").GetPath().u8string()); 158 159 InstallCommand install({}); 160 install.Execute(context); 161 INFO(installOutput.str()); 162 163 // Verify Installer is called and parameters are passed in. 164 REQUIRE(context.GetTerminationHR() == S_OK); 165 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 166 std::ifstream installResultFile(installResultPath.GetPath()); 167 REQUIRE(installResultFile.is_open()); 168 std::string installResultStr; 169 std::getline(installResultFile, installResultStr); 170 REQUIRE(installResultStr.find("/ExitCode 0x80070005") != std::string::npos); 171 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 172 } 173 174 TEST_CASE("InstallFlow_InstallationNotes", "[InstallFlow][workflow]") 175 { 176 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 177 178 std::ostringstream installOutput; 179 TestContext context{ installOutput, std::cin }; 180 auto previousThreadGlobals = context.SetForCurrentThread(); 181 OverrideForShellExecute(context); 182 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallationNotes.yaml").GetPath().u8string()); 183 184 InstallCommand install({}); 185 install.Execute(context); 186 INFO(installOutput.str()); 187 188 // Verify installation notes are displayed 189 REQUIRE(context.GetTerminationHR() == S_OK); 190 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 191 REQUIRE(installOutput.str().find("testInstallationNotes") != std::string::npos); 192 } 193 194 TEST_CASE("InstallFlow_UnsupportedArguments_Warn", "[InstallFlow][workflow]") 195 { 196 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 197 TestCommon::TempDirectory tempDirectory("TempDirectory", false); 198 199 std::ostringstream installOutput; 200 TestContext context{ installOutput, std::cin }; 201 auto previousThreadGlobals = context.SetForCurrentThread(); 202 OverrideForShellExecute(context); 203 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); 204 context.Args.AddArg(Execution::Args::Type::Log, tempDirectory); 205 206 InstallCommand install({}); 207 context.SetExecutingCommand(&install); 208 install.Execute(context); 209 INFO(installOutput.str()); 210 211 // Verify unsupported arguments warn message is shown 212 REQUIRE(context.GetTerminationHR() == S_OK); 213 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 214 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); 215 REQUIRE(installOutput.str().find("-o,--log") != std::string::npos); 216 } 217 218 TEST_CASE("InstallFlow_UnsupportedArguments_Error", "[InstallFlow][workflow]") 219 { 220 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 221 TestCommon::TempDirectory tempDirectory("TempDirectory", false); 222 223 std::ostringstream installOutput; 224 TestContext context{ installOutput, std::cin }; 225 auto previousThreadGlobals = context.SetForCurrentThread(); 226 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); 227 context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); 228 229 InstallCommand install({}); 230 context.SetExecutingCommand(&install); 231 install.Execute(context); 232 INFO(installOutput.str()); 233 234 // Verify unsupported arguments error message is shown 235 REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); 236 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 237 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); 238 REQUIRE(installOutput.str().find("-l,--location") != std::string::npos); 239 } 240 241 TEST_CASE("InstallFlow_UnsupportedArguments_NotProvided") 242 { 243 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 244 245 std::ostringstream installOutput; 246 TestContext context{ installOutput, std::cin }; 247 auto previousThreadGlobals = context.SetForCurrentThread(); 248 OverrideForShellExecute(context); 249 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); 250 251 InstallCommand install({}); 252 context.SetExecutingCommand(&install); 253 install.Execute(context); 254 INFO(installOutput.str()); 255 256 // Verify unsupported arguments error message is not shown when not provided 257 REQUIRE(context.GetTerminationHR() == S_OK); 258 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 259 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) == std::string::npos); 260 REQUIRE(installOutput.str().find("-o,--log") == std::string::npos); 261 REQUIRE(installOutput.str().find("-l,--location") == std::string::npos); 262 } 263 264 TEST_CASE("InstallFlow_ExpectedReturnCodes", "[InstallFlow][workflow]") 265 { 266 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 267 268 std::ostringstream installOutput; 269 TestContext context{ installOutput, std::cin }; 270 auto previousThreadGlobals = context.SetForCurrentThread(); 271 OverrideForShellExecute(context); 272 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml").GetPath().u8string()); 273 context.Args.AddArg(Execution::Args::Type::Override, "/ExitCode 8"sv); 274 275 InstallCommand install({}); 276 install.Execute(context); 277 INFO(installOutput.str()); 278 279 // Verify install failed with the right message 280 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT); 281 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 282 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallFlowReturnCodeContactSupport).get()) != std::string::npos); 283 REQUIRE(installOutput.str().find("https://TestReturnResponseUrl") != std::string::npos); 284 } 285 286 TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow][workflow]") 287 { 288 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 289 290 std::ostringstream installOutput; 291 TestContext context{ installOutput, std::cin }; 292 auto previousThreadGlobals = context.SetForCurrentThread(); 293 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yaml").GetPath().u8string()); 294 295 InstallCommand install({}); 296 install.Execute(context); 297 INFO(installOutput.str()); 298 299 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); 300 301 // Verify Installer was not called 302 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 303 } 304 305 TEST_CASE("InstallFlow_Zip_Exe", "[InstallFlow][workflow]") 306 { 307 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 308 309 std::ostringstream installOutput; 310 TestContext context{ installOutput, std::cin }; 311 auto previousThreadGlobals = context.SetForCurrentThread(); 312 OverrideForShellExecute(context); 313 OverrideForExtractInstallerFromArchive(context); 314 OverrideForVerifyAndSetNestedInstaller(context); 315 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); 316 317 TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); 318 319 InstallCommand install({}); 320 install.Execute(context); 321 INFO(installOutput.str()); 322 323 // Verify Installer is called and parameters are passed in. 324 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 325 std::ifstream installResultFile(installResultPath.GetPath()); 326 REQUIRE(installResultFile.is_open()); 327 std::string installResultStr; 328 std::getline(installResultFile, installResultStr); 329 REQUIRE(installResultStr.find("/custom") != std::string::npos); 330 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 331 } 332 333 TEST_CASE("InstallFlow_Zip_BadRelativePath", "[InstallFlow][workflow]") 334 { 335 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 336 337 std::ostringstream installOutput; 338 TestContext context{ installOutput, std::cin }; 339 auto previousThreadGlobals = context.SetForCurrentThread(); 340 OverrideForShellExecute(context); 341 OverrideForExtractInstallerFromArchive(context); 342 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); 343 344 TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); 345 346 InstallCommand install({}); 347 install.Execute(context); 348 INFO(installOutput.str()); 349 350 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND); 351 352 // Verify Installer was not called 353 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 354 auto relativePath = context.Get<Execution::Data::InstallerPath>().parent_path() / "extracted" / "relativeFilePath"; 355 auto expectedMessage = Resource::String::NestedInstallerNotFound(AppInstaller::Utility::LocIndString{ relativePath.u8string()}); 356 REQUIRE(installOutput.str().find(Resource::LocString(expectedMessage).get()) != std::string::npos); 357 } 358 359 TEST_CASE("InstallFlow_Zip_MissingNestedInstaller", "[InstallFlow][workflow]") 360 { 361 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 362 363 std::ostringstream installOutput; 364 TestContext context{ installOutput, std::cin }; 365 auto previousThreadGlobals = context.SetForCurrentThread(); 366 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_MissingNestedInstaller.yaml").GetPath().u8string()); 367 368 InstallCommand install({}); 369 install.Execute(context); 370 INFO(installOutput.str()); 371 372 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); 373 374 // Verify Installer was not called 375 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 376 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NestedInstallerNotSpecified).get()) != std::string::npos); 377 } 378 379 TEST_CASE("InstallFlow_Zip_UnsupportedNestedInstaller", "[InstallFlow][workflow]") 380 { 381 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 382 383 std::ostringstream installOutput; 384 TestContext context{ installOutput, std::cin }; 385 auto previousThreadGlobals = context.SetForCurrentThread(); 386 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_UnsupportedNestedInstaller.yaml").GetPath().u8string()); 387 388 InstallCommand install({}); 389 install.Execute(context); 390 INFO(installOutput.str()); 391 392 REQUIRE_TERMINATED_WITH(context, ERROR_NOT_SUPPORTED); 393 394 // Verify Installer was not called 395 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 396 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NestedInstallerNotSupported).get()) != std::string::npos); 397 } 398 399 TEST_CASE("InstallFlow_Zip_MultipleNonPortableNestedInstallers", "[InstallFlow][workflow]") 400 { 401 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 402 403 std::ostringstream installOutput; 404 TestContext context{ installOutput, std::cin }; 405 auto previousThreadGlobals = context.SetForCurrentThread(); 406 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_MultipleNonPortableNestedInstallers.yaml").GetPath().u8string()); 407 408 InstallCommand install({}); 409 install.Execute(context); 410 INFO(installOutput.str()); 411 412 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); 413 414 // Verify Installer was not called 415 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 416 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::MultipleNonPortableNestedInstallersSpecified).get()) != std::string::npos); 417 } 418 419 TEST_CASE("InstallFlow_Zip_ArchiveScanFailed", "[InstallFlow][workflow]") 420 { 421 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 422 423 std::ostringstream installOutput; 424 TestContext context{ installOutput, std::cin }; 425 auto previousThreadGlobals = context.SetForCurrentThread(); 426 OverrideForShellExecute(context); 427 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); 428 429 TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); 430 431 InstallCommand install({}); 432 install.Execute(context); 433 INFO(installOutput.str()); 434 435 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED); 436 437 // Verify Installer was not called 438 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 439 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScan).get()) != std::string::npos); 440 } 441 442 TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingDisabled", "[InstallFlow][workflow]") 443 { 444 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 445 446 std::ostringstream installOutput; 447 TestContext context{ installOutput, std::cin }; 448 auto previousThreadGlobals = context.SetForCurrentThread(); 449 OverrideForShellExecute(context); 450 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); 451 context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); 452 453 DisableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); 454 455 TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); 456 457 InstallCommand install({}); 458 install.Execute(context); 459 INFO(installOutput.str()); 460 461 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED); 462 463 // Verify Installer was not called 464 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 465 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScan).get()) != std::string::npos); 466 } 467 468 TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingEnabled", "[InstallFlow][workflow]") 469 { 470 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 471 472 std::ostringstream installOutput; 473 TestContext context{ installOutput, std::cin }; 474 auto previousThreadGlobals = context.SetForCurrentThread(); 475 OverrideForShellExecute(context); 476 OverrideForExtractInstallerFromArchive(context); 477 OverrideForVerifyAndSetNestedInstaller(context); 478 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); 479 context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); 480 481 EnableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); 482 483 TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); 484 485 InstallCommand install({}); 486 install.Execute(context); 487 INFO(installOutput.str()); 488 489 // Verify override message is displayed to the user. 490 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScanOverridden).get()) != std::string::npos); 491 492 // Verify Installer is called and parameters are passed in. 493 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 494 std::ifstream installResultFile(installResultPath.GetPath()); 495 REQUIRE(installResultFile.is_open()); 496 std::string installResultStr; 497 std::getline(installResultFile, installResultStr); 498 REQUIRE(installResultStr.find("/custom") != std::string::npos); 499 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 500 } 501 502 TEST_CASE("ExtractInstallerFromArchive_InvalidZip", "[InstallFlow][workflow]") 503 { 504 std::ostringstream installOutput; 505 TestContext context{ installOutput, std::cin }; 506 auto previousThreadGlobals = context.SetForCurrentThread(); 507 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); 508 context.Add<Data::Manifest>(manifest); 509 context.Add<Data::Installer>(manifest.Installers.at(0)); 510 511 // Provide an invalid zip file which should be handled appropriately. 512 context.Add<Data::InstallerPath>(TestDataFile("AppInstallerTestExeInstaller.exe")); 513 context << ExtractFilesFromArchive; 514 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); 515 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveFailed).get()) != std::string::npos); 516 } 517 518 TEST_CASE("ExtractInstallerFromArchiveWithTar", "[InstallFlow][workflow]") 519 { 520 TestCommon::TestUserSettings testSettings; 521 testSettings.Set<Setting::ArchiveExtractionMethod>(AppInstaller::Archive::ExtractionMethod::Tar); 522 523 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 524 525 std::ostringstream installOutput; 526 TestContext context{ installOutput, std::cin }; 527 auto previousThreadGlobals = context.SetForCurrentThread(); 528 529 OverrideForShellExecute(context); 530 OverrideForVerifyAndSetNestedInstaller(context); 531 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); 532 533 TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); 534 TestHook::SetExtractArchiveWithTarResult_Override setExtractArchiveWithTarResultOverride(ERROR_SUCCESS); 535 536 InstallCommand install({}); 537 install.Execute(context); 538 INFO(installOutput.str()); 539 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveSucceeded).get()) != std::string::npos); 540 541 // Verify Installer is called and parameters are passed in. 542 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 543 std::ifstream installResultFile(installResultPath.GetPath()); 544 REQUIRE(installResultFile.is_open()); 545 std::string installResultStr; 546 std::getline(installResultFile, installResultStr); 547 REQUIRE(installResultStr.find("/custom") != std::string::npos); 548 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 549 } 550 551 TEST_CASE("ExtractInstallerFromArchiveWithTar_InvalidZip", "[InstallFlow][workflow]") 552 { 553 TestCommon::TestUserSettings testSettings; 554 testSettings.Set<Setting::ArchiveExtractionMethod>(AppInstaller::Archive::ExtractionMethod::Tar); 555 556 std::ostringstream installOutput; 557 TestContext context{ installOutput, std::cin }; 558 auto previousThreadGlobals = context.SetForCurrentThread(); 559 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); 560 context.Add<Data::Manifest>(manifest); 561 context.Add<Data::Installer>(manifest.Installers.at(0)); 562 563 // Provide an invalid zip file which should be handled appropriately. 564 context.Add<Data::InstallerPath>(TestDataFile("AppInstallerTestExeInstaller.exe")); 565 context << ExtractFilesFromArchive; 566 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); 567 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveFailed).get()) != std::string::npos); 568 } 569 570 TEST_CASE("MSStoreInstallFlowWithTestManifest", "[InstallFlow][workflow]") 571 { 572 TestCommon::TempFile installResultPath("TestMSStoreInstalled.txt"); 573 574 std::ostringstream installOutput; 575 TestContext context{ installOutput, std::cin }; 576 auto previousThreadGlobals = context.SetForCurrentThread(); 577 OverrideForMSStore(context, false); 578 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore.yaml").GetPath().u8string()); 579 580 InstallCommand install({}); 581 install.Execute(context); 582 INFO(installOutput.str()); 583 584 // Verify Installer is called and parameters are passed in. 585 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 586 std::ifstream installResultFile(installResultPath.GetPath()); 587 REQUIRE(installResultFile.is_open()); 588 std::string installResultStr; 589 std::getline(installResultFile, installResultStr); 590 REQUIRE(installResultStr.find("9WZDNCRFJ364") != std::string::npos); 591 } 592 593 TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow][workflow]") 594 { 595 TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); 596 597 std::ostringstream installOutput; 598 TestContext context{ installOutput, std::cin }; 599 auto previousThreadGlobals = context.SetForCurrentThread(); 600 OverrideForMSIX(context); 601 OverrideForUpdateInstallerMotw(context); 602 // Todo: point to files from our repo when the repo goes public 603 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yaml").GetPath().u8string()); 604 605 InstallCommand install({}); 606 install.Execute(context); 607 INFO(installOutput.str()); 608 609 // Verify Installer is called and a local file is used as package Uri. 610 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 611 std::ifstream installResultFile(installResultPath.GetPath()); 612 REQUIRE(installResultFile.is_open()); 613 std::string installResultStr; 614 std::getline(installResultFile, installResultStr); 615 Uri uri = Uri(ConvertToUTF16(installResultStr)); 616 REQUIRE(uri.SchemeName() == L"file"); 617 } 618 619 TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow][workflow]") 620 { 621 TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); 622 623 std::ostringstream installOutput; 624 TestContext context{ installOutput, std::cin }; 625 auto previousThreadGlobals = context.SetForCurrentThread(); 626 OverrideForMSIX(context); 627 OverrideForCheckExistingInstaller(context); 628 // Todo: point to files from our repo when the repo goes public 629 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml").GetPath().u8string()); 630 631 InstallCommand install({}); 632 install.Execute(context); 633 INFO(installOutput.str()); 634 635 // Verify Installer is called and a http address is used as package Uri. 636 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 637 std::ifstream installResultFile(installResultPath.GetPath()); 638 REQUIRE(installResultFile.is_open()); 639 std::string installResultStr; 640 std::getline(installResultFile, installResultStr); 641 Uri uri = Uri(ConvertToUTF16(installResultStr)); 642 REQUIRE(uri.SchemeName() == L"https"); 643 } 644 645 TEST_CASE("MsiInstallFlow_DirectMsi", "[InstallFlow][workflow]") 646 { 647 TestCommon::TempFile installResultPath("TestMsiInstalled.txt"); 648 649 TestCommon::TestUserSettings testSettings; 650 testSettings.Set<Setting::EFDirectMSI>(true); 651 652 std::ostringstream installOutput; 653 TestContext context{ installOutput, std::cin }; 654 auto previousThreadGlobals = context.SetForCurrentThread(); 655 OverrideForDirectMsi(context); 656 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml").GetPath().u8string()); 657 context.Args.AddArg(Execution::Args::Type::Silent); 658 659 InstallCommand install({}); 660 install.Execute(context); 661 INFO(installOutput.str()); 662 663 // Verify Installer is called and parameters are passed in. 664 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 665 std::ifstream installResultFile(installResultPath.GetPath()); 666 REQUIRE(installResultFile.is_open()); 667 std::string installResultStr; 668 std::getline(installResultFile, installResultStr); 669 REQUIRE(installResultStr.find("/quiet") != std::string::npos); 670 } 671 672 TEST_CASE("InstallFlow_Portable", "[InstallFlow][workflow]") 673 { 674 TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); 675 TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); 676 677 std::ostringstream installOutput; 678 TestContext context{ installOutput, std::cin }; 679 auto previousThreadGlobals = context.SetForCurrentThread(); 680 OverrideForPortableInstallFlow(context); 681 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); 682 context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); 683 684 InstallCommand install({}); 685 install.Execute(context); 686 INFO(installOutput.str()); 687 688 REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); 689 } 690 691 TEST_CASE("InstallFlow_Portable_SymlinkCreationFail", "[InstallFlow][workflow]") 692 { 693 TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); 694 std::ostringstream installOutput; 695 TestContext installContext{ installOutput, std::cin }; 696 auto PreviousThreadGlobals = installContext.SetForCurrentThread(); 697 OverridePortableInstaller(installContext); 698 TestHook::SetCreateSymlinkResult_Override createSymlinkResultOverride(false); 699 const auto& targetDirectory = tempDirectory.GetPath(); 700 const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; 701 installContext.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); 702 installContext.Args.AddArg(Execution::Args::Type::InstallLocation, targetDirectory.u8string()); 703 installContext.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); 704 705 InstallCommand install({}); 706 install.Execute(installContext); 707 708 { 709 INFO(installOutput.str()); 710 711 // Use CHECK to allow the uninstall to still occur 712 CHECK(std::filesystem::exists(portableTargetPath)); 713 CHECK(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); 714 } 715 716 // Perform uninstall 717 std::ostringstream uninstallOutput; 718 TestContext uninstallContext{ uninstallOutput, std::cin }; 719 auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); 720 uninstallContext.Args.AddArg(Execution::Args::Type::Name, "AppInstaller Test Portable Exe"sv); 721 uninstallContext.Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); 722 723 UninstallCommand uninstall({}); 724 uninstall.Execute(uninstallContext); 725 INFO(uninstallOutput.str()); 726 REQUIRE_FALSE(std::filesystem::exists(portableTargetPath)); 727 } 728 729 TEST_CASE("PortableInstallFlow_UserScope", "[InstallFlow][workflow]") 730 { 731 TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); 732 TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); 733 734 std::ostringstream installOutput; 735 TestContext context{ installOutput, std::cin }; 736 auto previousThreadGlobals = context.SetForCurrentThread(); 737 OverrideForPortableInstallFlow(context); 738 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); 739 context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); 740 context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); 741 742 InstallCommand install({}); 743 install.Execute(context); 744 INFO(installOutput.str()); 745 746 REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); 747 } 748 749 TEST_CASE("PortableInstallFlow_MachineScope", "[InstallFlow][workflow]") 750 { 751 if (!AppInstaller::Runtime::IsRunningAsAdmin()) 752 { 753 WARN("Test requires admin privilege. Skipped."); 754 return; 755 } 756 757 TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); 758 TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); 759 760 std::ostringstream installOutput; 761 TestContext context{ installOutput, std::cin }; 762 auto previousThreadGlobals = context.SetForCurrentThread(); 763 OverrideForPortableInstallFlow(context); 764 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); 765 context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); 766 context.Args.AddArg(Execution::Args::Type::InstallScope, "machine"sv); 767 768 InstallCommand install({}); 769 install.Execute(context); 770 INFO(installOutput.str()); 771 REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); 772 } 773 774 TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow][workflow]") 775 { 776 { 777 std::ostringstream installOutput; 778 TestContext context{ installOutput, std::cin }; 779 auto previousThreadGlobals = context.SetForCurrentThread(); 780 // Default Msi type with no args passed in, no switches specified in manifest 781 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml")); 782 context.Add<Data::Manifest>(manifest); 783 context.Add<Data::Installer>(manifest.Installers.at(0)); 784 context.Add<Data::InstallerPath>(TestDataFile("AppInstallerTestExeInstaller.exe")); 785 context << GetInstallerArgs; 786 std::string installerArgs = context.Get<Data::InstallerArgs>(); 787 REQUIRE(installerArgs.find("/passive") != std::string::npos); 788 REQUIRE(installerArgs.find(FileLogger::DefaultPrefix()) != std::string::npos); 789 REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); 790 REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); 791 } 792 793 { 794 std::ostringstream installOutput; 795 TestContext context{ installOutput, std::cin }; 796 auto previousThreadGlobals = context.SetForCurrentThread(); 797 // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest 798 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml")); 799 context.Args.AddArg(Execution::Args::Type::Silent); 800 context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); 801 context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); 802 context.Add<Data::Manifest>(manifest); 803 context.Add<Data::Installer>(manifest.Installers.at(0)); 804 context << GetInstallerArgs; 805 std::string installerArgs = context.Get<Data::InstallerArgs>(); 806 REQUIRE(installerArgs.find("/quiet") != std::string::npos); 807 REQUIRE(installerArgs.find("/log \"MyLog.log\"") != std::string::npos); 808 REQUIRE(installerArgs.find("TARGETDIR=\"MyDir\"") != std::string::npos); 809 } 810 811 { 812 std::ostringstream installOutput; 813 TestContext context{ installOutput, std::cin }; 814 auto previousThreadGlobals = context.SetForCurrentThread(); 815 // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest 816 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yaml")); 817 context.Args.AddArg(Execution::Args::Type::Silent); 818 context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); 819 context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); 820 context.Add<Data::Manifest>(manifest); 821 context.Add<Data::Installer>(manifest.Installers.at(0)); 822 context << GetInstallerArgs; 823 std::string installerArgs = context.Get<Data::InstallerArgs>(); 824 REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest 825 REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest 826 REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest 827 REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest 828 } 829 830 { 831 std::ostringstream installOutput; 832 TestContext context{ installOutput, std::cin }; 833 auto previousThreadGlobals = context.SetForCurrentThread(); 834 // Default Inno type with no args passed in, no switches specified in manifest 835 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml")); 836 context.Add<Data::Manifest>(manifest); 837 context.Add<Data::Installer>(manifest.Installers.at(0)); 838 context.Add<Data::InstallerPath>(TestDataFile("AppInstallerTestExeInstaller.exe")); 839 context << GetInstallerArgs; 840 std::string installerArgs = context.Get<Data::InstallerArgs>(); 841 REQUIRE(installerArgs.find("/SILENT") != std::string::npos); 842 REQUIRE(installerArgs.find(FileLogger::DefaultPrefix()) != std::string::npos); 843 REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); 844 REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); 845 } 846 847 { 848 std::ostringstream installOutput; 849 TestContext context{ installOutput, std::cin }; 850 auto previousThreadGlobals = context.SetForCurrentThread(); 851 // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest 852 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml")); 853 context.Args.AddArg(Execution::Args::Type::Silent); 854 context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); 855 context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); 856 context.Add<Data::Manifest>(manifest); 857 context.Add<Data::Installer>(manifest.Installers.at(0)); 858 context << GetInstallerArgs; 859 std::string installerArgs = context.Get<Data::InstallerArgs>(); 860 REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); 861 REQUIRE(installerArgs.find("/LOG=\"MyLog.log\"") != std::string::npos); 862 REQUIRE(installerArgs.find("/DIR=\"MyDir\"") != std::string::npos); 863 } 864 865 { 866 std::ostringstream installOutput; 867 TestContext context{ installOutput, std::cin }; 868 auto previousThreadGlobals = context.SetForCurrentThread(); 869 // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest 870 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); 871 context.Args.AddArg(Execution::Args::Type::Silent); 872 context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); 873 context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); 874 context.Add<Data::Manifest>(manifest); 875 context.Add<Data::Installer>(manifest.Installers.at(0)); 876 context << GetInstallerArgs; 877 std::string installerArgs = context.Get<Data::InstallerArgs>(); 878 REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest 879 REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest 880 REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest 881 REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest 882 } 883 884 { 885 std::ostringstream installOutput; 886 TestContext context{ installOutput, std::cin }; 887 auto previousThreadGlobals = context.SetForCurrentThread(); 888 // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest and --custom argument used in cli 889 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); 890 context.Args.AddArg(Execution::Args::Type::Silent); 891 context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); 892 context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); 893 context.Args.AddArg(Execution::Args::Type::CustomSwitches, "/MyAppendedSwitch"sv); 894 context.Add<Data::Manifest>(manifest); 895 context.Add<Data::Installer>(manifest.Installers.at(0)); 896 context << GetInstallerArgs; 897 std::string installerArgs = context.Get<Data::InstallerArgs>(); 898 REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest 899 REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest 900 REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest 901 REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest 902 REQUIRE(installerArgs.find("/MyAppendedSwitch") != std::string::npos); // Use declaration from argument 903 } 904 905 { 906 std::ostringstream installOutput; 907 TestContext context{ installOutput, std::cin }; 908 auto previousThreadGlobals = context.SetForCurrentThread(); 909 // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest and whitespace-only --custom argument used in cli 910 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); 911 context.Args.AddArg(Execution::Args::Type::Silent); 912 context.Args.AddArg(Execution::Args::Type::CustomSwitches, "\t"sv); 913 context.Add<Data::Manifest>(manifest); 914 context.Add<Data::Installer>(manifest.Installers.at(0)); 915 context << GetInstallerArgs; 916 std::string installerArgs = context.Get<Data::InstallerArgs>(); 917 REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest 918 REQUIRE(installerArgs.find("\t") == std::string::npos); // Whitespace only Custom switches should not be appended 919 } 920 921 { 922 std::ostringstream installOutput; 923 TestContext context{ installOutput, std::cin }; 924 auto previousThreadGlobals = context.SetForCurrentThread(); 925 // Override switch specified. The whole arg passed to installer is overridden. 926 auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); 927 context.Args.AddArg(Execution::Args::Type::Silent); 928 context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); 929 context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); 930 context.Args.AddArg(Execution::Args::Type::Override, "/OverrideEverything"sv); 931 context.Add<Data::Manifest>(manifest); 932 context.Add<Data::Installer>(manifest.Installers.at(0)); 933 context << GetInstallerArgs; 934 std::string installerArgs = context.Get<Data::InstallerArgs>(); 935 REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch 936 } 937 } 938 939 TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow][workflow]") 940 { 941 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 942 943 std::ostringstream installOutput; 944 TestContext context{ installOutput, std::cin }; 945 auto previousThreadGlobals = context.SetForCurrentThread(); 946 OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne }), true); 947 OverrideForShellExecute(context); 948 context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); 949 950 InstallCommand install({}); 951 install.Execute(context); 952 INFO(installOutput.str()); 953 954 // Verify Installer is called and parameters are passed in. 955 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 956 std::ifstream installResultFile(installResultPath.GetPath()); 957 REQUIRE(installResultFile.is_open()); 958 std::string installResultStr; 959 std::getline(installResultFile, installResultStr); 960 REQUIRE(installResultStr.find("/custom") != std::string::npos); 961 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 962 } 963 964 TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow][workflow]") 965 { 966 std::ostringstream installOutput; 967 TestContext context{ installOutput, std::cin }; 968 auto previousThreadGlobals = context.SetForCurrentThread(); 969 OverrideForOpenSource(context, CreateTestSource({}), true); 970 context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnZero"sv); 971 972 InstallCommand install({}); 973 install.Execute(context); 974 INFO(installOutput.str()); 975 976 // Verify proper message is printed 977 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NoPackageFound).get()) != std::string::npos); 978 } 979 980 TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow][workflow]") 981 { 982 std::ostringstream installOutput; 983 TestContext context{ installOutput, std::cin }; 984 auto previousThreadGlobals = context.SetForCurrentThread(); 985 OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnTwo }), true); 986 context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnTwo.Query); 987 988 InstallCommand install({}); 989 install.Execute(context); 990 INFO(installOutput.str()); 991 992 // Verify proper message is printed 993 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::MultiplePackagesFound).get()) != std::string::npos); 994 } 995 996 TEST_CASE("InstallFlow_LicenseAgreement", "[InstallFlow][workflow]") 997 { 998 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 999 1000 std::ostringstream installOutput; 1001 TestContext context{ installOutput, std::cin }; 1002 auto previousThreadGlobals = context.SetForCurrentThread(); 1003 OverrideForShellExecute(context); 1004 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); 1005 context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); 1006 1007 InstallCommand install({}); 1008 install.Execute(context); 1009 INFO(installOutput.str()); 1010 1011 // Verify agreements are shown 1012 REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); 1013 REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); 1014 REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); 1015 REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); 1016 1017 // Verify Installer is called. 1018 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 1019 } 1020 1021 TEST_CASE("InstallFlow_LicenseAgreement_Prompt", "[InstallFlow][workflow]") 1022 { 1023 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1024 1025 // Accept the agreements by saying "Yes" at the prompt 1026 std::istringstream installInput{ "y" }; 1027 1028 std::ostringstream installOutput; 1029 TestContext context{ installOutput, installInput }; 1030 auto previousThreadGlobals = context.SetForCurrentThread(); 1031 OverrideForShellExecute(context); 1032 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); 1033 1034 InstallCommand install({}); 1035 install.Execute(context); 1036 INFO(installOutput.str()); 1037 1038 // Verify prompt was shown 1039 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsPrompt).get()) != std::string::npos); 1040 1041 // Verify agreements are shown 1042 REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); 1043 REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); 1044 REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); 1045 REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); 1046 1047 // Verify Installer is called. 1048 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 1049 } 1050 1051 TEST_CASE("InstallFlow_LicenseAgreement_NotAccepted", "[InstallFlow][workflow]") 1052 { 1053 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1054 1055 // Say "No" at the agreements prompt 1056 std::istringstream installInput{ "n" }; 1057 1058 std::ostringstream installOutput; 1059 TestContext context{ installOutput, installInput }; 1060 auto previousThreadGlobals = context.SetForCurrentThread(); 1061 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); 1062 1063 InstallCommand install({}); 1064 install.Execute(context); 1065 INFO(installOutput.str()); 1066 1067 // Verify agreements are shown 1068 REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); 1069 REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); 1070 REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); 1071 REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); 1072 1073 // Verify installation failed 1074 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); 1075 REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); 1076 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); 1077 } 1078 1079 TEST_CASE("InstallFlowMultiLocale_RequirementNotSatisfied", "[InstallFlow][workflow]") 1080 { 1081 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1082 1083 std::ostringstream installOutput; 1084 TestContext context{ installOutput, std::cin }; 1085 auto previousThreadGlobals = context.SetForCurrentThread(); 1086 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); 1087 context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); 1088 1089 InstallCommand install({}); 1090 install.Execute(context); 1091 INFO(installOutput.str()); 1092 1093 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); 1094 1095 // Verify Installer was not called 1096 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 1097 } 1098 1099 TEST_CASE("InstallFlowMultiLocale_RequirementSatisfied", "[InstallFlow][workflow]") 1100 { 1101 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1102 1103 std::ostringstream installOutput; 1104 TestContext context{ installOutput, std::cin }; 1105 auto previousThreadGlobals = context.SetForCurrentThread(); 1106 OverrideForShellExecute(context); 1107 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); 1108 context.Args.AddArg(Execution::Args::Type::Locale, "fr-FR"sv); 1109 1110 InstallCommand install({}); 1111 install.Execute(context); 1112 INFO(installOutput.str()); 1113 1114 // Verify Installer is called and parameters are passed in. 1115 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 1116 std::ifstream installResultFile(installResultPath.GetPath()); 1117 REQUIRE(installResultFile.is_open()); 1118 std::string installResultStr; 1119 std::getline(installResultFile, installResultStr); 1120 REQUIRE(installResultStr.find("/fr-FR") != std::string::npos); 1121 } 1122 1123 TEST_CASE("InstallFlowMultiLocale_PreferenceNoBetterLocale", "[InstallFlow][workflow]") 1124 { 1125 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1126 1127 std::ostringstream installOutput; 1128 TestContext context{ installOutput, std::cin }; 1129 auto previousThreadGlobals = context.SetForCurrentThread(); 1130 OverrideForShellExecute(context); 1131 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); 1132 1133 TestUserSettings settings; 1134 settings.Set<AppInstaller::Settings::Setting::InstallLocalePreference>({ "zh-CN" }); 1135 1136 InstallCommand install({}); 1137 install.Execute(context); 1138 INFO(installOutput.str()); 1139 1140 // Verify Installer is called and parameters are passed in. 1141 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 1142 std::ifstream installResultFile(installResultPath.GetPath()); 1143 REQUIRE(installResultFile.is_open()); 1144 std::string installResultStr; 1145 std::getline(installResultFile, installResultStr); 1146 REQUIRE(installResultStr.find("/unknown") != std::string::npos); 1147 } 1148 1149 TEST_CASE("InstallFlowMultiLocale_PreferenceWithBetterLocale", "[InstallFlow][workflow]") 1150 { 1151 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1152 1153 std::ostringstream installOutput; 1154 TestContext context{ installOutput, std::cin }; 1155 auto previousThreadGlobals = context.SetForCurrentThread(); 1156 OverrideForShellExecute(context); 1157 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); 1158 1159 TestUserSettings settings; 1160 settings.Set<AppInstaller::Settings::Setting::InstallLocalePreference>({ "en-US" }); 1161 1162 InstallCommand install({}); 1163 install.Execute(context); 1164 INFO(installOutput.str()); 1165 1166 // Verify Installer is called and parameters are passed in. 1167 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 1168 std::ifstream installResultFile(installResultPath.GetPath()); 1169 REQUIRE(installResultFile.is_open()); 1170 std::string installResultStr; 1171 std::getline(installResultFile, installResultStr); 1172 REQUIRE(installResultStr.find("/en-GB") != std::string::npos); 1173 } 1174 1175 TEST_CASE("InstallFlow_InstallMultiple", "[InstallFlow][workflow][MultiQuery]") 1176 { 1177 TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); 1178 TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); 1179 1180 std::ostringstream installOutput; 1181 TestContext context{ installOutput, std::cin }; 1182 auto previousThreadGlobals = context.SetForCurrentThread(); 1183 OverrideForMSIX(context); 1184 OverrideForShellExecute(context); 1185 OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix }), true); 1186 context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); 1187 context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); 1188 1189 InstallCommand installCommand({}); 1190 installCommand.Execute(context); 1191 INFO(installOutput.str()); 1192 1193 // Verify all packages were installed 1194 REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); 1195 REQUIRE(std::filesystem::exists(msixInstallResultPath.GetPath())); 1196 } 1197 1198 TEST_CASE("InstallFlow_InstallMultiple_SearchFailed", "[InstallFlow][workflow][MultiQuery]") 1199 { 1200 std::ostringstream installOutput; 1201 TestContext context{ installOutput, std::cin }; 1202 auto previousThreadGlobals = context.SetForCurrentThread(); 1203 OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe }), true); 1204 context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); 1205 context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); 1206 1207 InstallCommand installCommand({}); 1208 installCommand.Execute(context); 1209 INFO(installOutput.str()); 1210 1211 REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); 1212 } 1213 1214 TEST_CASE("InstallFlow_InstallAcquiresLock", "[InstallFlow][workflow]") 1215 { 1216 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1217 1218 std::ostringstream installOutput; 1219 TestContext context{ installOutput, std::cin }; 1220 auto previousThreadGlobals = context.SetForCurrentThread(); 1221 OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne }), true); 1222 OverrideForShellExecute(context); 1223 context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); 1224 1225 wil::unique_event enteredShellExecute; 1226 enteredShellExecute.create(); 1227 wil::unique_event canLeaveShellExecute; 1228 canLeaveShellExecute.create(); 1229 AppInstaller::ProgressCallback progress; 1230 1231 context.Override({ ShellExecuteInstallImpl, [&](TestContext& context) 1232 { 1233 enteredShellExecute.SetEvent(); 1234 canLeaveShellExecute.wait(500); 1235 ShellExecuteInstallImpl(context); 1236 }}); 1237 1238 { 1239 std::thread otherThread([&]() { 1240 InstallCommand install({}); 1241 install.Execute(context); 1242 }); 1243 1244 REQUIRE(enteredShellExecute.wait(5000)); 1245 1246 AppInstaller::Synchronization::CrossProcessInstallLock mainThreadLock; 1247 REQUIRE(!mainThreadLock.TryAcquireNoWait()); 1248 1249 canLeaveShellExecute.SetEvent(); 1250 otherThread.join(); 1251 } 1252 1253 INFO(installOutput.str()); 1254 1255 // Verify Installer is called and parameters are passed in. 1256 REQUIRE(std::filesystem::exists(installResultPath.GetPath())); 1257 std::ifstream installResultFile(installResultPath.GetPath()); 1258 REQUIRE(installResultFile.is_open()); 1259 std::string installResultStr; 1260 std::getline(installResultFile, installResultStr); 1261 REQUIRE(installResultStr.find("/custom") != std::string::npos); 1262 REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); 1263 } 1264 1265 TEST_CASE("InstallFlow_InstallWithReboot", "[InstallFlow][workflow][reboot]") 1266 { 1267 TestCommon::TempFile installResultPath("TestExeInstalled.txt"); 1268 TestCommon::TestUserSettings testSettings; 1269 1270 std::ostringstream installOutput; 1271 TestContext context{ installOutput, std::cin }; 1272 auto previousThreadGlobals = context.SetForCurrentThread(); 1273 OverrideForShellExecute(context); 1274 1275 context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml").GetPath().u8string()); 1276 context.Args.AddArg(Execution::Args::Type::AllowReboot); 1277 1278 context.Override({ ShellExecuteInstallImpl, [&](TestContext& context) 1279 { 1280 // APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_INSTALL (should be treated as an installer error) 1281 context.Add<Data::OperationReturnCode>(10); 1282 } }); 1283 1284 SECTION("Reboot success") 1285 { 1286 TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(true); 1287 1288 InstallCommand install({}); 1289 install.Execute(context); 1290 INFO(installOutput.str()); 1291 1292 REQUIRE(context.IsTerminated()); 1293 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 1294 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); 1295 REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); 1296 } 1297 SECTION("Reboot failed") 1298 { 1299 TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(false); 1300 1301 InstallCommand install({}); 1302 install.Execute(context); 1303 INFO(installOutput.str()); 1304 1305 REQUIRE(context.IsTerminated()); 1306 REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); 1307 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); 1308 REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); 1309 } 1310 }