Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

751 lines
22 KiB

  1. //depot/Lab01_N/sdktools/cabs/symbolcd/tools/setup/msisetup.c#16 - edit change 2216 (text)
  2. #include <windows.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include "tchar.h"
  6. #include "shlwapi.h"
  7. #define MSI_BUILD_VER_ALPHA 816 // MSI version for Win2K Alpha WIN2KMINBUILD
  8. #define MSI_BUILD_VER_X86 1029 // Latest MSI version for Win2K x86
  9. #define WIN2K_MIN_BUILD_X86 2183 // Win2K RC3
  10. #define WIN2K_MIN_BUILD_ALPHA 2128 // Last supported Win2K Alpha build
  11. typedef struct _CommandArgs {
  12. BOOL QuietInstall;
  13. BOOL StressInstall;
  14. BOOL UIStressInstall;
  15. TCHAR szInstDir[ _MAX_PATH*sizeof(TCHAR) ];
  16. TCHAR szMsiName[ _MAX_PATH*sizeof(TCHAR) ];
  17. TCHAR szProductRegKey[ _MAX_PATH*sizeof(TCHAR) ];
  18. } COMMAND_ARGS, *PCOMMAND_ARGS;
  19. // Function prototypes
  20. BOOL
  21. RunCommand(
  22. PTCHAR szCommandLine,
  23. HINSTANCE hInst
  24. );
  25. BOOL
  26. GetCommandLineArgs(
  27. LPTSTR szCmdLine,
  28. PCOMMAND_ARGS pComArgs
  29. );
  30. TCHAR szMSIInstFile[_MAX_PATH*sizeof(TCHAR)];
  31. TCHAR szPkgInstFile[_MAX_PATH*sizeof(TCHAR)];
  32. TCHAR szPkgInstCommand[_MAX_PATH*2*sizeof(TCHAR)];
  33. // For stress installs, this command will be used to
  34. // remove the current package but don't remove its
  35. // files, if the current package with the same
  36. // product ID is already installed.
  37. TCHAR szPkgRemoveCommand[_MAX_PATH*2*sizeof(TCHAR)];
  38. TCHAR szPkgRemoveCommand2[_MAX_PATH*2*sizeof(TCHAR)];
  39. // If the first install fails, stress tries again without
  40. // the quiet switch before giving a pop-up
  41. TCHAR szPkgInstCommandNoQuiet[_MAX_PATH*2*sizeof(TCHAR)];
  42. TCHAR szCommandFullPath[_MAX_PATH*sizeof(TCHAR)];
  43. int WINAPI WinMain(
  44. HINSTANCE hInstance,
  45. HINSTANCE hPrevInstance,
  46. LPTSTR lpszCmdLine,
  47. int nCmdShow
  48. )
  49. {
  50. OSVERSIONINFO VersionInfo;
  51. SYSTEM_INFO SystemInfo;
  52. BOOL rc;
  53. BOOL MSIIsInstalled;
  54. PTCHAR ch;
  55. TCHAR szBuf[1000];
  56. TCHAR szSystemDirectory[_MAX_PATH];
  57. COMMAND_ARGS ComArgs;
  58. HKEY hKey;
  59. DWORD dwrc;
  60. DWORD dwSizeValue;
  61. DWORD dwType;
  62. HANDLE hFile;
  63. WIN32_FIND_DATA FindFileData;
  64. MSIIsInstalled=FALSE;
  65. // Get this info for later use
  66. VersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
  67. GetVersionEx( &VersionInfo );
  68. GetSystemInfo( &SystemInfo );
  69. // Parse through the command line for the various arguments
  70. rc = GetCommandLineArgs(lpszCmdLine, &ComArgs );
  71. if (!rc) {
  72. _stprintf( szBuf, _T("%s%s%s%s%s"),
  73. _T(" Usage: \n\n"),
  74. _T(" setup.exe [ /q [ /i <InstDir> ] ]\n\n"),
  75. _T(" /q\tGive pop-ups only for errors\n\n"),
  76. _T(" /i\tInstall to <Instdir>\n\n"),
  77. _T(" /n\tInstall <msi package Name>\n\n")
  78. );
  79. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"), 0 );
  80. return (1);
  81. }
  82. //
  83. // Set the full path to this setup.exe
  84. //
  85. if (GetModuleFileName( NULL, szCommandFullPath, MAX_PATH ) == 0) {
  86. return(1);
  87. }
  88. // Put an end of string after the directory that this was
  89. // started from
  90. ch = szCommandFullPath + _tcslen(szCommandFullPath);
  91. while ( *ch != _T('\\') && ( ch > szCommandFullPath ) ) ch--;
  92. *ch=_T('\0');
  93. // This will become the full path and name of the MSI file to install
  94. _tcscpy( szMSIInstFile, szCommandFullPath);
  95. // Set the full path and name of the msi package
  96. _tcscpy( szPkgInstFile, szCommandFullPath);
  97. _tcscat( szPkgInstFile, _T("\\") );
  98. _tcscat( szPkgInstFile, ComArgs.szMsiName );
  99. // See if the package exists
  100. hFile = FindFirstFile( szPkgInstFile, &FindFileData );
  101. if ( hFile == INVALID_HANDLE_VALUE ) {
  102. _stprintf( szBuf, _T("%s%s%s%s"),
  103. _T(" The Microsoft Debugging Tools package "),
  104. szPkgInstFile,
  105. _T(" does not exist.\n Setup cannot contine"),
  106. _T(" for this platform.")
  107. );
  108. MessageBox( NULL,
  109. szBuf,
  110. _T("Microsoft Debugging Tools"),
  111. 0
  112. );
  113. return(1);
  114. }
  115. FindClose(hFile);
  116. // Set the command for installing the package
  117. _tcscpy( szPkgInstCommand, _T("msiexec /i ") );
  118. _tcscat( szPkgInstCommand, szPkgInstFile );
  119. // Set the command for removing the current package
  120. // that is installed.
  121. _tcscpy( szBuf, _T("") );
  122. dwrc = RegOpenKeyEx( HKEY_CURRENT_USER,
  123. ComArgs.szProductRegKey,
  124. 0,
  125. KEY_QUERY_VALUE,
  126. &hKey
  127. );
  128. if ( dwrc == ERROR_SUCCESS ) {
  129. _tcscpy( szBuf, _T("") );
  130. dwSizeValue=sizeof(szBuf);
  131. RegQueryValueEx ( hKey,
  132. _T("ProductCode"),
  133. 0,
  134. &dwType,
  135. (PBYTE)szBuf,
  136. &dwSizeValue
  137. );
  138. RegCloseKey(hKey);
  139. }
  140. // Set the command to remove the current package
  141. // that has an Add/Remove link in the start menu
  142. _tcscpy(szPkgRemoveCommand2, _T("") );
  143. if ( _tcslen(szBuf) > 0 ) {
  144. _tcscpy(szPkgRemoveCommand2, _T("msiexec /x ") );
  145. _tcscat(szPkgRemoveCommand2, szBuf);
  146. _tcscat(szPkgRemoveCommand2, _T(" REMOVETHEFILES=0 /qn") );
  147. }
  148. // Set the command to remove the current package so that
  149. // this program works like it used to.
  150. _tcscpy(szPkgRemoveCommand, _T("msiexec /x ") );
  151. _tcscat(szPkgRemoveCommand, szPkgInstFile );
  152. _tcscat(szPkgRemoveCommand, _T(" REMOVETHEFILES=0 /qn") );
  153. // Add a user override installation directory
  154. if ( _tcslen(ComArgs.szInstDir) > 0 ) {
  155. _tcscat( szPkgInstCommand, _T(" INSTDIR=") );
  156. _tcscat( szPkgInstCommand, ComArgs.szInstDir );
  157. } else if ( ComArgs.UIStressInstall ) {
  158. GetSystemDirectory( szSystemDirectory, _MAX_PATH );
  159. _tcscat( szPkgInstCommand, _T(" INSTDIR=") );
  160. _tcscat( szPkgInstCommand, szSystemDirectory );
  161. }
  162. // If this is an "undocumented" stress install
  163. // don't remove the files of the previous install
  164. // when you upgrade
  165. // FEATURESTOREMOVE should never actually need to be used, unless
  166. // the user has something screwed up on his system where the registry
  167. // key and products installed don't agree, or MSI thinks there's more
  168. // products installed than the registry key we look at.
  169. if ( ComArgs.StressInstall ) {
  170. _tcscat( szPkgInstCommand, _T(" FEATURESTOREMOVE=\"\"") );
  171. }
  172. // If this is an "undocumented" UI stress install
  173. // only install the private extensions
  174. if ( ComArgs.UIStressInstall ) {
  175. _tcscat( szPkgInstCommand,
  176. _T(" ADDLOCAL=DBG.DbgExts.Internal,DBG.NtsdFix.Internal") );
  177. }
  178. // Add the quiet switch
  179. // Save the command without a quiet switch
  180. _tcscpy( szPkgInstCommandNoQuiet, szPkgInstCommand);
  181. if ( ComArgs.QuietInstall ) {
  182. _tcscat( szPkgInstCommand, _T(" /qn") );
  183. }
  184. // Do version checks for whether msi is already installed
  185. //
  186. // If this is Windows 2000 and build number is >=
  187. // WIN2K_MIN_BUILD_X86 then MSI is installed
  188. // Don't try to run instmsi.exe on Windows 2000 because
  189. // you will get file system protection pop-ups.
  190. //
  191. if ( (VersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) &&
  192. (VersionInfo.dwMajorVersion >= 5.0 ) ) {
  193. switch (SystemInfo.wProcessorArchitecture) {
  194. case PROCESSOR_ARCHITECTURE_ALPHA:
  195. if (VersionInfo.dwBuildNumber < WIN2K_MIN_BUILD_ALPHA) {
  196. // The version of MSI that is on early builds of Windows
  197. // 2000 shouldn't be trusted for installs.
  198. _stprintf( szBuf, "%s",
  199. _T("The Debugging Tools does not install on ")
  200. _T("this version of Alpha Windows 2000. Please upgrade ")
  201. _T("your system to Windows 2000 Beta 3 ")
  202. _T("before trying to install this package.")
  203. );
  204. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"),0);
  205. return(1);
  206. }
  207. break;
  208. case PROCESSOR_ARCHITECTURE_INTEL:
  209. if (VersionInfo.dwBuildNumber < WIN2K_MIN_BUILD_X86 ) {
  210. // The version of MSI that is on early builds of Windows
  211. // 2000 shouldn't be trusted for installs.
  212. _stprintf( szBuf, "%s%s%s%s",
  213. _T("The Debugging Tools does not install on "),
  214. _T("this version of Windows 2000. Please upgrade "),
  215. _T("your system to a retail version of Windows 2000 "),
  216. _T("before trying to install this package.")
  217. );
  218. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"),0);
  219. return(1);
  220. }
  221. break;
  222. case PROCESSOR_ARCHITECTURE_IA64:
  223. break;
  224. default:
  225. _stprintf( szBuf, "%s",
  226. _T("Unknown computer architecture.")
  227. );
  228. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"),0);
  229. return(1);
  230. }
  231. MSIIsInstalled = TRUE;
  232. } else if ( SystemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) {
  233. //
  234. // For Intel OS's prior to Windows 2000, run instmsi.exe
  235. //
  236. //
  237. // NT4 X86
  238. //
  239. if ( VersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) {
  240. _tcscat( szMSIInstFile,
  241. _T("\\setup\\winnt\\i386\\instmsi.exe /q") );
  242. }
  243. //
  244. // Win9x
  245. //
  246. else if ( VersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) {
  247. _tcscat( szMSIInstFile,
  248. _T("\\setup\\win9x\\instmsi.exe /q") );
  249. } else {
  250. _stprintf( szBuf, _T("%s %s"),
  251. _T("The Microsoft Debugging Tools does not install"),
  252. _T("on this system.")
  253. );
  254. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"),0);
  255. return(1);
  256. }
  257. } else if ( SystemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ALPHA ) {
  258. if ( VersionInfo.dwBuildNumber >= WIN2K_MIN_BUILD_ALPHA ) {
  259. MSIIsInstalled = TRUE;
  260. } else {
  261. _tcscat( szMSIInstFile,
  262. _T("\\setup\\winnt\\alpha\\instmsi.exe /q"));
  263. }
  264. } else {
  265. MessageBox( NULL,
  266. _T("The Microsoft Debugging Tools cannot be installed on this system."),
  267. _T("Microsoft Debugging Tools"),
  268. 0 );
  269. return(1);
  270. }
  271. // Install MSI if it is not already installed
  272. if ( !MSIIsInstalled ) {
  273. if ( RunCommand( szMSIInstFile, hInstance ) ) {
  274. MSIIsInstalled = TRUE;
  275. }
  276. if (!MSIIsInstalled) {
  277. _stprintf( szBuf, _T("%s %s %s %s"),
  278. _T("The Windows Installer could not be installed"),
  279. _T("on this system. This is required before"),
  280. _T("installing the Microsoft Debugging Tools package."),
  281. _T("Try logging in as an administrator and try again.")
  282. );
  283. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"),0);
  284. return(1);
  285. }
  286. }
  287. //
  288. // Now, if this is a stress install,
  289. // Try to remove the current package in case it is installed
  290. //
  291. if ( ComArgs.StressInstall ) {
  292. if ( _tcslen(szPkgRemoveCommand2) > 0 ) {
  293. RunCommand( szPkgRemoveCommand2, hInstance);
  294. }
  295. RunCommand( szPkgRemoveCommand, hInstance );
  296. if ( !RunCommand( szPkgInstCommand, hInstance ) ) {
  297. // Try again without the quiet switch, so that the user will get
  298. // a pop-up from dbg.msi and quit calling us
  299. _stprintf( szBuf, _T("%s %s %s %s"),
  300. _T("There were errors when trying to install the"),
  301. _T("debuggers.\nClick OK to attempt an install of the"),
  302. _T("debuggers with\n the GUI and you will see the"),
  303. _T("correct error message.")
  304. );
  305. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"), 0);
  306. if ( !RunCommand( szPkgInstCommandNoQuiet, hInstance ) ) {
  307. _stprintf( szBuf, _T("%s %s %s"),
  308. _T("There were still errors in the install.\n"),
  309. _T("Please see http://dbg/triage/top10.html #2 "),
  310. _T("for more help.")
  311. );
  312. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"), 0);
  313. return(1);
  314. }
  315. }
  316. return(0);
  317. }
  318. //
  319. // Now, install the package dbg.msi
  320. //
  321. if ( !RunCommand( szPkgInstCommand, hInstance ) ) {
  322. if (ComArgs.QuietInstall) {
  323. _stprintf( szBuf, _T("%s %s %s %s"),
  324. _T("There were errors in the Debugging Tools install."),
  325. _T(" Please run "),
  326. szPkgInstFile,
  327. _T("to receive more detailed error information.")
  328. );
  329. MessageBox( NULL, szBuf, _T("Microsoft Debugging Tools"),0);
  330. }
  331. return(1);
  332. }
  333. return(0);
  334. }
  335. //
  336. // RunCommand
  337. //
  338. // Purpose: Install MSI
  339. //
  340. // Return Values:
  341. // 0 error
  342. // 1 successful
  343. BOOL
  344. RunCommand( PTCHAR szCommandLine,
  345. HINSTANCE hInst)
  346. {
  347. BOOL rc;
  348. DWORD dwRet;
  349. PROCESS_INFORMATION ProcInfo = {0};
  350. STARTUPINFO SI= {0};
  351. // Spawn the command line specified by szCommandLine
  352. rc = CreateProcess(NULL,
  353. szCommandLine,
  354. NULL,
  355. NULL,
  356. FALSE,
  357. CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS,
  358. NULL,
  359. NULL,
  360. &SI,
  361. &ProcInfo );
  362. if ( (!rc) || (!ProcInfo.hProcess) ) {
  363. goto cleanup;
  364. }
  365. //
  366. // Wait for command to complete ... Give it 20 minutes
  367. //
  368. dwRet = WaitForSingleObject(ProcInfo.hProcess, 1200000);
  369. if (dwRet != WAIT_OBJECT_0) {
  370. rc = FALSE;
  371. goto cleanup;
  372. }
  373. // Get the process exit code
  374. rc = GetExitCodeProcess( ProcInfo.hProcess, &dwRet);
  375. if (dwRet == ERROR_SUCCESS ) {
  376. rc = 1;
  377. } else {
  378. rc = 0;
  379. }
  380. cleanup:
  381. if (ProcInfo.hProcess)
  382. CloseHandle(ProcInfo.hProcess);
  383. return (rc);
  384. }
  385. BOOL
  386. GetCommandLineArgs(
  387. LPTSTR szCmdLine,
  388. PCOMMAND_ARGS pComArgs
  389. )
  390. {
  391. ULONG length;
  392. ULONG i,cur;
  393. BOOL SkippingSpaces=FALSE;
  394. BOOL QuotedString=FALSE;
  395. BOOL NeedSecond=FALSE;
  396. BOOL rc=TRUE;
  397. LPTSTR *argv;
  398. ULONG argc=0;
  399. LPTSTR szCmdLineTmp;
  400. TCHAR c;
  401. ZeroMemory(pComArgs, sizeof(COMMAND_ARGS));
  402. // Create a line to use for temporary marking
  403. length=_tcslen(szCmdLine);
  404. szCmdLineTmp= (LPTSTR)malloc( (_tcslen(szCmdLine) + 1) * sizeof(TCHAR) );
  405. if (szCmdLineTmp==NULL)
  406. {
  407. return FALSE;
  408. }
  409. _tcscpy(szCmdLineTmp, szCmdLine);
  410. // Count the number of arguments
  411. // Create a argv and argc
  412. SkippingSpaces=TRUE;
  413. QuotedString=FALSE;
  414. argc=0;
  415. for ( i=0; i<length; i++ )
  416. {
  417. c=szCmdLineTmp[i];
  418. switch (szCmdLineTmp[i]) {
  419. case _T(' '):
  420. case _T('\t'): if (QuotedString)
  421. {
  422. break;
  423. }
  424. if (!SkippingSpaces)
  425. {
  426. SkippingSpaces=TRUE;
  427. }
  428. break;
  429. case _T('\"'): if (QuotedString)
  430. {
  431. // This is the end of a quoted string
  432. // The next character to read in is a space
  433. QuotedString=FALSE;
  434. SkippingSpaces=TRUE;
  435. if ( i < (length-1) &&
  436. szCmdLineTmp[i+1] != _T(' ') &&
  437. szCmdLineTmp[i+1] != _T('\t') )
  438. {
  439. // This is the end of a quote and its not
  440. // followed by a space
  441. rc=FALSE;
  442. goto CommandLineFinish;
  443. }
  444. break;
  445. }
  446. if (SkippingSpaces) {
  447. // This is the beginning of a quoted string
  448. // Its a new argument and it follows spaces
  449. argc++;
  450. SkippingSpaces=FALSE;
  451. QuotedString=TRUE;
  452. break;
  453. }
  454. // This is an error -- This is a quote in the middle of a string
  455. rc=FALSE;
  456. goto CommandLineFinish;
  457. break;
  458. default: if (QuotedString) {
  459. break;
  460. }
  461. if (SkippingSpaces) {
  462. argc++;
  463. SkippingSpaces=FALSE;
  464. }
  465. break;
  466. }
  467. }
  468. if (QuotedString)
  469. {
  470. // Make sure that all the quotes got a finished pair
  471. rc=FALSE;
  472. goto CommandLineFinish;
  473. }
  474. // Now, create argv with the correct number of entries
  475. argv=(LPTSTR*)malloc(argc * sizeof(LPTSTR) );
  476. if (argv==NULL)
  477. {
  478. free(szCmdLineTmp);
  479. return FALSE;
  480. }
  481. // Set argv to point to the correct place on szCmdLineTmp
  482. // and put '\0' after each token.
  483. SkippingSpaces=TRUE;
  484. QuotedString=FALSE;
  485. argc=0;
  486. for ( i=0; i<length; i++ )
  487. {
  488. c=szCmdLineTmp[i];
  489. switch (szCmdLineTmp[i]) {
  490. case _T(' '):
  491. case _T('\t'): if (QuotedString)
  492. {
  493. break;
  494. }
  495. if (!SkippingSpaces)
  496. {
  497. szCmdLineTmp[i]='\0';
  498. SkippingSpaces=TRUE;
  499. }
  500. break;
  501. case _T('\"'): if (QuotedString)
  502. {
  503. // This is the end of a quoted string
  504. // The next character to read in is a space
  505. QuotedString=FALSE;
  506. SkippingSpaces=TRUE;
  507. szCmdLineTmp[i+1]=_T('\0');
  508. break;
  509. }
  510. if (SkippingSpaces) {
  511. // This is the beginning of a quoted string
  512. // Its a new argument and it follows spaces
  513. argv[argc]=szCmdLineTmp+i;
  514. argc++;
  515. SkippingSpaces=FALSE;
  516. QuotedString=TRUE;
  517. break;
  518. }
  519. // This is an error -- This is a quote in the middle of a string
  520. rc=FALSE;
  521. goto CommandLineFinish;
  522. break;
  523. default: if (QuotedString)
  524. {
  525. break;
  526. }
  527. if (SkippingSpaces) {
  528. argv[argc]=szCmdLineTmp+i;
  529. argc++;
  530. SkippingSpaces=FALSE;
  531. }
  532. break;
  533. }
  534. }
  535. // Now, parse the arguments
  536. NeedSecond=FALSE;
  537. for (i=0; i<argc; i++) {
  538. if (!NeedSecond)
  539. {
  540. if ( (argv[i][0] != '/') && (argv[i][0] != '-') )
  541. {
  542. rc=FALSE;
  543. goto CommandLineFinish;
  544. }
  545. if ( _tcslen(argv[i]) != 2 )
  546. {
  547. rc=FALSE;
  548. goto CommandLineFinish;
  549. }
  550. c=argv[i][1];
  551. switch ( c )
  552. {
  553. case 'q':
  554. case 'Q': pComArgs->QuietInstall=TRUE;
  555. break;
  556. case 'i':
  557. case 'I': NeedSecond=TRUE;;
  558. break;
  559. case 'n':
  560. case 'N': NeedSecond=TRUE;
  561. break;
  562. case 'z':
  563. case 'Z': pComArgs->StressInstall=TRUE;
  564. break;
  565. case 'u':
  566. case 'U': pComArgs->UIStressInstall=TRUE;
  567. pComArgs->StressInstall=TRUE;
  568. break;
  569. default: {
  570. rc=FALSE;
  571. goto CommandLineFinish;
  572. }
  573. }
  574. } else {
  575. NeedSecond = FALSE;
  576. switch ( c )
  577. {
  578. case 'i':
  579. case 'I': _tcscpy(pComArgs->szInstDir,argv[i]);
  580. break;
  581. case 'n':
  582. case 'N': _tcscpy(pComArgs->szMsiName,argv[i]);
  583. break;
  584. default: {
  585. rc=FALSE;
  586. goto CommandLineFinish;
  587. }
  588. }
  589. }
  590. }
  591. if (pComArgs->szMsiName[0] == 0)
  592. {
  593. #ifdef BUILD_X86
  594. _tcscpy(pComArgs->szMsiName, _T("dbg_x86.msi") );
  595. _tcscpy(pComArgs->szProductRegKey, _T("Software\\Microsoft\\DebuggingTools\\AddRemove") );
  596. #elif defined(BUILD_IA64)
  597. _tcscpy(pComArgs->szMsiName, _T("dbg_ia64.msi") );
  598. _tcscpy(pComArgs->szProductRegKey, _T("Software\\Microsoft\\DebuggingTools64\\AddRemove") );
  599. #endif
  600. }
  601. CommandLineFinish:
  602. free(szCmdLineTmp);
  603. free(argv);
  604. return (rc);
  605. }