winget-cli

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

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 }