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.

2324 lines
58 KiB

  1. /*++
  2. Copyright (c) 1997 Microsoft Corporation
  3. Module Name:
  4. migisol.c
  5. Abstract:
  6. Implements an EXE that is used to run migration DLLs in a
  7. separate address space (sandboxing).
  8. Author:
  9. Jim Schmidt (jimschm) 04-Aug-1997
  10. Revision History:
  11. jimschm 19-Mar-2001 Removed DVD check because it is now
  12. in a migration dll
  13. jimschm 02-Jun-1999 Added DVD checking support to avoid
  14. setup crash on a Win9x blue screen
  15. jimschm 18-Mar-1999 Added cleanup for cases where text mode
  16. fails and the user returns to Win9x.
  17. jimschm 23-Sep-1998 Converted to new IPC mechanism
  18. --*/
  19. #include "pch.h"
  20. #include "plugin.h"
  21. #include "migui.h"
  22. #include "ntui.h"
  23. #include "unattend.h"
  24. BOOL g_ReportPhase = FALSE;
  25. BOOL g_MigrationPhase = FALSE;
  26. TCHAR g_DllName[MAX_TCHAR_PATH] = "";
  27. P_INITIALIZE_NT InitializeNT;
  28. P_MIGRATE_USER_NT MigrateUserNT;
  29. P_MIGRATE_SYSTEM_NT MigrateSystemNT;
  30. P_QUERY_VERSION QueryVersion;
  31. P_INITIALIZE_9X Initialize9x;
  32. P_MIGRATE_USER_9X MigrateUser9x;
  33. P_MIGRATE_SYSTEM_9X MigrateSystem9x;
  34. BOOL
  35. WINAPI
  36. MigUtil_Entry (
  37. HINSTANCE hinstDLL,
  38. DWORD fdwReason,
  39. PVOID lpvReserved
  40. );
  41. BOOL
  42. IsNEC98(
  43. VOID
  44. );
  45. #define NO_GUI_ERROR 0
  46. //
  47. // Local functions
  48. //
  49. BOOL
  50. PackExeNames(
  51. PGROWBUFFER GrowBuf,
  52. PCSTR p
  53. );
  54. BOOL
  55. PackDword(
  56. PGROWBUFFER GrowBuf,
  57. DWORD dw
  58. );
  59. BOOL
  60. PackQuadWord(
  61. PGROWBUFFER GrowBuf,
  62. LONGLONG qw
  63. );
  64. BOOL
  65. PackIntArray(
  66. PGROWBUFFER GrowBuf,
  67. PINT Array
  68. );
  69. BOOL
  70. PackString (
  71. PGROWBUFFER GrowBuf,
  72. PCSTR String
  73. );
  74. BOOL
  75. PackBinary (
  76. PGROWBUFFER GrowBuf,
  77. PBYTE Data,
  78. DWORD DataSize
  79. );
  80. HINF
  81. pGetInfHandleFromFileNameW (
  82. PCWSTR UnattendFile
  83. );
  84. VOID
  85. ProcessCommands (
  86. VOID
  87. );
  88. BOOL
  89. pParseCommandLine (
  90. VOID
  91. );
  92. VOID
  93. DoInitializeNT (
  94. PCWSTR Args
  95. );
  96. VOID
  97. DoInitialize9x (
  98. PCSTR Args
  99. );
  100. VOID
  101. DoMigrateUserNT (
  102. PCWSTR Args
  103. );
  104. VOID
  105. DoQueryVersion (
  106. PCSTR Args
  107. );
  108. VOID
  109. DoMigrateUser9x (
  110. PCSTR Args
  111. );
  112. VOID
  113. DoMigrateSystemNT (
  114. PCWSTR Args
  115. );
  116. VOID
  117. DoMigrateSystem9x (
  118. PCSTR Args
  119. );
  120. HWND
  121. pFindParentWindow (
  122. IN PCTSTR WindowTitle,
  123. IN DWORD ProcessId
  124. );
  125. static HINSTANCE g_hLibrary;
  126. HANDLE g_hHeap;
  127. HINSTANCE g_hInst;
  128. #ifdef DEBUG
  129. #define DBG_MIGISOL "MigIsol"
  130. #endif
  131. INT
  132. WINAPI
  133. WinMain (
  134. HINSTANCE hInstance,
  135. HINSTANCE hPrevInstance,
  136. PSTR AnsiCmdLine,
  137. INT CmdShow
  138. )
  139. /*++
  140. Routine Description:
  141. The entry point to migisol.exe. The entire body of code is wrapped
  142. in a try/except block to catch all problems with any migration DLLs.
  143. Arguments:
  144. hInstance - The instance handle of this EXE
  145. hPrevInstance - The previous instance handle of this EXE if it is
  146. running, or NULL if no other instances exist.
  147. AnsiCmdLine - The command line (ANSI version)
  148. CmdShow - The ShowWindow command passed by the shell
  149. Return Value:
  150. Returns -1 if an error occurred, or 0 if the exe completed. The exe
  151. will automatically terminate with 0 if the migration DLL throws an
  152. exception.
  153. --*/
  154. {
  155. TCHAR OurDir[MAX_TCHAR_PATH];
  156. PTSTR p;
  157. __try {
  158. g_hInst = hInstance;
  159. g_hHeap = GetProcessHeap();
  160. *OurDir = 0;
  161. GetModuleFileName (NULL, OurDir, ARRAYSIZE(OurDir));
  162. p = _tcsrchr (OurDir, TEXT('\\'));
  163. if (p) {
  164. *p = 0;
  165. if (!_tcschr (OurDir, TEXT('\\'))) {
  166. p[0] = TEXT('\\');
  167. p[1] = 0;
  168. }
  169. SetCurrentDirectory (OurDir);
  170. //
  171. // Force a specific setupapi.dll to be loaded
  172. //
  173. StringCopy (AppendWack (OurDir), TEXT("setupapi.dll"));
  174. LoadLibraryEx (
  175. OurDir,
  176. NULL,
  177. LOAD_WITH_ALTERED_SEARCH_PATH
  178. );
  179. }
  180. // Initialize utility library
  181. if (!MigUtil_Entry (g_hInst, DLL_PROCESS_ATTACH, NULL)) {
  182. FreeLibrary (g_hLibrary);
  183. return -1;
  184. }
  185. DEBUGMSG ((DBG_MIGISOL, "migisol.exe started"));
  186. if (!pParseCommandLine()) {
  187. FreeLibrary (g_hLibrary);
  188. return -1;
  189. }
  190. DEBUGMSG ((DBG_MIGISOL, "CmdLine parsed"));
  191. if (!OpenIpc (FALSE, NULL, NULL, NULL)) {
  192. DEBUGMSG ((DBG_MIGISOL, "OpenIpc failed"));
  193. FreeLibrary (g_hLibrary);
  194. return -1;
  195. }
  196. __try {
  197. DEBUGMSG ((DBG_MIGISOL, "Processing commands"));
  198. ProcessCommands();
  199. }
  200. __except (TRUE) {
  201. LOG ((LOG_ERROR, "Upgrade Pack process is terminating because of an exception in WinMain"));
  202. }
  203. CloseIpc();
  204. FreeLibrary (g_hLibrary);
  205. DEBUGMSG ((DBG_MIGISOL, "migisol.exe terminating"));
  206. if (!MigUtil_Entry (g_hInst, DLL_PROCESS_DETACH, NULL)) {
  207. return -1;
  208. }
  209. }
  210. __except (TRUE) {
  211. }
  212. return 0;
  213. }
  214. #define WINNT32_SECTOR_SIZE 512
  215. #define WINNT32_FAT_BOOT_SECTOR_COUNT 1
  216. #define WINNT32_FAT_BOOT_SIZE (WINNT32_SECTOR_SIZE * WINNT32_FAT_BOOT_SECTOR_COUNT)
  217. #define FILE_ATTRIBUTE_RHS (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)
  218. BOOL
  219. pWriteFATBootSector (
  220. IN PCTSTR BootDataFile,
  221. IN TCHAR BootDriveLetter
  222. )
  223. {
  224. HANDLE BootDataHandle;
  225. BYTE Data[WINNT32_FAT_BOOT_SIZE];
  226. DWORD BytesRead;
  227. BOOL Success = FALSE;
  228. if (GetFileAttributes (BootDataFile) == INVALID_ATTRIBUTES) {
  229. DEBUGMSG ((DBG_ERROR, "Can't find %s", BootDataFile));
  230. return FALSE;
  231. }
  232. BootDataHandle = CreateFile (
  233. BootDataFile,
  234. GENERIC_READ,
  235. FILE_SHARE_READ,
  236. NULL,
  237. OPEN_EXISTING,
  238. FILE_FLAG_SEQUENTIAL_SCAN,
  239. NULL
  240. );
  241. if (BootDataHandle == INVALID_HANDLE_VALUE) {
  242. DEBUGMSG ((DBG_ERROR, "Can't open %s", BootDataFile));
  243. return FALSE;
  244. }
  245. Success = ReadFile (BootDataHandle, Data, WINNT32_FAT_BOOT_SIZE, &BytesRead, NULL) &&
  246. (BytesRead == WINNT32_FAT_BOOT_SIZE);
  247. CloseHandle (BootDataHandle);
  248. if (Success) {
  249. //
  250. // write the boot sector with this data; don't save NT boot sector
  251. //
  252. Success = WriteDiskSectors (
  253. BootDriveLetter,
  254. 0,
  255. WINNT32_FAT_BOOT_SECTOR_COUNT,
  256. WINNT32_SECTOR_SIZE,
  257. Data
  258. );
  259. DEBUGMSG_IF ((
  260. !Success,
  261. DBG_ERROR,
  262. "WriteDiskSectors failed for %c!",
  263. BootDriveLetter
  264. ));
  265. }
  266. ELSE_DEBUGMSG ((DBG_ERROR, "Unexpected boot sector size %u in %s", BytesRead, BootDataFile));
  267. return Success;
  268. }
  269. VOID
  270. pCleanUpUndoDirectory (
  271. CHAR BootDrive
  272. )
  273. /*++
  274. Routine Description:
  275. This function delete recursively all files and directories
  276. include and in %windir%\undo directory.
  277. Arguments:
  278. none
  279. Return Value:
  280. none
  281. --*/
  282. {
  283. TCHAR PathBuffer[MAX_PATH];
  284. TCHAR Answer[MAX_PATH];
  285. TCHAR NullPath[] = {0};
  286. DEBUGMSG((DBG_MIGISOL, "Cleanup routine of undo directory"));
  287. if(!BootDrive){
  288. if (!GetWindowsDirectory (PathBuffer, ARRAYSIZE(PathBuffer))) {
  289. DEBUGMSG((DBG_MIGISOL, "GetWindowsDirectory failed"));
  290. return;
  291. }
  292. BootDrive = PathBuffer[0];
  293. }
  294. wsprintf(PathBuffer, TEXT("%c:\\$win_nt$.~bt\\winnt.sif"), BootDrive);
  295. GetPrivateProfileString(
  296. S_WIN9XUPGUSEROPTIONS,
  297. S_PATH_FOR_BACKUP,
  298. NullPath,
  299. Answer,
  300. ARRAYSIZE(Answer),
  301. PathBuffer);
  302. if(!Answer[0]) {
  303. DEBUGMSG ((DBG_MIGISOL, "Failed to retrieve directory path"));
  304. return;
  305. }
  306. wsprintf(PathBuffer, TEXT("%c:\\$win_nt$.~bt\\dataloss"), BootDrive);
  307. if (DoesFileExist (PathBuffer)) {
  308. LOG ((
  309. LOG_INFORMATION,
  310. "Data loss was detected because of a failure to restore one or more files. "
  311. "The data can be recovered from backup files in %s.",
  312. Answer
  313. ));
  314. return;
  315. }
  316. SetFileAttributes(Answer, FILE_ATTRIBUTE_NORMAL);
  317. RemoveCompleteDirectory (Answer);
  318. DEBUGMSG ((DBG_MIGISOL, "Cleaned %s directory", Answer));
  319. }
  320. VOID
  321. pCleanUpAfterTextModeFailure (
  322. VOID
  323. )
  324. {
  325. TCHAR squiggleBtDir[] = TEXT("?:\\$win_nt$.~bt");
  326. TCHAR squiggleLsDir[] = TEXT("?:\\$win_nt$.~ls");
  327. TCHAR squiggleBuDir[] = TEXT("?:\\$win_nt$.~bu"); // for NEC98
  328. TCHAR drvLtr[] = TEXT("?:\\$DRVLTR$.~_~");
  329. TCHAR setupLdr[] = TEXT("?:\\$LDR$");
  330. TCHAR txtSetupSif[] = TEXT("?:\\TXTSETUP.SIF");
  331. PCTSTR bootSectDat;
  332. TCHAR setupTempDir[MAX_PATH];
  333. TCHAR bootIni[] = TEXT("?:\\boot.ini");
  334. TCHAR ntLdr[] = TEXT("?:\\NTLDR");
  335. TCHAR ntDetectCom[] = TEXT("?:\\NTDETECT.COM");
  336. TCHAR bootFontBin[] = TEXT("?:\\BOOTFONT.BIN");
  337. TCHAR bootSectDos[] = TEXT("?:\\BootSect.dos");
  338. TCHAR renamedFile1[] = TEXT("?:\\boot~tmp.$$1");
  339. TCHAR renamedFile2[] = TEXT("?:\\boot~tmp.$$2");
  340. TCHAR renamedFile3[] = TEXT("?:\\boot~tmp.$$3");
  341. BOOL noLdr = FALSE;
  342. BOOL noNtDetect = FALSE;
  343. BOOL noBootFontBin = FALSE;
  344. DWORD Drives;
  345. TCHAR DriveLetter;
  346. DWORD Bit;
  347. TCHAR Root[] = TEXT("?:\\");
  348. TCHAR Scratch[MAX_PATH];
  349. PCTSTR bootSectBak;
  350. PCTSTR bootIniBak;
  351. PCTSTR ntldrBak;
  352. PCTSTR bootFontBak;
  353. PCTSTR ntdetectBak;
  354. TCHAR WinDir[MAX_PATH];
  355. DWORD Type;
  356. DWORD Attribs;
  357. FILE_ENUM e;
  358. HANDLE WinInit;
  359. CHAR AnsiBuffer[MAX_PATH + 10];
  360. DWORD Dummy;
  361. PTSTR Write;
  362. BOOL prepareBootIni = FALSE;
  363. CHAR SystemDirPath[MAX_PATH];
  364. TCHAR bootDriveLetter;
  365. PCTSTR bootSectorFile;
  366. BOOL bootLoaderWritten;
  367. HKEY key;
  368. BOOL dontTouchBootCode = FALSE;
  369. if (ISNT()) {
  370. return;
  371. }
  372. DEBUGMSG ((DBG_MIGISOL, "Entering cleanup routine"));
  373. SuppressAllLogPopups (TRUE);
  374. //
  375. // Reinitialize system restore
  376. //
  377. key = OpenRegKeyStr (TEXT("HKLM\\SYSTEM\\CurrentControlSet\\Services\\VxD\\VxDMon"));
  378. if (key) {
  379. RegSetValueEx (key, TEXT("FirstRun"), 0, REG_SZ, (PCBYTE) "Y", 2);
  380. CloseRegKey (key);
  381. }
  382. //
  383. // Prepare windir and temp dir paths, get the bitmask of drive letters
  384. //
  385. // We need to know the system drive to be repaired, since win98 on NEC98
  386. // can boot up from any partition that can be installed.
  387. //
  388. GetSystemDirectory (SystemDirPath, MAX_PATH);
  389. if (!GetWindowsDirectory (setupTempDir, sizeof (setupTempDir) / sizeof (setupTempDir[0]))) {
  390. DEBUGMSG ((DBG_ERROR, "Can't get Windows dir"));
  391. return;
  392. } else {
  393. StringCopy (WinDir, setupTempDir);
  394. }
  395. StringCopy (AppendWack (setupTempDir), TEXT("setup"));
  396. Drives = GetLogicalDrives();
  397. bootDriveLetter = IsNEC98() ? SystemDirPath[0] : TEXT('C');
  398. if (WinDir[0] != bootDriveLetter) {
  399. dontTouchBootCode = TRUE;
  400. }
  401. //
  402. // Create paths
  403. //
  404. bootIniBak = JoinPaths (setupTempDir, S_BOOTINI_BACKUP);
  405. ntldrBak = JoinPaths (setupTempDir, S_NTLDR_BACKUP);
  406. ntdetectBak = JoinPaths (setupTempDir, S_NTDETECT_BACKUP);
  407. bootSectBak = JoinPaths (setupTempDir, S_BOOTSECT_BACKUP);
  408. bootFontBak = JoinPaths (setupTempDir, S_BOOTFONT_BACKUP);
  409. //
  410. // Deltree $win_nt$.~bt and $win_nt$.~ls
  411. //
  412. for (Bit = 1, DriveLetter = TEXT('A') ; Bit ; Bit <<= 1, DriveLetter++) {
  413. if (!(Drives & Bit)) {
  414. continue;
  415. }
  416. Root[0] = DriveLetter;
  417. Type = GetDriveType (Root);
  418. if (Type == DRIVE_FIXED || Type == DRIVE_UNKNOWN) {
  419. //
  420. // Clean this drive
  421. //
  422. squiggleBtDir[0] = DriveLetter;
  423. squiggleLsDir[0] = DriveLetter;
  424. RemoveCompleteDirectory (squiggleBtDir);
  425. RemoveCompleteDirectory (squiggleLsDir);
  426. //
  427. // On NEC98, there may be another temp directry to be clean up.
  428. //
  429. if (IsNEC98()) {
  430. squiggleBuDir[0] = DriveLetter;
  431. RemoveCompleteDirectory (squiggleBuDir);
  432. }
  433. }
  434. }
  435. DEBUGMSG ((DBG_MIGISOL, "Cleaned squiggle dirs"));
  436. //
  437. // Repair boot.ini (do not necessarily restore it to its original form though)
  438. // and clean the root of the drive.
  439. //
  440. for (Bit = 1, DriveLetter = TEXT('A') ; Bit ; Bit <<= 1, DriveLetter++) {
  441. if (!(Drives & Bit)) {
  442. continue;
  443. }
  444. //
  445. // On NEC98, there may be multiple boot files in each partition.
  446. // So we will just take care the system that booted up.
  447. //
  448. if (IsNEC98() && (DriveLetter != SystemDirPath[0])) {
  449. continue;
  450. }
  451. Root[0] = DriveLetter;
  452. Type = GetDriveType (Root);
  453. if (Type == DRIVE_FIXED || Type == DRIVE_UNKNOWN) {
  454. //
  455. // Remove setup from boot.ini if it is on this drive,
  456. // and clean root of the drive.
  457. //
  458. squiggleBtDir[0] = DriveLetter;
  459. squiggleLsDir[0] = DriveLetter;
  460. bootIni[0] = DriveLetter;
  461. drvLtr[0] = DriveLetter;
  462. setupLdr[0] = DriveLetter;
  463. ntLdr[0] = DriveLetter;
  464. ntDetectCom[0] = DriveLetter;
  465. bootFontBin[0] = DriveLetter;
  466. txtSetupSif[0] = DriveLetter;
  467. bootSectDos[0] = DriveLetter;
  468. renamedFile1[0] = DriveLetter;
  469. renamedFile2[0] = DriveLetter;
  470. renamedFile3[0] = DriveLetter;
  471. SetFileAttributes (drvLtr, FILE_ATTRIBUTE_NORMAL);
  472. DeleteFile (drvLtr);
  473. SetFileAttributes (setupLdr, FILE_ATTRIBUTE_NORMAL);
  474. DeleteFile (setupLdr);
  475. SetFileAttributes (txtSetupSif, FILE_ATTRIBUTE_NORMAL);
  476. DeleteFile (txtSetupSif);
  477. //
  478. // If this is the boot drive, and if we have a bootsect.bak and
  479. // boot.bak in the setup temp directory, then that means Win9x had
  480. // an initial boot.ini, and we must restore it. Otherwise there
  481. // was no boot.ini.
  482. //
  483. if (!dontTouchBootCode && DriveLetter == bootDriveLetter) {
  484. DEBUGMSG ((DBG_MIGISOL, "Processing boot drive %c", bootDriveLetter));
  485. //
  486. // test for existence of bootini.bak/bootsect.bak (we don't
  487. // care about the attributes).
  488. //
  489. Attribs = GetFileAttributes (bootIniBak);
  490. DEBUGMSG ((DBG_MIGISOL, "Attributes of %s: 0x%08X", bootIniBak, Attribs));
  491. if (Attribs != INVALID_ATTRIBUTES) {
  492. DEBUGMSG ((DBG_MIGISOL, "Attributes of %s: 0x%08X", bootSectBak, Attribs));
  493. Attribs = GetFileAttributes (bootSectBak);
  494. }
  495. //
  496. // if pair exists, then get attributes of real boot.ini file
  497. //
  498. if (Attribs != INVALID_ATTRIBUTES) {
  499. Attribs = GetFileAttributes (bootIni);
  500. if (Attribs == INVALID_ATTRIBUTES) {
  501. Attribs = FILE_ATTRIBUTE_RHS;
  502. }
  503. //
  504. // Restore ntdetect.com, ntldr, boot sector, and original
  505. // boot.ini.
  506. //
  507. DEBUGMSG ((DBG_MIGISOL, "Restoring dual-boot environment"));
  508. if (pWriteFATBootSector (bootSectBak, bootDriveLetter)) {
  509. SetFileAttributes (bootIni, FILE_ATTRIBUTE_NORMAL);
  510. CopyFile (bootIniBak, bootIni, FALSE); // ignore failure
  511. SetFileAttributes (bootIni, Attribs);
  512. //
  513. // Restore ntldr and ntdetect.com [as a pair]
  514. //
  515. if (DoesFileExist (ntldrBak) && DoesFileExist (ntdetectBak)) {
  516. //
  517. // wipe away collisions with our temp file names,
  518. // then move current working loader to temp files
  519. //
  520. if (DoesFileExist (ntLdr)) {
  521. SetFileAttributes (renamedFile1, FILE_ATTRIBUTE_NORMAL);
  522. DeleteFile (renamedFile1);
  523. MoveFile (ntLdr, renamedFile1);
  524. noLdr = FALSE;
  525. } else {
  526. noLdr = TRUE;
  527. }
  528. if (DoesFileExist (ntDetectCom)) {
  529. SetFileAttributes (renamedFile2, FILE_ATTRIBUTE_NORMAL);
  530. DeleteFile (renamedFile2);
  531. MoveFile (ntDetectCom, renamedFile2);
  532. noNtDetect = FALSE;
  533. } else {
  534. noNtDetect = TRUE;
  535. }
  536. if (DoesFileExist (bootFontBin)) {
  537. SetFileAttributes (renamedFile3, FILE_ATTRIBUTE_NORMAL);
  538. DeleteFile (renamedFile3);
  539. MoveFile (bootFontBin, renamedFile3);
  540. noBootFontBin = FALSE;
  541. } else {
  542. noBootFontBin = TRUE;
  543. }
  544. //
  545. // now attempt to copy backup files to loader location
  546. //
  547. bootLoaderWritten = FALSE;
  548. if (CopyFile (ntldrBak, ntLdr, FALSE)) {
  549. bootLoaderWritten = CopyFile (ntdetectBak, ntDetectCom, FALSE);
  550. DEBUGMSG_IF ((!bootLoaderWritten, DBG_ERROR, "Can't copy %s to %s", ntdetectBak, ntDetectCom));
  551. if (bootLoaderWritten && DoesFileExist (bootFontBak)) {
  552. bootLoaderWritten = CopyFile (bootFontBak, bootFontBin, FALSE);
  553. DEBUGMSG_IF ((!bootLoaderWritten, DBG_ERROR, "Can't copy %s to %s", bootFontBak, bootFontBin));
  554. }
  555. }
  556. ELSE_DEBUGMSG ((DBG_ERROR, "Can't copy %s to %s", ntldrBak, ntLdr));
  557. if (bootLoaderWritten) {
  558. //
  559. // success -- remove temp files
  560. //
  561. SetFileAttributes (renamedFile1, FILE_ATTRIBUTE_NORMAL);
  562. DeleteFile (renamedFile1);
  563. SetFileAttributes (renamedFile2, FILE_ATTRIBUTE_NORMAL);
  564. DeleteFile (renamedFile2);
  565. SetFileAttributes (renamedFile3, FILE_ATTRIBUTE_NORMAL);
  566. DeleteFile (renamedFile3);
  567. } else {
  568. //
  569. // fail -- restore temp files. If restoration
  570. // fails, then generate a working boot.ini.
  571. //
  572. SetFileAttributes (ntLdr, FILE_ATTRIBUTE_NORMAL);
  573. DeleteFile (ntLdr);
  574. SetFileAttributes (ntDetectCom, FILE_ATTRIBUTE_NORMAL);
  575. DeleteFile (ntDetectCom);
  576. SetFileAttributes (bootFontBin, FILE_ATTRIBUTE_NORMAL);
  577. DeleteFile (bootFontBin);
  578. if (!noLdr) {
  579. if (!MoveFile (renamedFile1, ntLdr)) {
  580. prepareBootIni = TRUE;
  581. DEBUGMSG ((DBG_ERROR, "Can't restore %s to %s", renamedFile1, ntLdr));
  582. }
  583. }
  584. if (!noNtDetect) {
  585. if (!MoveFile (renamedFile2, ntDetectCom)) {
  586. prepareBootIni = TRUE;
  587. DEBUGMSG ((DBG_ERROR, "Can't restore %s to %s", renamedFile2, ntDetectCom));
  588. }
  589. }
  590. if (!noBootFontBin) {
  591. if (!MoveFile (renamedFile3, bootFontBin)) {
  592. prepareBootIni = TRUE;
  593. DEBUGMSG ((DBG_ERROR, "Can't restore %s to %s", renamedFile3, bootFontBin));
  594. }
  595. }
  596. }
  597. }
  598. } else {
  599. LOG ((LOG_WARNING, "Unable to restore dual-boot loader"));
  600. }
  601. } else {
  602. //
  603. // Remove the NT boot code. Delete ntdetect.com,
  604. // bootfont.bin and ntldr. If any part of this code fails,
  605. // make a boot.ini that will work. (ntdetect.com won't
  606. // be needed.)
  607. //
  608. SetFileAttributes (ntDetectCom, FILE_ATTRIBUTE_NORMAL);
  609. DeleteFile (ntDetectCom);
  610. Attribs = GetFileAttributes (bootIni);
  611. if (Attribs != INVALID_ATTRIBUTES) {
  612. SetFileAttributes (bootIni, FILE_ATTRIBUTE_NORMAL);
  613. prepareBootIni = TRUE;
  614. //
  615. // SystemDrive is not only C: on NEC98. Also, boot.ini
  616. // should be always sitting on system drive but boot
  617. // drive during setup, if these are separated.
  618. // So we must take care the boot files on system drive.
  619. //
  620. if (GetFileAttributes (bootSectBak) != INVALID_ATTRIBUTES) {
  621. bootSectorFile = bootSectBak;
  622. } else {
  623. bootSectorFile = bootSectDos;
  624. }
  625. if (pWriteFATBootSector (bootSectorFile, bootDriveLetter)) {
  626. DEBUGMSG ((DBG_MIGISOL, "Successfully restored FAT boot sector"));
  627. //
  628. // restored original boot sector, NT boot files not required any longer
  629. //
  630. DeleteFile (bootIni);
  631. SetFileAttributes (ntLdr, FILE_ATTRIBUTE_NORMAL);
  632. DeleteFile (ntLdr);
  633. SetFileAttributes (bootSectDos, FILE_ATTRIBUTE_NORMAL);
  634. DeleteFile (bootSectDos);
  635. SetFileAttributes (ntDetectCom, FILE_ATTRIBUTE_NORMAL);
  636. DeleteFile (ntDetectCom);
  637. SetFileAttributes (bootFontBin, FILE_ATTRIBUTE_NORMAL);
  638. DeleteFile (bootFontBin);
  639. prepareBootIni = FALSE;
  640. } else {
  641. //
  642. // make sure this boot file is not accidentally
  643. // deleted by the end-user
  644. //
  645. SetFileAttributes (ntLdr, FILE_ATTRIBUTE_RHS);
  646. DEBUGMSG ((DBG_ERROR, "Cannot restore FAT boot sector from %s", bootSectDos));
  647. }
  648. }
  649. ELSE_DEBUGMSG ((DBG_MIGISOL, "Skipping removal of boot.ini because it is not present"));
  650. }
  651. //
  652. // If we have any failure, this code here will make a boot
  653. // sector & loader that at least boots Win9x.
  654. //
  655. if (prepareBootIni) {
  656. bootSectDat = JoinPaths (squiggleBtDir, TEXT("BOOTSECT.DAT"));
  657. WritePrivateProfileString (TEXT("Boot Loader"), TEXT("Default"), Root, bootIni);
  658. WritePrivateProfileString (TEXT("Operating Systems"), bootSectDat, NULL, bootIni);
  659. GetPrivateProfileString (TEXT("Operating Systems"), Root, TEXT(""), Scratch, MAX_PATH, bootIni);
  660. if (!Scratch[0]) {
  661. //
  662. // This should never ever occur, but for unknown cases we have this
  663. //
  664. WritePrivateProfileString (TEXT("Operating Systems"), Root, TEXT("Microsoft Windows"), bootIni);
  665. }
  666. WritePrivateProfileString (NULL, NULL, NULL, bootIni);
  667. SetFileAttributes (bootIni, Attribs);
  668. prepareBootIni = FALSE;
  669. FreePathString (bootSectDat);
  670. }
  671. }
  672. }
  673. }
  674. //
  675. // Remove setup's temp dir as best we can. This leaves some junk around,
  676. // but we will fix that on the next reboot.
  677. //
  678. RemoveCompleteDirectory (setupTempDir);
  679. //
  680. // put all remaining files in wininit.ini\[rename] they will be
  681. // automatically removed at next reboot
  682. //
  683. StringCopy (Scratch, WinDir);
  684. StringCopy (AppendWack (Scratch), TEXT("wininit.ini"));
  685. //
  686. // append "manually" since using WritePrivateProfileString will just
  687. // overwrite previous setting
  688. //
  689. if (EnumFirstFile (&e, setupTempDir, NULL)) {
  690. WinInit = CreateFile (
  691. Scratch,
  692. GENERIC_WRITE,
  693. 0,
  694. NULL,
  695. OPEN_ALWAYS,
  696. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
  697. NULL
  698. );
  699. if (WinInit != INVALID_HANDLE_VALUE) {
  700. StringCopyA (AnsiBuffer, "\r\n[rename]");
  701. if (WriteFile (WinInit, AnsiBuffer, _mbslen (AnsiBuffer), &Dummy, NULL)) {
  702. StringCopyA (AnsiBuffer, "\r\nNUL=");
  703. Write = GetEndOfString (AnsiBuffer);
  704. do {
  705. #ifdef UNICODE
  706. KnownSizeUnicodeToDbcs (Write, e.FullPath);
  707. #else
  708. StringCopyA (Write, e.FullPath);
  709. #endif
  710. if (!WriteFile (WinInit, AnsiBuffer, _mbslen (AnsiBuffer), &Dummy, NULL)) {
  711. break;
  712. }
  713. } while (EnumNextFile (&e));
  714. }
  715. CloseHandle (WinInit);
  716. }
  717. ELSE_DEBUGMSG ((DBG_MIGISOL, "Cannot create wininit.ini"));
  718. }
  719. ELSE_DEBUGMSG ((DBG_MIGISOL, "No files found in temp dir"));
  720. FreePathString (bootIniBak);
  721. FreePathString (ntldrBak);
  722. FreePathString (ntdetectBak);
  723. FreePathString (bootSectBak);
  724. FreePathString (bootFontBak);
  725. DEBUGMSG ((DBG_MIGISOL, "Leaving cleanup routine"));
  726. }
  727. BOOL
  728. pParseCommandLine (
  729. VOID
  730. )
  731. /*++
  732. Routine Description:
  733. Prepares the global variables g_hLibrary, g_ReportPhase, g_MigrationPhase,
  734. g_DllName and the migration DLL entry points (Initialize9x, etc.)
  735. Arguments:
  736. none
  737. Return Value:
  738. TRUE if the module was successfully loaded, or FALSE if a parsing
  739. error or load error occurred.
  740. --*/
  741. {
  742. PCTSTR CmdLine;
  743. PCTSTR *argv;
  744. INT argc;
  745. INT i;
  746. PCTSTR p;
  747. TCHAR drive;
  748. CmdLine = GetCommandLine();
  749. argv = CommandLineToArgv (CmdLine, &argc);
  750. if (!argv) {
  751. DEBUGMSG ((DBG_MIGISOL, "Parse error"));
  752. return FALSE;
  753. }
  754. //
  755. // Parse command line
  756. //
  757. for (i = 1 ; i < argc ; i++) {
  758. if (argv[i][0] == TEXT('-') || argv[i][0] == TEXT('/')) {
  759. p = _tcsinc (argv[i]);
  760. switch (_totlower (_tcsnextc (p))) {
  761. case 'r':
  762. // Report-phase
  763. g_ReportPhase = TRUE;
  764. break;
  765. case 'm':
  766. // Migration-phase
  767. g_MigrationPhase = TRUE;
  768. break;
  769. case 'b':
  770. drive = '\0';
  771. p = _tcsinc(p);
  772. if(p && ':' == _tcsnextc(p)){
  773. p = _tcsinc(p);
  774. if(p){
  775. drive = (TCHAR)_tcsnextc(p);
  776. }
  777. }
  778. pCleanUpUndoDirectory(drive);
  779. case 'c':
  780. // Restore Win9x
  781. pCleanUpAfterTextModeFailure();
  782. return FALSE;
  783. }
  784. }
  785. else if (!g_DllName[0]) {
  786. StringCopy (g_DllName, argv[i]);
  787. } else {
  788. DEBUGMSG ((DBG_MIGISOL, "Broken arg: %s", argv[i]));
  789. return FALSE;
  790. }
  791. }
  792. //
  793. // Verify expected options exist
  794. //
  795. // One must be FALSE while the other must be TRUE
  796. if (g_ReportPhase == g_MigrationPhase) {
  797. DEBUGMSG ((DBG_MIGISOL, "Too many args"));
  798. return FALSE;
  799. }
  800. if (!g_DllName[0]) {
  801. DEBUGMSG ((DBG_MIGISOL, "No operation"));
  802. return FALSE;
  803. }
  804. //
  805. // Load migration DLL
  806. //
  807. g_hLibrary = LoadLibraryEx (
  808. g_DllName,
  809. NULL,
  810. LOAD_WITH_ALTERED_SEARCH_PATH
  811. );
  812. // If it fails, assume the DLL does not want to be loaded
  813. if (!g_hLibrary) {
  814. LOG ((LOG_ERROR, "Cannot load %s, rc=%u", g_DllName, GetLastError()));
  815. return FALSE;
  816. }
  817. // Get proc addresses for NT-side functions
  818. InitializeNT = (P_INITIALIZE_NT) GetProcAddress (g_hLibrary, PLUGIN_INITIALIZE_NT);
  819. MigrateUserNT = (P_MIGRATE_USER_NT) GetProcAddress (g_hLibrary, PLUGIN_MIGRATE_USER_NT);
  820. MigrateSystemNT = (P_MIGRATE_SYSTEM_NT) GetProcAddress (g_hLibrary, PLUGIN_MIGRATE_SYSTEM_NT);
  821. // Get proc addresses for 9x-side functions
  822. QueryVersion = (P_QUERY_VERSION) GetProcAddress (g_hLibrary, PLUGIN_QUERY_VERSION);
  823. Initialize9x = (P_INITIALIZE_9X) GetProcAddress (g_hLibrary, PLUGIN_INITIALIZE_9X);
  824. MigrateUser9x = (P_MIGRATE_USER_9X) GetProcAddress (g_hLibrary, PLUGIN_MIGRATE_USER_9X);
  825. MigrateSystem9x = (P_MIGRATE_SYSTEM_9X) GetProcAddress (g_hLibrary, PLUGIN_MIGRATE_SYSTEM_9X);
  826. // If any function does not exist, ignore the out-of-spec DLL
  827. if (!QueryVersion || !Initialize9x || !MigrateUser9x || !MigrateSystem9x ||
  828. !InitializeNT || !MigrateUserNT || !MigrateSystemNT
  829. ) {
  830. LOG ((LOG_ERROR, "Cannot load %s, one or more functions missing", g_DllName));
  831. return FALSE;
  832. }
  833. return TRUE;
  834. }
  835. VOID
  836. ProcessCommands (
  837. VOID
  838. )
  839. /*++
  840. Routine Description:
  841. ProcessCommands waits on the IPC pipe for a command message. When
  842. a command message is received, it is dispatched to the processing
  843. function. If a terminate command is received, the EXE terminates.
  844. If no command is received in one second, the EXE terminates. Therefore,
  845. Setup must always be ready to feed the EXE commands.
  846. Arguments:
  847. none
  848. Return Value:
  849. none
  850. --*/
  851. {
  852. DWORD Command;
  853. PBYTE Data;
  854. DWORD DataSize;
  855. DEBUGMSG ((DBG_MIGISOL, "Starting to process %s", g_DllName));
  856. do {
  857. // We wait for an interval: w95upgnt.dll or w95upg.dll should be ready
  858. // to feed us continuously.
  859. //
  860. // Receive command, don't care about size, OK to fail.
  861. //
  862. if (!GetIpcCommand (
  863. IPC_GET_COMMAND_WIN9X,
  864. &Command,
  865. &Data,
  866. &DataSize
  867. )) {
  868. DEBUGMSG ((DBG_WARNING, "MIGISOL: No command recieved"));
  869. break;
  870. }
  871. DEBUGMSG ((DBG_NAUSEA, "MigIsol - Command recieved: %u", Command));
  872. switch (Command) {
  873. case IPC_QUERY:
  874. if (g_MigrationPhase) {
  875. } else {
  876. DoQueryVersion ((PCSTR) Data);
  877. }
  878. break;
  879. case IPC_INITIALIZE:
  880. if (g_MigrationPhase) {
  881. DoInitializeNT ((PCWSTR) Data);
  882. } else {
  883. DoInitialize9x ((PCSTR) Data);
  884. }
  885. break;
  886. case IPC_MIGRATEUSER:
  887. if (g_MigrationPhase) {
  888. DoMigrateUserNT ((PCWSTR) Data);
  889. } else {
  890. DoMigrateUser9x ((PCSTR) Data);
  891. }
  892. break;
  893. case IPC_MIGRATESYSTEM:
  894. if (g_MigrationPhase) {
  895. DoMigrateSystemNT ((PCWSTR) Data);
  896. } else {
  897. DoMigrateSystem9x ((PCSTR) Data);
  898. }
  899. break;
  900. case IPC_TERMINATE:
  901. DEBUGMSG ((DBG_MIGISOL, "Processing of %s is complete", g_DllName));
  902. return;
  903. default:
  904. DEBUGMSG ((DBG_MIGISOL, "ProcessCommands: Unrecognized command -- terminating"));
  905. return;
  906. }
  907. if (Data) {
  908. MemFree (g_hHeap, 0, Data);
  909. Data = NULL;
  910. }
  911. } while (Command != IPC_TERMINATE);
  912. }
  913. VOID
  914. DoInitializeNT (
  915. PCWSTR Args
  916. )
  917. /*++
  918. Routine Description:
  919. Calls migration DLL's InitializeNT function. This function unpacks
  920. the arguments passed by Setup, calls the migration DLL and returns
  921. the status code back to Setup.
  922. Arguments:
  923. Args - A pointer to the argument buffer sent by Setup. This buffer
  924. is received with the initialize command.
  925. Return Value:
  926. none
  927. --*/
  928. {
  929. PCWSTR WorkingDir = NULL;
  930. PCWSTR SourceDirs = NULL;
  931. PCWSTR EndOfSourceDirs;
  932. PDWORD ReservedSize;
  933. PVOID Reserved;
  934. DWORD rc = RPC_S_CALL_FAILED;
  935. DWORD TechnicalLogId = 0;
  936. DWORD GuiLogId = 0;
  937. //
  938. // Set pointers of IN parameters
  939. //
  940. WorkingDir = Args;
  941. SourceDirs = wcschr (Args, 0) + 1;
  942. EndOfSourceDirs = SourceDirs;
  943. while (*EndOfSourceDirs) {
  944. EndOfSourceDirs = wcschr (EndOfSourceDirs, 0);
  945. EndOfSourceDirs++;
  946. }
  947. ReservedSize = (PDWORD) (EndOfSourceDirs + 1);
  948. if (*ReservedSize) {
  949. Reserved = (PVOID) (ReservedSize + 1);
  950. } else {
  951. Reserved = NULL;
  952. }
  953. //
  954. // Set CWD
  955. //
  956. SetCurrentDirectoryW(WorkingDir);
  957. //
  958. // Call migration DLL function
  959. //
  960. __try {
  961. rc = InitializeNT (WorkingDir, SourceDirs, Reserved);
  962. }
  963. __except (TRUE) {
  964. // Send log message
  965. DEBUGMSG ((DBG_MIGISOL, "%s threw an exception in InitializeNT", g_DllName));
  966. rc = ERROR_NOACCESS;
  967. TechnicalLogId = MSG_EXCEPTION_MIGRATE_INIT_NT;
  968. }
  969. //
  970. // No OUT parameters to send
  971. //
  972. SendIpcCommandResults (rc, TechnicalLogId, GuiLogId, NULL, 0);
  973. }
  974. HINF
  975. pGetInfHandleFromFileNameW (
  976. PCWSTR UnattendFile
  977. )
  978. /*++
  979. Routine Description:
  980. pGetInfHandleFromFileName uses the Setup APIs to open the specified
  981. UnattendFile.
  982. Arguments:
  983. UnattendFile - A pointer to the UNICODE file name specifying the unattend
  984. file. This string is converted to ANSI and the ANSI version
  985. of SetupOpenInfFile is called.
  986. Return Value:
  987. The INF handle, or NULL (*not* INVALID_HANDLE_VALUE) if the file could not
  988. be opened.
  989. --*/
  990. {
  991. CHAR AnsiUnattendFile[MAX_MBCHAR_PATH];
  992. HINF UnattendHandle;
  993. KnownSizeWtoA (AnsiUnattendFile, UnattendFile);
  994. UnattendHandle = SetupOpenInfFileA (AnsiUnattendFile, NULL, INF_STYLE_OLDNT|INF_STYLE_WIN4, NULL);
  995. if (UnattendHandle == INVALID_HANDLE_VALUE) {
  996. DEBUGMSG ((DBG_ERROR, "pGetInfHandleFromFileNameW: Could not open %s", UnattendFile));
  997. UnattendHandle = NULL;
  998. }
  999. return UnattendHandle;
  1000. }
  1001. VOID
  1002. DoMigrateUserNT (
  1003. PCWSTR Args
  1004. )
  1005. /*++
  1006. Routine Description:
  1007. Calls migration DLL's MigrateUserNT function. This function unpacks
  1008. the arguments passed by Setup, calls the migration DLL and returns
  1009. the status code back to Setup.
  1010. Arguments:
  1011. Args - A pointer to the argument buffer sent by Setup. This buffer
  1012. is received with the IPC_MIGRATEUSER command.
  1013. Return Value:
  1014. none
  1015. --*/
  1016. {
  1017. PCWSTR UnattendFile;
  1018. PCWSTR UserRegKey;
  1019. PCWSTR UserName;
  1020. PCWSTR UserDomain;
  1021. PCWSTR FixedUserName;
  1022. PCWSTR UserProfileDir;
  1023. WCHAR OrgProfileDir[MAX_WCHAR_PATH];
  1024. HINF UnattendHandle = NULL;
  1025. HKEY UserRegHandle = NULL;
  1026. DWORD rc;
  1027. PVOID Reserved;
  1028. PDWORD ReservedBytesPtr;
  1029. DWORD TechnicalLogId = 0;
  1030. DWORD GuiLogId = 0;
  1031. __try {
  1032. //
  1033. // Preserve USERPROFILE environment variable
  1034. //
  1035. GetEnvironmentVariableW (S_USERPROFILEW, OrgProfileDir, MAX_WCHAR_PATH);
  1036. //
  1037. // Set pointers of IN parameters
  1038. //
  1039. UnattendFile = Args;
  1040. UserRegKey = wcschr (UnattendFile, 0) + 1;
  1041. UserName = wcschr (UserRegKey, 0) + 1;
  1042. UserDomain = wcschr (UserName, 0) + 1;
  1043. FixedUserName = wcschr (UserDomain, 0) + 1;
  1044. UserProfileDir = wcschr (FixedUserName, 0) + 1;
  1045. ReservedBytesPtr = (PDWORD) (wcschr (UserProfileDir, 0) + 1);
  1046. if (*ReservedBytesPtr) {
  1047. Reserved = (PVOID) (ReservedBytesPtr + 1);
  1048. } else {
  1049. Reserved = NULL;
  1050. }
  1051. //
  1052. // Set USERPROFILE
  1053. //
  1054. if (UserProfileDir[0]) {
  1055. WCHAR DebugDir[MAX_WCHAR_PATH];
  1056. SetEnvironmentVariableW (S_USERPROFILEW, UserProfileDir);
  1057. DEBUGMSG ((DBG_MIGISOL, "USERPROFILE set to %ls", UserProfileDir));
  1058. GetEnvironmentVariableW (S_USERPROFILEW, DebugDir, MAX_WCHAR_PATH);
  1059. DEBUGMSG ((DBG_MIGISOL, "USERPROFILE set to %ls", DebugDir));
  1060. }
  1061. //
  1062. // Get UnattendHandle and UserRegHandle
  1063. //
  1064. UnattendHandle = pGetInfHandleFromFileNameW (UnattendFile);
  1065. UserRegHandle = OpenRegKeyStrW (UserRegKey);
  1066. if (!UnattendHandle || !UserRegHandle) {
  1067. // Send log message and failure code
  1068. rc = ERROR_OPEN_FAILED;
  1069. } else {
  1070. //
  1071. // Call migration DLL function
  1072. //
  1073. __try {
  1074. rc = MigrateUserNT (
  1075. UnattendHandle,
  1076. UserRegHandle,
  1077. UserName[0] ? UserName : NULL,
  1078. Reserved
  1079. );
  1080. }
  1081. __except (TRUE) {
  1082. // Send log message and failure code
  1083. DEBUGMSG ((DBG_MIGISOL, "%s threw an exception in MigrateUserNT", g_DllName));
  1084. rc = ERROR_NOACCESS;
  1085. TechnicalLogId = MSG_EXCEPTION_MIGRATE_USER_NT;
  1086. }
  1087. }
  1088. //
  1089. // No OUT parameters to send
  1090. //
  1091. if (UserRegHandle) {
  1092. CloseRegKey (UserRegHandle);
  1093. UserRegHandle = NULL;
  1094. }
  1095. SendIpcCommandResults (rc, TechnicalLogId, GuiLogId, NULL, 0);
  1096. }
  1097. __finally {
  1098. //
  1099. // Clean up
  1100. //
  1101. SetEnvironmentVariableW (S_USERPROFILEW, OrgProfileDir);
  1102. if (UserRegHandle) {
  1103. CloseRegKey (UserRegHandle);
  1104. }
  1105. if (UnattendHandle != INVALID_HANDLE_VALUE) {
  1106. SetupCloseInfFile (UnattendHandle);
  1107. }
  1108. }
  1109. }
  1110. VOID
  1111. DoMigrateSystemNT (
  1112. PCWSTR Args
  1113. )
  1114. /*++
  1115. Routine Description:
  1116. Calls migration DLL's MigrateSystemNT function. This function unpacks
  1117. the arguments passed by Setup, calls the migration DLL and returns
  1118. the status code back to Setup.
  1119. Arguments:
  1120. Args - A pointer to the argument buffer sent by Setup. This buffer
  1121. is received with the IPC_MIGRATESYSTEM command.
  1122. Return Value:
  1123. none
  1124. --*/
  1125. {
  1126. PCWSTR UnattendFile;
  1127. HINF UnattendHandle = NULL;
  1128. DWORD rc;
  1129. PVOID Reserved;
  1130. PDWORD ReservedBytesPtr;
  1131. DWORD TechnicalLogId = 0;
  1132. DWORD GuiLogId = 0;
  1133. __try {
  1134. //
  1135. // Set pointers of IN parameters
  1136. //
  1137. UnattendFile = Args;
  1138. ReservedBytesPtr = (PDWORD) (wcschr (UnattendFile, 0) + 1);
  1139. if (*ReservedBytesPtr) {
  1140. Reserved = (PVOID) (ReservedBytesPtr + 1);
  1141. } else {
  1142. Reserved = NULL;
  1143. }
  1144. //
  1145. // Get UnattendHandle and UserRegHandle
  1146. //
  1147. UnattendHandle = pGetInfHandleFromFileNameW (UnattendFile);
  1148. if (!UnattendHandle) {
  1149. rc = ERROR_OPEN_FAILED;
  1150. } else {
  1151. //
  1152. // Call migration DLL function
  1153. //
  1154. __try {
  1155. rc = MigrateSystemNT (UnattendHandle, Reserved);
  1156. }
  1157. __except (TRUE) {
  1158. DEBUGMSG ((DBG_MIGISOL, "%s threw an exception in MigrateSystemNT", g_DllName));
  1159. rc = ERROR_NOACCESS;
  1160. TechnicalLogId = MSG_EXCEPTION_MIGRATE_SYSTEM_NT;
  1161. }
  1162. }
  1163. //
  1164. // No OUT parameters to send
  1165. //
  1166. SendIpcCommandResults (rc, TechnicalLogId, GuiLogId, NULL, 0);
  1167. }
  1168. __finally {
  1169. if (UnattendHandle != INVALID_HANDLE_VALUE) {
  1170. SetupCloseInfFile (UnattendHandle);
  1171. }
  1172. }
  1173. }
  1174. VOID
  1175. DoQueryVersion (
  1176. PCSTR Args
  1177. )
  1178. /*++
  1179. Routine Description:
  1180. Calls migration DLL's QueryVersion function. This function unpacks
  1181. the arguments passed by Setup, calls the migration DLL and returns
  1182. the status code back to Setup.
  1183. Arguments:
  1184. Args - A pointer to the argument buffer sent by Setup. This buffer
  1185. is received with the IPC_QUERY command.
  1186. Return Value:
  1187. none
  1188. --*/
  1189. {
  1190. DWORD rc = RPC_S_CALL_FAILED;
  1191. GROWBUFFER GrowBuf = GROWBUF_INIT;
  1192. PSTR ProductId = NULL;
  1193. UINT DllVersion = 0;
  1194. PDWORD CodePageArray = NULL;
  1195. PCSTR ExeNames = NULL;
  1196. PCSTR WorkingDir;
  1197. PVENDORINFO VendorInfo = NULL;
  1198. DWORD TechnicalLogId = 0;
  1199. DWORD GuiLogId = 0;
  1200. DEBUGMSG ((DBG_MIGISOL, "Entering DoQueryVersion"));
  1201. __try {
  1202. //
  1203. // Set pointers of IN parameters
  1204. //
  1205. WorkingDir = (PSTR)Args; // CWD for this process
  1206. //
  1207. // Change CWD
  1208. //
  1209. SetCurrentDirectory(WorkingDir);
  1210. //
  1211. // Call migration DLL function
  1212. //
  1213. __try {
  1214. DEBUGMSG ((DBG_MIGISOL, "QueryVersion: WorkingDir=%s", WorkingDir));
  1215. rc = QueryVersion (
  1216. &ProductId,
  1217. &DllVersion,
  1218. &CodePageArray,
  1219. &ExeNames,
  1220. &VendorInfo
  1221. );
  1222. DEBUGMSG ((DBG_MIGISOL, "QueryVersion rc=%u", rc));
  1223. DEBUGMSG ((DBG_MIGISOL, "VendorInfo=0x%X", VendorInfo));
  1224. }
  1225. __except (TRUE) {
  1226. DEBUGMSG ((
  1227. DBG_MIGISOL,
  1228. "%s threw an exception in QueryVersion",
  1229. g_DllName
  1230. ));
  1231. TechnicalLogId = MSG_MIGDLL_QUERYVERSION_EXCEPTION_LOG;
  1232. rc = ERROR_NOACCESS;
  1233. }
  1234. //
  1235. // Unless we know failure occurred, return out parameters
  1236. //
  1237. if (rc == ERROR_SUCCESS) {
  1238. //
  1239. // Pack product id string
  1240. //
  1241. if (!PackString (&GrowBuf, ProductId)) {
  1242. DEBUGMSG ((DBG_MIGISOL, "QueryVersion PackProductId failed"));
  1243. rc = GetLastError();
  1244. __leave;
  1245. }
  1246. //
  1247. // Pack DLL version
  1248. //
  1249. if (!PackDword(&GrowBuf, DllVersion)) {
  1250. rc = GetLastError();
  1251. DEBUGMSG ((DBG_MIGISOL, "QueryVersion DllVersion failed"));
  1252. __leave;
  1253. }
  1254. //
  1255. // Pack CP array
  1256. //
  1257. if (!PackIntArray(&GrowBuf, CodePageArray)) {
  1258. rc = GetLastError();
  1259. DEBUGMSG ((DBG_MIGISOL, "QueryVersion PackIntArray failed"));
  1260. __leave;
  1261. }
  1262. //
  1263. // Pack Exe Names
  1264. //
  1265. if (!PackExeNames(&GrowBuf, ExeNames)) {
  1266. rc = GetLastError();
  1267. DEBUGMSG ((DBG_MIGISOL, "QueryVersion PackExeNames failed"));
  1268. __leave;
  1269. }
  1270. //
  1271. // Pack PVENDORINFO
  1272. //
  1273. if (!PackDword(&GrowBuf, (DWORD) VendorInfo)) {
  1274. rc = GetLastError();
  1275. DEBUGMSG ((DBG_MIGISOL, "QueryVersion VendorInfo failed"));
  1276. __leave;
  1277. }
  1278. if (VendorInfo) {
  1279. if (!PackBinary (&GrowBuf, (PBYTE) VendorInfo, sizeof (VENDORINFO))) {
  1280. rc = GetLastError();
  1281. DEBUGMSG ((DBG_MIGISOL, "QueryVersion VendorInfo failed"));
  1282. __leave;
  1283. }
  1284. }
  1285. }
  1286. //
  1287. // Send the packed parameters
  1288. //
  1289. if (!SendIpcCommandResults (
  1290. rc,
  1291. TechnicalLogId,
  1292. GuiLogId,
  1293. GrowBuf.End ? GrowBuf.Buf : NULL,
  1294. GrowBuf.End
  1295. )) {
  1296. DEBUGMSG ((
  1297. DBG_ERROR,
  1298. "DoQueryVersion failed to send command response"
  1299. ));
  1300. LOG ((LOG_ERROR, "Upgrade Pack process could not send reply data"));
  1301. }
  1302. }
  1303. __finally {
  1304. FreeGrowBuffer(&GrowBuf);
  1305. }
  1306. DEBUGMSG ((DBG_MIGISOL, "Leaving DoQueryVersion, rc=%u", rc));
  1307. }
  1308. VOID
  1309. DoInitialize9x (
  1310. PCSTR Args
  1311. )
  1312. /*++
  1313. Routine Description:
  1314. Calls migration DLL's Initialize9x function. This function unpacks
  1315. the arguments passed by Setup, calls the migration DLL and returns
  1316. the status code back to Setup.
  1317. Arguments:
  1318. Args - A pointer to the argument buffer sent by Setup. This buffer
  1319. is received with the IPC_INITIALIZE command.
  1320. Return Value:
  1321. none
  1322. --*/
  1323. {
  1324. DWORD rc = RPC_S_CALL_FAILED;
  1325. PSTR WorkingDir = NULL;
  1326. PSTR SourceDirs = NULL;
  1327. PVOID Reserved;
  1328. DWORD ReservedSize;
  1329. PCSTR p;
  1330. DWORD TechnicalLogId = 0;
  1331. DWORD GuiLogId = 0;
  1332. GROWBUFFER GrowBuf = GROWBUF_INIT;
  1333. DEBUGMSG ((DBG_MIGISOL, "Entering DoInitialize9x"));
  1334. __try {
  1335. //
  1336. // Set pointers of IN parameters
  1337. //
  1338. WorkingDir = (PSTR)Args; // CWD for this process
  1339. SourceDirs = GetEndOfStringA (WorkingDir) + 1; // arg for Initialize9x
  1340. p = SourceDirs;
  1341. while (*p) {
  1342. p = GetEndOfStringA (p);
  1343. p++;
  1344. }
  1345. p++;
  1346. ReservedSize = *((PDWORD) p);
  1347. p = (PCSTR) ((PBYTE) p + sizeof (DWORD));
  1348. if (ReservedSize) {
  1349. Reserved = (PVOID) p;
  1350. p = (PCSTR) ((PBYTE) p + ReservedSize);
  1351. } else {
  1352. Reserved = NULL;
  1353. }
  1354. //
  1355. // Change CWD
  1356. //
  1357. SetCurrentDirectory(WorkingDir);
  1358. //
  1359. // Call migration DLL function
  1360. //
  1361. __try {
  1362. rc = Initialize9x (
  1363. WorkingDir,
  1364. SourceDirs,
  1365. Reserved
  1366. );
  1367. }
  1368. __except (TRUE) {
  1369. //
  1370. // Send log message
  1371. //
  1372. DEBUGMSG ((DBG_MIGISOL,
  1373. "%s threw an exception in Initialize9x",
  1374. g_DllName));
  1375. TechnicalLogId = MSG_MIGDLL_INITIALIZE9X_EXCEPTION_LOG;
  1376. rc = ERROR_NOACCESS;
  1377. }
  1378. //
  1379. // Send reserved
  1380. //
  1381. if (rc == ERROR_SUCCESS) {
  1382. //
  1383. // Pack reserved parameter
  1384. //
  1385. // Set ReservedSize to zero for now because the Reserved arg is an IN only
  1386. ReservedSize = 0;
  1387. if (!PackBinary (&GrowBuf, (PBYTE) Reserved, ReservedSize)) {
  1388. rc = GetLastError();
  1389. DEBUGMSG ((DBG_MIGISOL, "Initialize9x reserved failed"));
  1390. __leave;
  1391. }
  1392. }
  1393. //
  1394. // Send the packed parameters
  1395. //
  1396. if (!SendIpcCommandResults (
  1397. rc,
  1398. TechnicalLogId,
  1399. GuiLogId,
  1400. GrowBuf.End ? GrowBuf.Buf : NULL,
  1401. GrowBuf.End
  1402. )) {
  1403. DEBUGMSG ((
  1404. DBG_ERROR,
  1405. "DoInitializeNT failed to send command response"
  1406. ));
  1407. LOG ((LOG_ERROR, "Upgrade Pack process could not send reply data"));
  1408. }
  1409. }
  1410. __finally {
  1411. FreeGrowBuffer (&GrowBuf);
  1412. }
  1413. DEBUGMSG ((DBG_MIGISOL, "Leaving DoInitialize9x, rc=%u", rc));
  1414. }
  1415. VOID
  1416. DoMigrateUser9x (
  1417. IN PCSTR Args
  1418. )
  1419. /*++
  1420. Routine Description:
  1421. Calls migration DLL's MigrateUser9x function. This function unpacks
  1422. the arguments passed by Setup, calls the migration DLL and returns
  1423. the status code back to Setup.
  1424. Arguments:
  1425. Args - A pointer to the argument buffer sent by Setup. This buffer
  1426. is received with the IPC_MIGRATEUSER command.
  1427. Return Value:
  1428. none
  1429. --*/
  1430. {
  1431. PCSTR ParentWndTitle = NULL;
  1432. HWND ParentWnd;
  1433. PCSTR UnattendFile = NULL;
  1434. PCSTR UserRegKey = NULL;
  1435. PCSTR UserName = NULL;
  1436. HKEY UserRegHandle = NULL;
  1437. DWORD rc = RPC_S_CALL_FAILED;
  1438. DWORD ProcessId;
  1439. DWORD GuiLogId = 0;
  1440. DWORD TechnicalLogId = 0;
  1441. DEBUGMSG ((DBG_MIGISOL, "Entering DoMigrateUser9x"));
  1442. __try {
  1443. //
  1444. // Set pointers of IN parameters
  1445. //
  1446. ParentWndTitle = Args;
  1447. UnattendFile = GetEndOfStringA (ParentWndTitle) + 1;
  1448. ProcessId = *((PDWORD) UnattendFile);
  1449. UnattendFile = (PCSTR) ((PBYTE) UnattendFile + sizeof (DWORD));
  1450. UserRegKey = GetEndOfStringA (UnattendFile) + 1;
  1451. UserName = GetEndOfStringA (UserRegKey) + 1;
  1452. //
  1453. // Get UserRegHandle
  1454. //
  1455. UserRegHandle = OpenRegKeyStr(UserRegKey);
  1456. if (!UserRegHandle) {
  1457. rc = ERROR_OPEN_FAILED;
  1458. } else {
  1459. ParentWnd = pFindParentWindow (ParentWndTitle, ProcessId);
  1460. //
  1461. // Call migration DLL function
  1462. //
  1463. __try {
  1464. rc = MigrateUser9x(
  1465. ParentWnd,
  1466. UnattendFile,
  1467. UserRegHandle,
  1468. *UserName ? UserName : NULL,
  1469. NULL
  1470. );
  1471. }
  1472. __except (TRUE) {
  1473. //
  1474. // Send log message
  1475. //
  1476. DEBUGMSG ((
  1477. DBG_MIGISOL,
  1478. "%s threw an exception in MigrateUser9x",
  1479. g_DllName
  1480. ));
  1481. TechnicalLogId = MSG_MIGDLL_MIGRATEUSER9X_EXCEPTION_LOG;
  1482. rc = ERROR_NOACCESS;
  1483. }
  1484. }
  1485. //
  1486. // No need to return out parameters
  1487. //
  1488. if (UserRegHandle) {
  1489. CloseRegKey (UserRegHandle);
  1490. UserRegHandle = NULL;
  1491. }
  1492. SendIpcCommandResults (rc, TechnicalLogId, GuiLogId, NULL, 0);
  1493. }
  1494. __finally {
  1495. //
  1496. // Free resources
  1497. //
  1498. if (UserRegHandle) {
  1499. CloseRegKey (UserRegHandle);
  1500. }
  1501. }
  1502. DEBUGMSG ((DBG_MIGISOL, "Leaving MigrateUser9x , rc=%u", rc));
  1503. }
  1504. VOID
  1505. DoMigrateSystem9x(
  1506. IN PCSTR Args
  1507. )
  1508. /*++
  1509. Routine Description:
  1510. Calls migration DLL's MigrateSystem9x function. This function unpacks
  1511. the arguments passed by Setup, calls the migration DLL and returns
  1512. the status code back to Setup.
  1513. Arguments:
  1514. Args - A pointer to the argument buffer sent by Setup. This buffer
  1515. is received with the IPC_MIGRATESYSTEM command.
  1516. Return Value:
  1517. none
  1518. --*/
  1519. {
  1520. PCSTR ParentWndTitle = NULL;
  1521. DWORD ProcessId;
  1522. PCSTR UnattendFile = NULL;
  1523. HWND ParentWnd = NULL;
  1524. DWORD rc = RPC_S_CALL_FAILED;
  1525. DWORD TechnicalLogId = 0;
  1526. DWORD GuiLogId = 0;
  1527. DEBUGMSG ((DBG_MIGISOL, "Entering DoMigrateSystem9x"));
  1528. //
  1529. // Set pointers of IN parameters
  1530. //
  1531. ParentWndTitle = Args;
  1532. UnattendFile = GetEndOfStringA (ParentWndTitle) + 1;
  1533. ProcessId = *((PDWORD) UnattendFile);
  1534. UnattendFile = (PCSTR) ((PBYTE) UnattendFile + sizeof (DWORD));
  1535. //
  1536. // Get ParentWnd
  1537. //
  1538. ParentWnd = pFindParentWindow (ParentWndTitle, ProcessId);
  1539. //
  1540. // Call migration DLL function
  1541. //
  1542. __try {
  1543. rc = MigrateSystem9x(
  1544. ParentWnd,
  1545. UnattendFile,
  1546. NULL
  1547. );
  1548. }
  1549. __except (TRUE) {
  1550. //
  1551. // Send log message
  1552. //
  1553. DEBUGMSG ((
  1554. DBG_MIGISOL,
  1555. "%s threw an exception in MigrateSystem9x",
  1556. g_DllName
  1557. ));
  1558. TechnicalLogId = MSG_MIGDLL_MIGRATESYSTEM9X_EXCEPTION_LOG;
  1559. rc = ERROR_NOACCESS;
  1560. }
  1561. //
  1562. // No need to return out parameters
  1563. //
  1564. SendIpcCommandResults (rc, TechnicalLogId, GuiLogId, NULL, 0);
  1565. DEBUGMSG ((DBG_MIGISOL, "Leaving DoMigrateSystem9x, rc=%u", rc));
  1566. }
  1567. //
  1568. // Function packs a DWORD into a GrowBuffer.
  1569. BOOL
  1570. PackDword(
  1571. PGROWBUFFER GrowBuf,
  1572. DWORD dw
  1573. )
  1574. {
  1575. PVOID p;
  1576. p = GrowBuffer (GrowBuf, sizeof(DWORD));
  1577. if (!p) {
  1578. return FALSE;
  1579. }
  1580. CopyMemory (p, (PVOID)(&dw), sizeof(dw));
  1581. return TRUE;
  1582. }
  1583. //
  1584. // Function packs a LONGLONG into a GrowBuffer
  1585. BOOL
  1586. PackQuadWord(
  1587. PGROWBUFFER GrowBuf,
  1588. LONGLONG qw)
  1589. {
  1590. return (
  1591. PackDword(GrowBuf, (DWORD)qw) &&
  1592. PackDword(GrowBuf, (DWORD)(qw >> 32)));
  1593. }
  1594. //
  1595. // Function packs 1) a NULL ptr, or 2) array of int terminated by -1, into a
  1596. // GrowBuffer.
  1597. //
  1598. BOOL
  1599. PackIntArray(
  1600. PGROWBUFFER GrowBuf,
  1601. PINT Array
  1602. )
  1603. {
  1604. DWORD Count;
  1605. PDWORD ArrayPos;
  1606. if (!Array) {
  1607. if (!GrowBufAppendDword (GrowBuf, 0)) {
  1608. return FALSE;
  1609. }
  1610. } else {
  1611. __try {
  1612. Count = 1;
  1613. for (ArrayPos = Array ; (*ArrayPos) != -1 ; ArrayPos++) {
  1614. Count++;
  1615. }
  1616. }
  1617. __except (TRUE) {
  1618. LOG ((LOG_ERROR, "Upgrade Pack %s provided an invalid code page array", g_DllName));
  1619. SetLastError (ERROR_NOACCESS);
  1620. return FALSE;
  1621. }
  1622. if (!GrowBufAppendDword (GrowBuf, Count)) {
  1623. return FALSE;
  1624. }
  1625. for (ArrayPos = Array ; Count ; ArrayPos++, Count--) {
  1626. if (!GrowBufAppendDword (GrowBuf, (DWORD) (UINT) (*ArrayPos))) {
  1627. return FALSE;
  1628. }
  1629. }
  1630. }
  1631. return TRUE;
  1632. }
  1633. //
  1634. // Function packs 1) a NULL pointer, or 2) a multi-sz, into a GrowBuffer.
  1635. //
  1636. BOOL
  1637. PackExeNames(
  1638. PGROWBUFFER GrowBuf,
  1639. PCSTR ExeNames
  1640. )
  1641. {
  1642. PCSTR p;
  1643. if (ExeNames) {
  1644. __try {
  1645. for (p = ExeNames ; *p ; p = GetEndOfStringA (p) + 1) {
  1646. }
  1647. }
  1648. __except (TRUE) {
  1649. LOG ((LOG_ERROR, "Upgrade Pack %s provided an invalid file list", g_DllName));
  1650. SetLastError (ERROR_NOACCESS);
  1651. return FALSE;
  1652. }
  1653. // Append non-empty strings
  1654. for (p = ExeNames ; *p ; p = GetEndOfStringA (p) + 1) {
  1655. if (!MultiSzAppendA (GrowBuf, p)) {
  1656. return FALSE;
  1657. }
  1658. }
  1659. }
  1660. // Append a final empty string
  1661. if (!MultiSzAppendA(GrowBuf, "")) {
  1662. return FALSE;
  1663. }
  1664. return TRUE;
  1665. }
  1666. BOOL
  1667. PackString (
  1668. PGROWBUFFER GrowBuf,
  1669. PCSTR String
  1670. )
  1671. {
  1672. __try {
  1673. if (!MultiSzAppendA (GrowBuf, String)) {
  1674. return FALSE;
  1675. }
  1676. }
  1677. __except (TRUE) {
  1678. DEBUGMSG ((
  1679. DBG_ERROR,
  1680. "%s provided an invalid ProductID string (%xh)",
  1681. g_DllName,
  1682. String
  1683. ));
  1684. LOG ((LOG_ERROR, "Upgrade Pack %s provided an invalid product ID", g_DllName));
  1685. SetLastError (ERROR_NOACCESS);
  1686. return FALSE;
  1687. }
  1688. return TRUE;
  1689. }
  1690. BOOL
  1691. PackBinary (
  1692. PGROWBUFFER GrowBuf,
  1693. PBYTE Data,
  1694. DWORD DataSize
  1695. )
  1696. {
  1697. PBYTE Buf;
  1698. if (!PackDword (GrowBuf, DataSize)) {
  1699. return FALSE;
  1700. }
  1701. if (!DataSize) {
  1702. return TRUE;
  1703. }
  1704. Buf = GrowBuffer (GrowBuf, DataSize);
  1705. if (!Buf) {
  1706. return FALSE;
  1707. }
  1708. __try {
  1709. CopyMemory (Buf, Data, DataSize);
  1710. }
  1711. __except (TRUE) {
  1712. DEBUGMSG ((
  1713. DBG_ERROR,
  1714. "%s provided an invalid binary parameter (%xh)",
  1715. g_DllName,
  1716. Data
  1717. ));
  1718. LOG ((LOG_ERROR, "Upgrade Pack %s provided an invalid binary parameter", g_DllName));
  1719. SetLastError (ERROR_NOACCESS);
  1720. return FALSE;
  1721. }
  1722. return TRUE;
  1723. }
  1724. typedef struct {
  1725. PCTSTR WindowTitle;
  1726. DWORD ProcessId;
  1727. HWND Match;
  1728. } FINDWINDOW_STRUCT, *PFINDWINDOW_STRUCT;
  1729. BOOL
  1730. CALLBACK
  1731. pEnumWndProc (
  1732. HWND hwnd,
  1733. LPARAM lParam
  1734. )
  1735. /*++
  1736. Routine Description:
  1737. A callback that is called for every top level window on the system.
  1738. It is used with pFindParentWindow to locate a specific window.
  1739. Arguments:
  1740. hwnd - Specifies the handle of the current enumerated window
  1741. lParam - Specifies a pointer to a FINDWINDOW_STRUCT variable that
  1742. holds WindowTitle and ProcessId, and receives the
  1743. handle if a match is found.
  1744. Return Value:
  1745. The handle to the matching window, or NULL if no window has the
  1746. specified title and process ID.
  1747. --*/
  1748. {
  1749. TCHAR Title[MAX_TCHAR_PATH];
  1750. DWORD ProcessId;
  1751. PFINDWINDOW_STRUCT p;
  1752. p = (PFINDWINDOW_STRUCT) lParam;
  1753. GetWindowText (hwnd, Title, MAX_TCHAR_PATH);
  1754. GetWindowThreadProcessId (hwnd, &ProcessId);
  1755. DEBUGMSG ((DBG_MIGISOL, "Testing window: %s, ID=%x against %s, %x",
  1756. Title, ProcessId, p->WindowTitle, p->ProcessId));
  1757. if (!StringCompare (Title, p->WindowTitle) &&
  1758. ProcessId == p->ProcessId) {
  1759. p->Match = hwnd;
  1760. LogReInit (&hwnd, NULL);
  1761. DEBUGMSG ((DBG_MIGISOL, "Window found: %s, ID=%u", Title, ProcessId));
  1762. return FALSE;
  1763. }
  1764. return TRUE;
  1765. }
  1766. HWND
  1767. pFindParentWindow (
  1768. IN PCTSTR WindowTitle,
  1769. IN DWORD ProcessId
  1770. )
  1771. /*++
  1772. Routine Description:
  1773. Locates the wizard window by enumerating all top-level windows.
  1774. The first one to match the supplied title and process ID is used.
  1775. Arguments:
  1776. WindowTitle - Specifies the name of the window to find.
  1777. ProcessId - Specifies the ID of the process who owns the window. If
  1778. zero is specified, NULL is returned.
  1779. Return Value:
  1780. The handle to the matching window, or NULL if no window has the
  1781. specified title and process ID.
  1782. --*/
  1783. {
  1784. FINDWINDOW_STRUCT FindWndStruct;
  1785. // If no process ID, we cannot have a match
  1786. if (!ProcessId) {
  1787. return NULL;
  1788. }
  1789. ZeroMemory (&FindWndStruct, sizeof (FindWndStruct));
  1790. FindWndStruct.WindowTitle = WindowTitle;
  1791. FindWndStruct.ProcessId = ProcessId;
  1792. EnumWindows (pEnumWndProc, (LPARAM) &FindWndStruct);
  1793. return FindWndStruct.Match;
  1794. }
  1795. //
  1796. // Check platform that I'm runnig on. Copyed from winnt32[au].dll.
  1797. // TRUE - NEC98
  1798. // FALSE - others(includes x86)
  1799. //
  1800. BOOL
  1801. IsNEC98(
  1802. VOID
  1803. )
  1804. {
  1805. static BOOL Checked = FALSE;
  1806. static BOOL Is98;
  1807. if(!Checked) {
  1808. Is98 = ((GetKeyboardType(0) == 7) && ((GetKeyboardType(1) & 0xff00) == 0x0d00));
  1809. Checked = TRUE;
  1810. }
  1811. return(Is98);
  1812. }