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 }