winget-cli

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

main.cpp (22034B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 
      4 #include <windows.h>
      5 #include <winreg.h>
      6 #include <winerror.h>
      7 #include <iostream>
      8 #include <fstream>
      9 #include <filesystem>
     10 #include <sstream>
     11 
     12 using namespace std::filesystem;
     13 
     14 std::wstring_view RegistrySubkey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
     15 std::wstring_view DefaultProductID = L"{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}";
     16 std::wstring_view DefaultDisplayName = L"AppInstallerTestExeInstaller";
     17 std::wstring_view DefaultDisplayVersion = L"1.0.0.0";
     18 
     19 void WriteModifyRepairScript(std::wofstream& script, const path& repairCompletedTextFilePath, bool isModifyScript) {
     20     std::wstring scriptName = isModifyScript ? L"Modify" : L"Uninstaller";
     21     script << L"    if /I \"%%A\"==\"/repair\" (\n"
     22         << L"        ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully > \"" << repairCompletedTextFilePath.wstring() << "\"\n"
     23         << L"        ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully\n"
     24         << L"        EXIT /B 0\n"
     25         << L"    ) else if /I \"%%A\"==\"/r\" (\n"
     26         << L"        ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully > \"" << repairCompletedTextFilePath.wstring() << "\"\n"
     27         << L"        ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully\n"
     28         << L"        EXIT /B 0\n"
     29         << L"    )";
     30 }
     31 
     32 void WriteModifyUninstallScript(std::wofstream& script) {
     33     script << L"    else if /I \"%%A\"==\"/uninstall\" (\n"
     34         << L"        call UninstallTestExe.bat\n"
     35         << L"        EXIT /B 0\n"
     36         << L"    ) else if /I \"%%A\"==\"/X\" (\n"
     37         << L"        call UninstallTestExe.bat\n"
     38         << L"        EXIT /B 0\n"
     39         << L"    )\n";
     40 }
     41 
     42 void WriteModifyInvalidOperationScript(std::wofstream& script) {
     43     script << L"echo Invalid operation\n"
     44         << L"EXIT /B 1\n";
     45 }
     46 
     47 void WriteUninstallerScript(
     48     std::wofstream& uninstallerScript,
     49     const path& uninstallerOutputTextFilePath,
     50     const std::wstring& registryKey,
     51     const path& modifyScriptPath,
     52     const path& repairCompletedTextFilePath,
     53     const path& dscResourceExecutablePath,
     54     const path& dscResourceManifestPath) {
     55     uninstallerScript << "ECHO. >" << uninstallerOutputTextFilePath << "\n";
     56     uninstallerScript << "ECHO AppInstallerTestExeInstaller.exe uninstalled successfully.\n";
     57     uninstallerScript << "REG DELETE " << registryKey << " /f\n";
     58     uninstallerScript << "if exist \"" << modifyScriptPath.wstring() << "\" del \"" << modifyScriptPath.wstring() << "\"\n";
     59     uninstallerScript << "if exist \"" << repairCompletedTextFilePath.wstring() << "\" del \"" << repairCompletedTextFilePath.wstring() << "\"\n";
     60     uninstallerScript << "if exist \"" << dscResourceExecutablePath.wstring() << "\" del \"" << dscResourceExecutablePath.wstring() << "\"\n";
     61     uninstallerScript << "if exist \"" << dscResourceManifestPath.wstring() << "\" del \"" << dscResourceManifestPath.wstring() << "\"\n";
     62 }
     63 
     64 path GenerateUninstaller(std::wostream& out, const path& installDirectory, const std::wstring& productID, bool useHKLM)
     65 {
     66     path uninstallerPath = installDirectory;
     67     uninstallerPath /= "UninstallTestExe.bat";
     68 
     69     out << "Uninstaller located at path: " << uninstallerPath << std::endl;
     70 
     71     path uninstallerOutputTextFilePath = installDirectory;
     72     uninstallerOutputTextFilePath /= "TestExeUninstalled.txt";
     73 
     74     path repairCompletedTextFilePath = installDirectory;
     75     repairCompletedTextFilePath /= "TestExeRepairCompleted.txt";
     76 
     77     path modifyScriptPath = installDirectory;
     78     modifyScriptPath /= "ModifyTestExe.bat";
     79 
     80     path dscResourceExecutablePath = installDirectory;
     81     dscResourceExecutablePath /= "AppInstallerTestResource.exe";
     82 
     83     path dscResourceManifestPath = installDirectory;
     84     dscResourceManifestPath /= "AppInstallerTest.dsc.resource.json";
     85 
     86     std::wstring registryKey{ useHKLM ? L"HKEY_LOCAL_MACHINE\\" : L"HKEY_CURRENT_USER\\" };
     87     registryKey += RegistrySubkey;
     88     if (!productID.empty())
     89     {
     90         registryKey += productID;
     91     }
     92     else
     93     {
     94         registryKey += DefaultProductID;
     95     }
     96 
     97     std::wofstream uninstallerScript(uninstallerPath);
     98     uninstallerScript << "@echo off\n";
     99     uninstallerScript << L"for %%A in (%*) do (\n";
    100     WriteModifyRepairScript(uninstallerScript, repairCompletedTextFilePath, false /*isModifyScript*/);
    101     uninstallerScript << ")\n";
    102     WriteUninstallerScript(uninstallerScript, uninstallerOutputTextFilePath, registryKey, modifyScriptPath, repairCompletedTextFilePath, dscResourceExecutablePath, dscResourceManifestPath);
    103 
    104     uninstallerScript.close();
    105 
    106     return uninstallerPath;
    107 }
    108 
    109 path GenerateModifyPath(const path& installDirectory)
    110 {
    111     path modifyScriptPath = installDirectory;
    112     modifyScriptPath /= "ModifyTestExe.bat";
    113 
    114     path repairCompletedTextFilePath = installDirectory;
    115     repairCompletedTextFilePath /= "TestExeRepairCompleted.txt";
    116 
    117     std::wofstream modifyScript(modifyScriptPath);
    118 
    119     modifyScript << L"@echo off\n";
    120     modifyScript << L"for %%A in (%*) do (\n";
    121     WriteModifyRepairScript(modifyScript, repairCompletedTextFilePath, true /*isModifyScript*/);
    122     WriteModifyUninstallScript(modifyScript);
    123     modifyScript << L")\n";
    124     WriteModifyInvalidOperationScript(modifyScript);
    125 
    126     modifyScript.close();
    127 
    128     return modifyScriptPath;
    129 }
    130 
    131 void GenerateDSCv3ProviderFiles(const path& installDirectory)
    132 {
    133     path dscResourceExecutablePath = installDirectory;
    134     dscResourceExecutablePath /= "AppInstallerTestResource.exe";
    135 
    136     WCHAR currentExecutable[MAX_PATH];
    137     GetModuleFileName(nullptr, currentExecutable, MAX_PATH);
    138     path currentExecutablePath{ currentExecutable };
    139     copy_file(currentExecutablePath, dscResourceExecutablePath);
    140 
    141     path dscResourceManifestPath = installDirectory;
    142     dscResourceManifestPath /= "AppInstallerTest.dsc.resource.json";
    143 
    144     std::wstring DscResourceJsonContent =
    145         LR"(
    146     {
    147         "$schema" : "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json",
    148         "description" : "AppInstallerTest dsc Resource.",
    149         "export" :
    150         {
    151             "args" :
    152             [
    153                 "/DscExport"
    154             ],
    155             "executable" : "AppInstallerTestResource.exe"
    156         },
    157         "get" :
    158         {
    159             "args" :
    160             [
    161                 "/DscGet"
    162             ],
    163             "executable" : "AppInstallerTestResource.exe",
    164             "input" : "stdin"
    165         },
    166         "set" :
    167         {
    168             "args" :
    169             [
    170                 "/DscSet"
    171             ] ,
    172             "executable" : "AppInstallerTestResource.exe",
    173             "handlesExist" : true,
    174             "implementsPretest" : true,
    175             "input" : "stdin",
    176             "return" : "state"
    177         },
    178         "test" :
    179         {
    180             "args" :
    181             [
    182                 "/DscTest"
    183             ] ,
    184             "executable" : "AppInstallerTestResource.exe",
    185             "input" : "stdin",
    186             "return" : "state"
    187         },
    188         "schema": {
    189             "embedded": {
    190                 "$schema": "http://json-schema.org/draft-07/schema#",
    191                 "title": "AppInstallerTestResource",
    192                 "description": "App Installer Test Resource",
    193                 "type": "object",
    194                 "required": [],
    195                 "additionalProperties": false,
    196                 "properties": {
    197                     "_inDesiredState": {
    198                         "description": "Indicates whether an instance is in the desired state.",
    199                         "type": "boolean"
    200                     },
    201                     "data": {
    202                         "type": "string",
    203                         "description": "Test data."
    204                     }
    205                 }
    206             }
    207         },
    208         "type" : "AppInstallerTest/TestResource",
    209         "version" : "1.0.0"
    210     }
    211         )";
    212 
    213 
    214     std::wofstream dscResourceJson(dscResourceManifestPath);
    215     dscResourceJson << DscResourceJsonContent;
    216     dscResourceJson.close();
    217 }
    218 
    219 void WriteToUninstallRegistry(
    220     std::wostream& out,
    221     const std::wstring& productID,
    222     const path& uninstallerPath,
    223     const path& modifyPath,
    224     const std::wstring& displayName,
    225     const std::wstring& displayVersion,
    226     const std::wstring& installLocation,
    227     bool useHKLM,
    228     bool noRepair,
    229     bool noModify)
    230 {
    231     HKEY hkey;
    232     LONG lReg;
    233 
    234     // String inputs to registry must be of wide char type
    235     const wchar_t* publisher = L"Microsoft Corporation";
    236     std::wstring uninstallString = uninstallerPath.wstring();
    237     std::wstring modifyPathString = modifyPath.wstring();
    238 
    239     DWORD version = 1;
    240 
    241     std::wstring registryKey{ RegistrySubkey };
    242 
    243     if (!productID.empty())
    244     {
    245         registryKey += productID;
    246         out << "Product Code overridden to: " << registryKey << std::endl;
    247     }
    248     else
    249     {
    250         registryKey += DefaultProductID;
    251         out << "Default Product Code used: " << registryKey << std::endl;
    252     }
    253 
    254     lReg = RegCreateKeyEx(
    255         useHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
    256         registryKey.c_str(),
    257         0,
    258         NULL,
    259         REG_OPTION_NON_VOLATILE,
    260         KEY_ALL_ACCESS,
    261         NULL,
    262         &hkey,
    263         NULL);
    264 
    265     if (lReg == ERROR_SUCCESS)
    266     {
    267         out << "Successfully opened registry key" << std::endl;
    268 
    269         // Set Display Name Property Value
    270         if (LONG res = RegSetValueEx(hkey, L"DisplayName", NULL, REG_SZ, (LPBYTE)displayName.c_str(), (DWORD)(displayName.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
    271         {
    272             out << "Failed to write DisplayName value. Error Code: " << res << std::endl;
    273         }
    274 
    275         // Set Display Version Property Value
    276         if (LONG res = RegSetValueEx(hkey, L"DisplayVersion", NULL, REG_SZ, (LPBYTE)displayVersion.c_str(), (DWORD)(displayVersion.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
    277         {
    278             out << "Failed to write DisplayVersion value. Error Code: " << res << std::endl;
    279         }
    280 
    281         // Set Publisher Property Value
    282         if (LONG res = RegSetValueEx(hkey, L"Publisher", NULL, REG_SZ, (LPBYTE)publisher, (DWORD)(wcslen(publisher) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
    283         {
    284             out << "Failed to write Publisher value. Error Code: " << res << std::endl;
    285         }
    286 
    287         // Set UninstallString Property Value
    288         if (LONG res = RegSetValueEx(hkey, L"UninstallString", NULL, REG_EXPAND_SZ, (LPBYTE)uninstallString.c_str(), (DWORD)(uninstallString.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
    289         {
    290             out << "Failed to write UninstallString value. Error Code: " << res << std::endl;
    291         }
    292 
    293         // Set Version Property Value
    294         if (LONG res = RegSetValueEx(hkey, L"Version", NULL, REG_DWORD, (LPBYTE)&version, sizeof(version)) != ERROR_SUCCESS)
    295         {
    296             out << "Failed to write Version value. Error Code: " << res << std::endl;
    297         }
    298 
    299         // Set InstallLocation Property Value
    300         if (LONG res = RegSetValueEx(hkey, L"InstallLocation", NULL, REG_SZ, (LPBYTE)installLocation.c_str(), (DWORD)(installLocation.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
    301         {
    302             out << "Failed to write InstallLocation value. Error Code: " << res << std::endl;
    303         }
    304 
    305         // Set ModifyPath Property Value
    306         if (LONG res = RegSetValueEx(hkey, L"ModifyPath", NULL, REG_EXPAND_SZ, (LPBYTE)modifyPathString.c_str(), (DWORD)(modifyPathString.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
    307         {
    308             out << "Failed to write ModifyPath value. Error Code: " << res << std::endl;
    309         }
    310 
    311         if(noRepair)
    312         {
    313             // Set NoRepair Property Value
    314             DWORD noRepairValue = 1;
    315             if (LONG res = RegSetValueEx(hkey, L"NoRepair", NULL, REG_DWORD, (LPBYTE)&noRepairValue, sizeof(noRepairValue)) != ERROR_SUCCESS)
    316             {
    317                 out << "Failed to write NoRepair value. Error Code: " << res << std::endl;
    318             }
    319         }
    320 
    321         if(noModify)
    322         {
    323             // Set NoModify Property Value
    324             DWORD noModifyValue = 1;
    325             if (LONG res = RegSetValueEx(hkey, L"NoModify", NULL, REG_DWORD, (LPBYTE)&noModifyValue, sizeof(noModifyValue)) != ERROR_SUCCESS)
    326             {
    327                 out << "Failed to write NoModify value. Error Code: " << res << std::endl;
    328             }
    329         }
    330 
    331         out << "Write to registry key completed" << std::endl;
    332     }
    333     else {
    334         out << "Key Creation Failed" << std::endl;
    335     }
    336 
    337     RegCloseKey(hkey);
    338 }
    339 
    340 void WriteToFile(const path& filePath, const std::wstringstream& content)
    341 {
    342     std::wofstream file(filePath, std::ofstream::out);
    343     file << content.str();
    344     file.close();
    345 }
    346 
    347 void HandleRepairOperation(const std::wstring& productID, const std::wstringstream& outContent, bool useHKLM)
    348 {
    349     path installDirectory;
    350 
    351     // Open the registry key
    352     HKEY hKey;
    353     std::wstring registryPath = std::wstring(RegistrySubkey);
    354 
    355     if (!productID.empty())
    356     {
    357         registryPath += productID;
    358     }
    359     else
    360     {
    361         registryPath += DefaultProductID;
    362     }
    363 
    364     LONG lReg = RegOpenKeyEx(useHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, registryPath.c_str(), 0, KEY_READ, &hKey);
    365 
    366     if (lReg == ERROR_SUCCESS)
    367     {
    368         // Query the value of the InstallLocation
    369         wchar_t regInstallLocation[MAX_PATH];
    370         DWORD bufferSize = sizeof(regInstallLocation);
    371         lReg = RegQueryValueEx(hKey, L"InstallLocation", NULL, NULL, (LPBYTE)regInstallLocation, &bufferSize);
    372 
    373         if (lReg == ERROR_SUCCESS)
    374         {
    375             // Convert the InstallLocation to a path
    376             installDirectory = std::wstring(regInstallLocation);
    377         }
    378 
    379         // Close the registry key
    380         RegCloseKey(hKey);
    381 
    382         if(installDirectory.empty())
    383         {
    384             // We could not find the install location, so we cannot repair
    385             return;
    386         }
    387     }
    388     else
    389     {
    390         // We could not find the uninstall APR registry key, so we cannot repair
    391         return;
    392     }
    393 
    394     path outFilePath = installDirectory;
    395     outFilePath /= "TestExeRepairCompleted.txt";
    396     WriteToFile(outFilePath, outContent);
    397 }
    398 
    399 void HandleInstallationOperation(
    400     std::wostream& out,
    401     const path& installDirectory,
    402     const std::wstringstream& outContent,
    403     const std::wstring& productCode,
    404     bool useHKLM,
    405     const std::wstring& displayName,
    406     const std::wstring& displayVersion,
    407     bool noRepair,
    408     bool noModify,
    409     bool generateDscResourceFiles)
    410 {
    411     path outFilePath = installDirectory;
    412     outFilePath /= "TestExeInstalled.txt";
    413 
    414     std::wofstream file(outFilePath, std::ofstream::out);
    415     file << outContent.str();
    416     file.close();
    417 
    418     if (generateDscResourceFiles)
    419     {
    420         GenerateDSCv3ProviderFiles(installDirectory);
    421     }
    422 
    423     path uninstallerPath = GenerateUninstaller(out, installDirectory, productCode, useHKLM);
    424     path modifyPath = GenerateModifyPath(installDirectory);
    425 
    426     WriteToUninstallRegistry(out, productCode, uninstallerPath, modifyPath, displayName, displayVersion, installDirectory.wstring(), useHKLM, noRepair, noModify);
    427 }
    428 
    429 // The installer prints all args to an output file and writes to the Uninstall registry key
    430 int wmain(int argc, const wchar_t** argv)
    431 {
    432     path installDirectory = temp_directory_path();
    433     std::wstringstream outContent;
    434     std::wstring productCode;
    435     std::wstring displayName;
    436     std::wstring displayVersion;
    437     std::wstring aliasToExecute;
    438     std::wstring aliasArguments;
    439     bool useHKLM = false;
    440     bool noOperation = false;
    441     int exitCode = 0;
    442     bool isRepair = false;
    443     bool noRepair = false;
    444     bool noModify = false;
    445     bool generateDscResourceFiles = false;
    446 
    447     // Output to cout by default, but swap to a file if requested
    448     std::wostream* out = &std::wcout;
    449     std::wofstream logFile;
    450 
    451     for (int i = 1; i < argc; i++)
    452     {
    453         outContent << argv[i] << ' ';
    454 
    455         // Supports custom install path.
    456         if (_wcsicmp(argv[i], L"/InstallDir") == 0)
    457         {
    458             if (++i < argc)
    459             {
    460                 installDirectory = argv[i];
    461                 std::filesystem::create_directories(installDirectory);
    462                 outContent << argv[i] << ' ';
    463             }
    464         }
    465 
    466         // Supports custom exit code
    467         else if (_wcsicmp(argv[i], L"/ExitCode") == 0)
    468         {
    469             if (++i < argc)
    470             {
    471                 exitCode = static_cast<int>(std::stoll(argv[i], 0, 0));
    472                 outContent << argv[i] << ' ';
    473             }
    474         }
    475 
    476         // Supports custom product code ID
    477         else if (_wcsicmp(argv[i], L"/ProductID") == 0)
    478         {
    479             if (++i < argc)
    480             {
    481                 productCode = argv[i];
    482                 outContent << argv[i] << ' ';
    483             }
    484         }
    485 
    486         // Supports custom DisplayName
    487         else if (_wcsicmp(argv[i], L"/DisplayName") == 0)
    488         {
    489             if (++i < argc)
    490             {
    491                 displayName = argv[i];
    492                 outContent << argv[i] << ' ';
    493             }
    494         }
    495 
    496         // Supports custom version
    497         else if (_wcsicmp(argv[i], L"/Version") == 0)
    498         {
    499             if (++i < argc)
    500             {
    501                 displayVersion = argv[i];
    502                 outContent << argv[i] << ' ';
    503             }
    504         }
    505 
    506         // Supports log file
    507         else if (_wcsicmp(argv[i], L"/LogFile") == 0)
    508         {
    509             if (++i < argc)
    510             {
    511                 logFile = std::wofstream(argv[i], std::wofstream::out | std::wofstream::trunc);
    512                 out = &logFile;
    513                 outContent << argv[i] << ' ';
    514             }
    515         }
    516 
    517         // Writes to HKLM
    518         else if (_wcsicmp(argv[i], L"/UseHKLM") == 0)
    519         {
    520             useHKLM = true;
    521         }
    522 
    523         // Executes a command alias during installation
    524         else if (_wcsicmp(argv[i], L"/AliasToExecute") == 0)
    525         {
    526             if (++i < argc)
    527             {
    528                 aliasToExecute = argv[i];
    529                 outContent << argv[i] << ' ';
    530             }
    531         }
    532 
    533         // Additional arguments to include when executing the command alias during installation
    534         else if (_wcsicmp(argv[i], L"/AliasArguments") == 0)
    535         {
    536             if (++i < argc)
    537             {
    538                 aliasArguments = argv[i];
    539                 outContent << argv[i] << ' ';
    540             }
    541         }
    542 
    543         // Supports /repair and /r to emulate repair operation using installer.
    544         else if (_wcsicmp(argv[i], L"/repair") == 0
    545             || _wcsicmp(argv[i], L"/r") == 0)
    546         {
    547             isRepair = true;
    548         }
    549 
    550         else if (_wcsicmp(argv[i], L"/NoRepair") == 0)
    551         {
    552             noRepair = true;
    553         }
    554 
    555         else if (_wcsicmp(argv[i], L"/NoModify") == 0)
    556         {
    557             noModify = true;
    558         }
    559 
    560         // Returns the success exit code to emulate being invoked by another caller.
    561         else if (_wcsicmp(argv[i], L"/NoOperation") == 0)
    562         {
    563             noOperation = true;
    564         }
    565 
    566         // Also output dsc resource files
    567         else if (_wcsicmp(argv[i], L"/GenerateDscResourceFiles") == 0)
    568         {
    569             generateDscResourceFiles = true;
    570         }
    571 
    572         // Dsc resource get
    573         else if (_wcsicmp(argv[i], L"/DscGet") == 0)
    574         {
    575             std::cout << R"({"data":"TestData"})" << std::endl;
    576             return 0;
    577         }
    578 
    579         // Dsc resource set
    580         else if (_wcsicmp(argv[i], L"/DscSet") == 0)
    581         {
    582             std::cout << R"({"_inDesiredState":true})" << std::endl;
    583             return 0;
    584         }
    585 
    586         // Dsc resource test
    587         else if (_wcsicmp(argv[i], L"/DscTest") == 0)
    588         {
    589             std::cout << R"({"_inDesiredState":true})" << std::endl;
    590             return 0;
    591         }
    592 
    593         // Dsc resource export
    594         else if (_wcsicmp(argv[i], L"/DscExport") == 0)
    595         {
    596             std::cout << R"({"data":"TestData"})" << std::endl;
    597             return 0;
    598         }
    599     }
    600 
    601     if (noOperation)
    602     {
    603         return exitCode;
    604     }
    605 
    606     if (!aliasToExecute.empty())
    607     {
    608         SHELLEXECUTEINFOW execInfo = { 0 };
    609         execInfo.cbSize = sizeof(execInfo);
    610         execInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    611         execInfo.lpFile = aliasToExecute.c_str();
    612 
    613         if (!aliasArguments.empty())
    614         {
    615             execInfo.lpParameters = aliasArguments.c_str();
    616         }
    617         execInfo.nShow = SW_SHOW;
    618 
    619         if (!ShellExecuteExW(&execInfo) || !execInfo.hProcess)
    620         {
    621             return -1;
    622         }
    623     }
    624 
    625     if (displayName.empty())
    626     {
    627         displayName = DefaultDisplayName;
    628     }
    629 
    630     if (displayVersion.empty())
    631     {
    632         displayVersion = DefaultDisplayVersion;
    633     }
    634 
    635     path outFilePath = installDirectory;
    636 
    637     if (isRepair)
    638     {
    639         outContent << L"\nInstaller Repair operation for AppInstallerTestExeInstaller.exe completed successfully.";
    640         HandleRepairOperation(productCode, outContent, useHKLM);
    641     }
    642     else
    643     {
    644         HandleInstallationOperation(*out, installDirectory, outContent, productCode, useHKLM, displayName, displayVersion, noRepair, noModify, generateDscResourceFiles);
    645     }
    646 
    647     return exitCode;
    648 }