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.

2570 lines
62 KiB

  1. /*++
  2. Copyright (c) 1997-1999 Microsoft Corporation
  3. Module Name:
  4. dbaction.c
  5. Abstract:
  6. This source implements action functions used by MigDb. There are two types
  7. of action functions here as the third parameter of the macro list is TRUE
  8. or FALSE.
  9. First type of action function is called whenever an action is triggered
  10. during file scanning. The second type of action function is called at the
  11. end of file scanning if the associated action was not triggered during
  12. file scanning phase.
  13. Author:
  14. Calin Negreanu (calinn) 07-Jan-1998
  15. Revision History:
  16. jimschm 20-Nov-2000 Added MarkForBackup
  17. marcw 31-Aug-1999 Added BlockingHardware
  18. ovidiut 20-Jul-1999 Added Ignore
  19. ovidiut 28-May-1999 Added IniFileMappings
  20. marcw 23-Sep-1998 Added BlockingVirusScanner
  21. jimschm 13-Aug-1998 Added CompatibleFiles
  22. jimschm 19-May-1998 Added MinorProblems_NoLinkRequired
  23. jimschm 27-Feb-1998 Added UninstallSections
  24. calinn 18-Jan-1998 Added CompatibleModules action
  25. --*/
  26. #include "pch.h"
  27. #include "migdbp.h"
  28. #include "migappp.h"
  29. /*++
  30. Macro Expansion List Description:
  31. GATHER_DATA_FUNCTIONS and ACTION_FUNCTIONS lists all valid actions to be performed
  32. by migdb when a context is met. Meeting a context means that all the sections
  33. associated with the context are satisfied (usually there is only one section).
  34. The difference is that GATHER_DATA_FUNCTIONS are called even if some function already
  35. handles a file.
  36. Line Syntax:
  37. DEFMAC(ActionFn, ActionName, CallWhenTriggered, CanHandleVirtualFiles)
  38. Arguments:
  39. ActionFn - This is a boolean function that returnes TRUE if the specified action
  40. could be performed. It should return FALSE only if a serious error
  41. occures. You must implement a function with this name and required
  42. parameters.
  43. ActionName - This is the string that identifies the action function. It should
  44. have the same value as listed in migdb.inf. This arg is declared
  45. as both a macro and the migdb.inf section name string.
  46. CallWhenTriggered - If the MigDbContext this action is associated with is triggered
  47. the action will be called if this field is TRUE, otherwise we will call
  48. the action at the end of file scan if the context was not triggered.
  49. CanHandleVirtualFiles - This is for treating files that are supposed to be in a fixed place
  50. but are not there (not installed or deleted). We need this in order to fix
  51. registry or links that point to this kind of files. A good example is backup.exe
  52. which is located in %ProgramFiles%\Accessories. The rules say that we should
  53. use ntbackup.exe instead but since this file is not existent we don't normally fix
  54. registry settings pointing to this file. We do now, with this new variable.
  55. Variables Generated From List:
  56. g_ActionFunctions - do not touch!
  57. For accessing the array there are the following functions:
  58. MigDb_GetActionAddr
  59. MigDb_GetActionIdx
  60. MigDb_GetActionName
  61. --*/
  62. /*
  63. Declare the macro list of action functions. If you need to add a new action just
  64. add a line in this list and implement the function.
  65. */
  66. #define GATHER_DATA_FUNCTIONS \
  67. DEFMAC1(CompatibleFiles, COMPATIBLEFILES, TRUE, FALSE) \
  68. DEFMAC1(CompatibleShellModules, COMPATIBLESHELLMODULES, TRUE, FALSE) \
  69. DEFMAC1(CompatibleRunKeyModules, COMPATIBLERUNKEYMODULES, TRUE, FALSE) \
  70. DEFMAC1(CompatibleDosModules, COMPATIBLEDOSMODULES, TRUE, FALSE) \
  71. DEFMAC1(CompatibleHlpFiles, COMPATIBLEHLPFILES, TRUE, FALSE) \
  72. DEFMAC1(StoreMapi32Locations, MAPI32, TRUE, FALSE) \
  73. DEFMAC1(IniFileMappings, INIFILEMAPPINGS, TRUE, FALSE) \
  74. DEFMAC1(SilentUninstall, SILENTUNINSTALL, TRUE, FALSE) \
  75. DEFMAC1(FileEdit, FILEEDIT, TRUE, FALSE) \
  76. DEFMAC1(IgnoreInReport, IGNORE, TRUE, FALSE) \
  77. DEFMAC1(MarkForBackup, MARKFORBACKUP, TRUE, FALSE) \
  78. DEFMAC1(ShowInSimplifiedView, SHOWINSIMPLIFIEDVIEW, TRUE, FALSE) \
  79. #define ACTION_FUNCTIONS \
  80. DEFMAC2(UseNtFiles, USENTFILES, TRUE, TRUE ) \
  81. DEFMAC2(ProductCollisions, PRODUCTCOLLISIONS, TRUE, FALSE) \
  82. DEFMAC2(MinorProblems, MINORPROBLEMS, TRUE, FALSE) \
  83. DEFMAC2(MinorProblems_NoLinkRequired, MINORPROBLEMS_NOLINKREQUIRED, TRUE, FALSE) \
  84. DEFMAC2(Reinstall_AutoUninstall, REINSTALL_AUTOUNINSTALL, TRUE, FALSE) \
  85. DEFMAC2(Reinstall, REINSTALL, TRUE, FALSE) \
  86. DEFMAC2(Reinstall_NoLinkRequired, REINSTALL_NOLINKREQUIRED, TRUE, FALSE) \
  87. DEFMAC2(Reinstall_SoftBlock, REINSTALL_SOFTBLOCK, TRUE, FALSE) \
  88. DEFMAC2(Incompatible_AutoUninstall, INCOMPATIBLE_AUTOUNINSTALL, TRUE, FALSE) \
  89. DEFMAC2(Incompatible, INCOMPATIBLE, TRUE, FALSE) \
  90. DEFMAC2(Incompatible_NoLinkRequired, INCOMPATIBLE_NOLINKREQUIRED, TRUE, FALSE) \
  91. DEFMAC2(Incompatible_PreinstalledUtilities, INCOMPATIBLE_PREINSTALLEDUTILITIES, TRUE, FALSE) \
  92. DEFMAC2(Incompatible_SimilarOsFunctionality,INCOMPATIBLE_SIMILAROSFUNCTIONALITY,TRUE, FALSE) \
  93. DEFMAC2(Incompatible_HardwareUtility, INCOMPATIBLE_HARDWAREUTILITY, TRUE, FALSE) \
  94. DEFMAC1(BadFusion, BADFUSION, TRUE, FALSE) \
  95. DEFMAC2(OsFiles, OSFILES, TRUE, FALSE) \
  96. DEFMAC2(DosApps, DOSAPPS, TRUE, FALSE) \
  97. DEFMAC2(NonPnpDrivers, NONPNPDRIVERS, TRUE, FALSE) \
  98. DEFMAC2(NonPnpDrivers_NoMessage, NONPNPDRIVERS_NOMESSAGE, TRUE, FALSE) \
  99. DEFMAC2(MigrationProcessing, MIGRATIONPROCESSING, TRUE, FALSE) \
  100. DEFMAC2(BlockingVirusScanner, BLOCKINGVIRUSSCANNERS, TRUE, FALSE) \
  101. DEFMAC2(BlockingFile, BLOCKINGFILES, TRUE, FALSE) \
  102. DEFMAC2(BlockingHardware, BLOCKINGHARDWARE, TRUE, FALSE) \
  103. DEFMAC2(RequiredOSFiles, REQUIREDOSFILES, FALSE,FALSE) \
  104. /*
  105. Declare the action functions
  106. */
  107. #define DEFMAC1(fn,id,call,can) ACTION_PROTOTYPE fn;
  108. #define DEFMAC2(fn,id,call,can) ACTION_PROTOTYPE fn;
  109. GATHER_DATA_FUNCTIONS
  110. ACTION_FUNCTIONS
  111. #undef DEFMAC1
  112. #undef DEFMAC2
  113. /*
  114. This is the structure used for handling action functions
  115. */
  116. typedef struct {
  117. PCSTR ActionName;
  118. PACTION_PROTOTYPE ActionFunction;
  119. BOOL CallWhenTriggered;
  120. BOOL CanHandleVirtualFiles;
  121. BOOL CallAlways;
  122. } ACTION_STRUCT, *PACTION_STRUCT;
  123. /*
  124. Declare a global array of functions and name identifiers for action functions
  125. */
  126. #define DEFMAC1(fn,id,call,can) {#id,fn,call,can,TRUE},
  127. #define DEFMAC2(fn,id,call,can) {#id,fn,call,can,FALSE},
  128. static ACTION_STRUCT g_ActionFunctions[] = {
  129. GATHER_DATA_FUNCTIONS
  130. ACTION_FUNCTIONS
  131. {NULL, NULL, FALSE}
  132. };
  133. #undef DEFMAC1
  134. #undef DEFMAC2
  135. BOOL g_BadVirusScannerFound = FALSE;
  136. BOOL g_BlockingFileFound = FALSE;
  137. BOOL g_BlockingHardwareFound = FALSE;
  138. BOOL g_UnknownOs = FALSE;
  139. GROWLIST g_BadVirusScannerGrowList = GROWLIST_INIT;
  140. DWORD g_BackupDirCount = 0;
  141. extern BOOL g_IsFusionDir;
  142. BOOL
  143. pNoLinkRequiredWorker (
  144. IN UINT GroupId,
  145. IN UINT SubGroupId, OPTIONAL
  146. IN DWORD ActionType,
  147. IN PMIGDB_CONTEXT Context
  148. );
  149. PACTION_PROTOTYPE
  150. MigDb_GetActionAddr (
  151. IN INT ActionIdx
  152. )
  153. /*++
  154. Routine Description:
  155. MigDb_GetActionAddr returns the address of the action function based on the action index
  156. Arguments:
  157. ActionIdx - Action index.
  158. Return value:
  159. Action function address. Note that no checking is made so the address returned could be invalid.
  160. This is not a problem since the parsing code did the right job.
  161. --*/
  162. {
  163. return g_ActionFunctions[ActionIdx].ActionFunction;
  164. }
  165. INT
  166. MigDb_GetActionIdx (
  167. IN PCSTR ActionName
  168. )
  169. /*++
  170. Routine Description:
  171. MigDb_GetActionIdx returns the action index based on the action name
  172. Arguments:
  173. ActionName - Action name.
  174. Return value:
  175. Action index. If the name is not found, the index returned is -1.
  176. --*/
  177. {
  178. PACTION_STRUCT p = g_ActionFunctions;
  179. INT i = 0;
  180. while (p->ActionName != NULL) {
  181. if (StringIMatch (p->ActionName, ActionName)) {
  182. return i;
  183. }
  184. p++;
  185. i++;
  186. }
  187. return -1;
  188. }
  189. PCSTR
  190. MigDb_GetActionName (
  191. IN INT ActionIdx
  192. )
  193. /*++
  194. Routine Description:
  195. MigDb_GetActionName returns the name of an action based on the action index
  196. Arguments:
  197. ActionIdx - Action index.
  198. Return value:
  199. Action name. Note that no checking is made so the returned pointer could be invalid.
  200. This is not a problem since the parsing code did the right job.
  201. --*/
  202. {
  203. return g_ActionFunctions[ActionIdx].ActionName;
  204. }
  205. BOOL
  206. MigDb_CallWhenTriggered (
  207. IN INT ActionIdx
  208. )
  209. /*++
  210. Routine Description:
  211. MigDb_CallWhenTriggered is called every time when an action is triggered. Will return
  212. TRUE is the associated action function needs to be called, FALSE otherwise.
  213. Arguments:
  214. ActionIdx - Action index.
  215. Return value:
  216. TRUE if the associated action function needs to be called, FALSE otherwise.
  217. --*/
  218. {
  219. return g_ActionFunctions[ActionIdx].CallWhenTriggered;
  220. }
  221. BOOL
  222. MigDb_CanHandleVirtualFiles (
  223. IN INT ActionIdx
  224. )
  225. /*++
  226. Routine Description:
  227. MigDb_CanHandleVirtualFiles is called if a virtual file is passed to migdb
  228. Arguments:
  229. ActionIdx - Action index.
  230. Return value:
  231. TRUE if the associated action can handle virtual files, false if not.
  232. --*/
  233. {
  234. return g_ActionFunctions[ActionIdx].CanHandleVirtualFiles;
  235. }
  236. BOOL
  237. MigDb_CallAlways (
  238. IN INT ActionIdx
  239. )
  240. /*++
  241. Routine Description:
  242. MigDb_CallAlways returnes if an action should be called regardless of handled state.
  243. Arguments:
  244. ActionIdx - Action index.
  245. Return value:
  246. TRUE if the associated action should be called every time.
  247. --*/
  248. {
  249. return g_ActionFunctions[ActionIdx].CallAlways;
  250. }
  251. BOOL
  252. IsMigrationPathEx (
  253. IN PCTSTR Path,
  254. OUT PBOOL IsWin9xOsPath OPTIONAL
  255. )
  256. /*++
  257. Routine Description:
  258. IsMigrationPath checks to see if the given path is a "MigrationPath" (meaning
  259. that the path belongs to the OS that we are currently upgrading). The MigrationPaths
  260. category was previously created in filescan.c by pBuildMigrationPaths
  261. Arguments:
  262. Path - Specifies the path to be checked.
  263. IsWin9xOsPath - Receives a BOOL indicating if this path is a Win9x OS migration path
  264. Return value:
  265. TRUE if the path is part of "Migration Paths", FALSE otherwise.
  266. --*/
  267. {
  268. TCHAR key [MEMDB_MAX];
  269. PTSTR pathPtr;
  270. DWORD depth = 0;
  271. DWORD pathValue;
  272. TCHAR path[MAX_TCHAR_PATH];
  273. DWORD bIsWin9xOsPath;
  274. StringCopyTcharCount (path, Path, MAX_TCHAR_PATH);
  275. RemoveWackAtEnd (path);
  276. MemDbBuildKey (key, MEMDB_CATEGORY_MIGRATION_PATHS, path, NULL, NULL);
  277. pathPtr = GetEndOfString (key);
  278. if (IsWin9xOsPath) {
  279. *IsWin9xOsPath = FALSE;
  280. }
  281. while (pathPtr) {
  282. *pathPtr = 0;
  283. if ((MemDbGetValueAndFlags (key, &pathValue, &bIsWin9xOsPath)) &&
  284. (pathValue >= depth)
  285. ) {
  286. if (IsWin9xOsPath) {
  287. *IsWin9xOsPath = bIsWin9xOsPath;
  288. }
  289. return TRUE;
  290. }
  291. depth ++;
  292. pathPtr = GetPrevChar (key, pathPtr, TEXT('\\'));
  293. }
  294. return FALSE;
  295. }
  296. BOOL
  297. DeleteFileWithWarning (
  298. IN PCTSTR FileName
  299. )
  300. /*++
  301. Routine Description:
  302. DeleteFileWithWarning marks a file for deletion but checks to see if a warning
  303. should be added in user report. We will generate a "backup files found" warning
  304. if the file that's going to be deleted is outside "Migration Paths",
  305. but not under %ProgramFiles%.
  306. Arguments:
  307. FileName - file to be deleted.
  308. Return value:
  309. TRUE if the file was deleted successfully, FALSE otherwise.
  310. --*/
  311. {
  312. PCTSTR filePtr;
  313. TCHAR filePath [MEMDB_MAX];
  314. TCHAR key [MEMDB_MAX];
  315. INFCONTEXT ic;
  316. RemoveOperationsFromPath (FileName, ALL_DEST_CHANGE_OPERATIONS);
  317. filePtr = (PTSTR)GetFileNameFromPath (FileName);
  318. if (!filePtr) {
  319. return FALSE;
  320. }
  321. filePtr = _tcsdec (FileName, filePtr);
  322. if (!filePtr) {
  323. return FALSE;
  324. }
  325. StringCopyABA (filePath, FileName, filePtr);
  326. if (!StringIMatchCharCount (filePath, g_ProgramFilesDirWack, g_ProgramFilesDirWackChars) &&
  327. !IsMigrationPath (filePath)
  328. ) {
  329. //
  330. // exclude certain files from user's report,
  331. // even if they are not in migration directories
  332. //
  333. if (!SetupFindFirstLine (g_Win95UpgInf, S_BACKUPFILESIGNORE, filePtr + 1, &ic)) {
  334. //
  335. // this file is not excluded; show it's dir in the report
  336. //
  337. MemDbBuildKey (key, MEMDB_CATEGORY_BACKUPDIRS, filePath, NULL, NULL);
  338. if (!MemDbGetValue (key, NULL)) {
  339. //
  340. // write it in the log
  341. //
  342. DEBUGMSG ((DBG_WARNING, "BACKUPDIR: %s is considered a backup directory.", filePath));
  343. MemDbSetValueEx (MEMDB_CATEGORY_BACKUPDIRS, filePath, NULL, NULL, 0 , NULL);
  344. g_BackupDirCount++;
  345. }
  346. }
  347. }
  348. if (CanSetOperation (FileName, OPERATION_FILE_DELETE)) {
  349. MarkFileForDelete (FileName);
  350. }
  351. return TRUE;
  352. }
  353. BOOL
  354. OsFiles (
  355. IN PMIGDB_CONTEXT Context
  356. )
  357. /*++
  358. Routine Description:
  359. This is the action taken when an OS file is found. Basically the file gets deleted to
  360. make room for NT version.
  361. Arguments:
  362. Context - See definition.
  363. Return value:
  364. TRUE - if operation was successful
  365. FALSE - otherwise
  366. --*/
  367. {
  368. PCSTR newFileName;
  369. DWORD fileStatus;
  370. MULTISZ_ENUM fileEnum;
  371. BOOL b = FALSE;
  372. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  373. do {
  374. if (!g_IsFusionDir || !IsNtCompatibleModule (fileEnum.CurrentString)) {
  375. //
  376. // If this file is marked with any MOVE operations, remove those operations.
  377. // we want this deletion to take precedence.
  378. //
  379. RemoveOperationsFromPath (
  380. fileEnum.CurrentString,
  381. ALL_MOVE_OPERATIONS
  382. );
  383. DeleteFileWithWarning (fileEnum.CurrentString);
  384. MarkFileAsOsFile (fileEnum.CurrentString);
  385. fileStatus = GetFileStatusOnNt (fileEnum.CurrentString);
  386. if ((fileStatus & FILESTATUS_REPLACED) != 0) {
  387. newFileName = GetPathStringOnNt (fileEnum.CurrentString);
  388. if (StringIMatch (newFileName, fileEnum.CurrentString)) {
  389. MarkFileForCreation (newFileName);
  390. } else {
  391. MarkFileForMoveByNt (fileEnum.CurrentString, newFileName);
  392. }
  393. FreePathString (newFileName);
  394. }
  395. b = TRUE;
  396. }
  397. }
  398. while (EnumNextMultiSz (&fileEnum));
  399. }
  400. return b;
  401. }
  402. BOOL
  403. UseNtFiles (
  404. IN PMIGDB_CONTEXT Context
  405. )
  406. /*++
  407. Routine Description:
  408. This is the action taken when NT uses another file for same functionality. We will
  409. mark the file as deleted, moved and we will add it in RenameFile category
  410. Arguments:
  411. Context - See definition.
  412. Return value:
  413. TRUE - if operation was successful
  414. FALSE - otherwise
  415. --*/
  416. {
  417. CHAR ntFilePath [MAX_MBCHAR_PATH];
  418. MULTISZ_ENUM fileEnum;
  419. TCHAR key [MEMDB_MAX];
  420. DWORD set;
  421. PCTSTR name;
  422. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  423. if (!GetNtFilePath (Context->Arguments, ntFilePath)) {
  424. LOG((LOG_ERROR, "Cannot UseNTFile for %s", Context->Arguments));
  425. return FALSE;
  426. }
  427. else {
  428. do {
  429. if (Context->VirtualFile) {
  430. MarkFileForExternalDelete (fileEnum.CurrentString);
  431. } else {
  432. DeleteFileWithWarning (fileEnum.CurrentString);
  433. }
  434. MarkFileForMoveByNt (fileEnum.CurrentString, ntFilePath);
  435. MarkFileAsOsFile (fileEnum.CurrentString);
  436. if (Context->VirtualFile) {
  437. continue;
  438. }
  439. //
  440. // add this info to memdb to update commands that use these files
  441. //
  442. name = GetFileNameFromPath (fileEnum.CurrentString);
  443. if (!g_UseNtFileHashTable) {
  444. continue;
  445. }
  446. if (!HtFindStringAndData (g_UseNtFileHashTable, name, &set)) {
  447. MYASSERT (FALSE);
  448. continue;
  449. }
  450. //
  451. // check if a file with this name, but not handled, was previously found
  452. //
  453. if (!set) {
  454. MemDbBuildKey (
  455. key,
  456. MEMDB_CATEGORY_USE_NT_FILES,
  457. name,
  458. GetFileNameFromPath (ntFilePath),
  459. NULL
  460. );
  461. if (!MemDbGetValue (key, NULL)) {
  462. MemDbSetValue (key, 0);
  463. }
  464. } else {
  465. DEBUGMSG ((
  466. DBG_VERBOSE,
  467. "Found [UseNtFiles] file %s, but there's already one that was not handled",
  468. name
  469. ));
  470. }
  471. }
  472. while (EnumNextMultiSz (&fileEnum));
  473. }
  474. }
  475. return TRUE;
  476. }
  477. BOOL
  478. Incompatible (
  479. IN PMIGDB_CONTEXT Context
  480. )
  481. /*++
  482. Routine Description:
  483. This is the action taken when a known incompatible file is found. The file gets marked
  484. for external deletion (it will not be deleted but in all subsequent operation will be
  485. considered as deleted) and if there is a link pointing to it an announcement is made
  486. in LinkProcessing phase.
  487. Arguments:
  488. Context - See definition.
  489. Return value:
  490. TRUE - if operation was successful
  491. FALSE - otherwise
  492. --*/
  493. {
  494. MULTISZ_ENUM fileEnum;
  495. PCTSTR extPtr;
  496. BOOL result = FALSE;
  497. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  498. do {
  499. if (!IsFileMarkedAsKnownGood (fileEnum.CurrentString)) {
  500. result = TRUE;
  501. extPtr = GetFileExtensionFromPath (fileEnum.CurrentString);
  502. if (extPtr) {
  503. if (StringIMatch (extPtr, TEXT("SCR"))) {
  504. DisableFile (fileEnum.CurrentString);
  505. }
  506. }
  507. MarkFileForExternalDelete (fileEnum.CurrentString);
  508. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  509. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_INC_NOBADAPPS);
  510. }
  511. }
  512. }
  513. while (EnumNextMultiSz (&fileEnum));
  514. }
  515. return result;
  516. }
  517. BOOL
  518. Incompatible_PreinstalledUtilities (
  519. IN PMIGDB_CONTEXT Context
  520. )
  521. /*++
  522. Routine Description:
  523. This is the action taken when a known incompatible preinstalled utility is found.
  524. The file gets marked for external deletion (it will not be deleted but in all
  525. subsequent operation will be considered as deleted) and if there is a link
  526. pointing to it an announcement is made in LinkProcessing phase.
  527. Arguments:
  528. Context - See definition.
  529. Return value:
  530. TRUE - if operation was successful
  531. FALSE - otherwise
  532. --*/
  533. {
  534. MULTISZ_ENUM fileEnum;
  535. BOOL result = FALSE;
  536. if (!Context->Arguments) {
  537. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  538. do {
  539. if (!IsFileMarkedAsKnownGood (fileEnum.CurrentString)) {
  540. result = TRUE;
  541. MarkFileForExternalDelete (fileEnum.CurrentString);
  542. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  543. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_INC_PREINSTUTIL);
  544. }
  545. }
  546. } while (EnumNextMultiSz (&fileEnum));
  547. }
  548. } else {
  549. return pNoLinkRequiredWorker (
  550. MSG_INCOMPATIBLE_ROOT,
  551. MSG_INCOMPATIBLE_PREINSTALLED_UTIL_SUBGROUP,
  552. ACT_INC_PREINSTUTIL,
  553. Context
  554. );
  555. }
  556. return result;
  557. }
  558. BOOL
  559. Incompatible_SimilarOsFunctionality (
  560. IN PMIGDB_CONTEXT Context
  561. )
  562. /*++
  563. Routine Description:
  564. This is the action taken when a known incompatible preinstalled utility is found.
  565. The file gets marked for external deletion (it will not be deleted but in all
  566. subsequent operation will be considered as deleted) and if there is a link
  567. pointing to it an announcement is made in LinkProcessing phase.
  568. Arguments:
  569. Context - See definition.
  570. Return value:
  571. TRUE - if operation was successful
  572. FALSE - otherwise
  573. --*/
  574. {
  575. MULTISZ_ENUM fileEnum;
  576. BOOL result = FALSE;
  577. if (!Context->Arguments) {
  578. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  579. do {
  580. if (!IsFileMarkedAsKnownGood (fileEnum.CurrentString)) {
  581. result = TRUE;
  582. MarkFileForExternalDelete (fileEnum.CurrentString);
  583. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  584. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_INC_SIMILAROSFUNC);
  585. }
  586. }
  587. } while (EnumNextMultiSz (&fileEnum));
  588. }
  589. } else {
  590. return pNoLinkRequiredWorker (
  591. MSG_INCOMPATIBLE_ROOT,
  592. MSG_INCOMPATIBLE_UTIL_SIMILAR_FEATURE_SUBGROUP,
  593. ACT_INC_SIMILAROSFUNC,
  594. Context
  595. );
  596. }
  597. return result;
  598. }
  599. BOOL
  600. Incompatible_HardwareUtility (
  601. IN PMIGDB_CONTEXT Context
  602. )
  603. /*++
  604. Routine Description:
  605. This is the action taken when a known incompatible hardware
  606. utility is found. The file gets marked for external deletion
  607. (it will not be deleted but in all subsequent operation will
  608. be considered as deleted) and if there is a link pointing to
  609. it an announcement is made in LinkProcessing phase.
  610. Arguments:
  611. Context - See definition.
  612. Return value:
  613. TRUE - if operation was successful
  614. FALSE - otherwise
  615. --*/
  616. {
  617. MULTISZ_ENUM fileEnum;
  618. BOOL result = FALSE;
  619. if (!Context->Arguments) {
  620. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  621. do {
  622. if (!IsFileMarkedAsKnownGood (fileEnum.CurrentString)) {
  623. result = TRUE;
  624. MarkFileForExternalDelete (fileEnum.CurrentString);
  625. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  626. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_INC_IHVUTIL);
  627. }
  628. }
  629. } while (EnumNextMultiSz (&fileEnum));
  630. }
  631. } else {
  632. return pNoLinkRequiredWorker (
  633. MSG_INCOMPATIBLE_ROOT,
  634. MSG_INCOMPATIBLE_HW_UTIL_SUBGROUP,
  635. ACT_INC_IHVUTIL,
  636. Context
  637. );
  638. }
  639. return result;
  640. }
  641. BOOL
  642. MinorProblems (
  643. IN PMIGDB_CONTEXT Context
  644. )
  645. /*++
  646. Routine Description:
  647. This is the action taken when we found an app with known minor problems. If there is a link
  648. pointing to one of these files an announcement is made in LinkProcessing phase.
  649. Arguments:
  650. Context - See definition.
  651. Return value:
  652. TRUE - if operation was successful
  653. FALSE - otherwise
  654. --*/
  655. {
  656. MULTISZ_ENUM fileEnum;
  657. BOOL result = FALSE;
  658. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  659. do {
  660. if (!IsFileMarkedAsKnownGood (fileEnum.CurrentString)) {
  661. result = TRUE;
  662. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  663. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_MINORPROBLEMS);
  664. }
  665. }
  666. }
  667. while (EnumNextMultiSz (&fileEnum));
  668. }
  669. return result;
  670. }
  671. BOOL
  672. Reinstall (
  673. IN PMIGDB_CONTEXT Context
  674. )
  675. /*++
  676. Routine Description:
  677. This is the action taken when we found an app that should be reinstalled on NT. If there
  678. is a link pointing to one of these files an announcement is made in LinkProcessing phase.
  679. Arguments:
  680. Context - See definition.
  681. Return value:
  682. TRUE - if operation was successful
  683. FALSE - otherwise
  684. --*/
  685. {
  686. MULTISZ_ENUM fileEnum;
  687. BOOL result = FALSE;
  688. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  689. do {
  690. if (!IsFileMarkedAsKnownGood (fileEnum.CurrentString)) {
  691. result = TRUE;
  692. MarkFileForExternalDelete (fileEnum.CurrentString);
  693. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  694. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_REINSTALL);
  695. }
  696. }
  697. }
  698. while (EnumNextMultiSz (&fileEnum));
  699. }
  700. return result;
  701. }
  702. BOOL
  703. DosApps (
  704. IN PMIGDB_CONTEXT Context
  705. )
  706. /*++
  707. Routine Description:
  708. This is the action taken when we found an DOS app that's either incompatible or has
  709. minor problems (it's incompatible if Context has Message field NULL). We are forced
  710. to add this now in the user report since DOS apps usually don't have PIF files
  711. associated.
  712. Arguments:
  713. Context - See definition.
  714. Return value:
  715. TRUE - at least one file was announced
  716. FALSE - no files were announced
  717. --*/
  718. {
  719. MULTISZ_ENUM fileEnum;
  720. BOOL AtLeastOneFile;
  721. AtLeastOneFile = FALSE;
  722. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  723. do {
  724. if (!IsFileMarkedAsKnownGood (fileEnum.CurrentString)) {
  725. if (Context->Message != NULL) {
  726. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  727. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_MINORPROBLEMS);
  728. }
  729. } else {
  730. MarkFileForExternalDelete (fileEnum.CurrentString);
  731. if (!IsFileMarkedForAnnounce (fileEnum.CurrentString)) {
  732. AnnounceFileInReport (fileEnum.CurrentString, (DWORD)Context, ACT_INCOMPATIBLE);
  733. }
  734. }
  735. HandleDeferredAnnounce (NULL, fileEnum.CurrentString, TRUE);
  736. AtLeastOneFile = TRUE;
  737. if (*g_Boot16 == BOOT16_AUTOMATIC) {
  738. *g_Boot16 = BOOT16_YES;
  739. }
  740. }
  741. } while (EnumNextMultiSz (&fileEnum));
  742. }
  743. return AtLeastOneFile;
  744. }
  745. BOOL
  746. pNoLinkRequiredWorker (
  747. IN UINT GroupId,
  748. IN UINT SubGroupId, OPTIONAL
  749. IN DWORD ActionType,
  750. IN PMIGDB_CONTEXT Context
  751. )
  752. {
  753. MULTISZ_ENUM e;
  754. PCTSTR Group;
  755. PCTSTR RootGroup;
  756. PCTSTR SubGroup;
  757. PCTSTR GroupTemp;
  758. BOOL result = FALSE;
  759. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  760. do {
  761. if (!IsFileMarkedAsKnownGood (e.CurrentString)) {
  762. result = TRUE;
  763. }
  764. } while (EnumNextMultiSz (&e));
  765. }
  766. if (!result) {
  767. return FALSE;
  768. }
  769. //
  770. // Add the message using section name as the context
  771. //
  772. if (!Context->SectNameForDisplay) {
  773. DEBUGMSG ((DBG_WHOOPS, "Rule for %s needs a localized app title", Context->SectName));
  774. return TRUE;
  775. }
  776. RootGroup = GetStringResource (GroupId);
  777. if (!RootGroup) {
  778. LOG((LOG_ERROR, "Cannot get resources while processing section %s", Context->SectNameForDisplay));
  779. return TRUE;
  780. }
  781. //
  782. // Now fetch the group string, and optional subgroup string,
  783. // and join them together
  784. //
  785. if (SubGroupId) {
  786. SubGroup = GetStringResource (SubGroupId);
  787. MYASSERT (SubGroup);
  788. GroupTemp = JoinPaths (RootGroup, SubGroup);
  789. Group = JoinPaths (GroupTemp, Context->SectNameForDisplay);
  790. FreePathString (GroupTemp);
  791. } else {
  792. Group = JoinPaths (RootGroup, Context->SectNameForDisplay);
  793. }
  794. FreeStringResource (RootGroup);
  795. //
  796. // Put the message in msgmgr
  797. //
  798. MsgMgr_ContextMsg_Add (
  799. Context->SectName,
  800. Group,
  801. Context->Message
  802. );
  803. FreePathString (Group);
  804. //
  805. // Associate all files with the context
  806. //
  807. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  808. do {
  809. if (ActionType != ACT_MINORPROBLEMS) {
  810. MarkFileForExternalDelete (e.CurrentString);
  811. }
  812. if (!IsFileMarkedForAnnounce (e.CurrentString)) {
  813. AnnounceFileInReport (e.CurrentString, (DWORD)Context, ActionType);
  814. }
  815. MsgMgr_LinkObjectWithContext (
  816. Context->SectName,
  817. e.CurrentString
  818. );
  819. } while (EnumNextMultiSz (&e));
  820. }
  821. return TRUE;
  822. }
  823. BOOL
  824. Reinstall_NoLinkRequired (
  825. IN PMIGDB_CONTEXT Context
  826. )
  827. /*++
  828. Routine Description:
  829. This is the action taken when we found an app that should be reinstalled on NT. The
  830. message is added to the report whenever the action is triggered; no link is required.
  831. Arguments:
  832. Context - See definition.
  833. Return value:
  834. TRUE - if operation was successful
  835. FALSE - otherwise
  836. --*/
  837. {
  838. return pNoLinkRequiredWorker (
  839. MSG_REINSTALL_ROOT,
  840. Context->Message ? MSG_REINSTALL_DETAIL_SUBGROUP : MSG_REINSTALL_LIST_SUBGROUP,
  841. ACT_REINSTALL,
  842. Context
  843. );
  844. }
  845. BOOL
  846. Reinstall_SoftBlock (
  847. IN PMIGDB_CONTEXT Context
  848. )
  849. /*++
  850. Routine Description:
  851. This is the action taken when we found an app that should be reinstalled on NT. The
  852. message is added to the report whenever the action is triggered; no link is required.
  853. In addition, the user must uninstall the app before proceeding.
  854. Arguments:
  855. Context - See definition.
  856. Return value:
  857. TRUE - if operation was successful
  858. FALSE - otherwise
  859. --*/
  860. {
  861. MULTISZ_ENUM e;
  862. //
  863. // Add all files to blocking hash table in msgmgr
  864. //
  865. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  866. do {
  867. if (!IsFileMarkedAsKnownGood (e.CurrentString)) {
  868. AddBlockingObject (e.CurrentString);
  869. }
  870. } while (EnumNextMultiSz (&e));
  871. }
  872. return pNoLinkRequiredWorker (MSG_BLOCKING_ITEMS_ROOT, MSG_REINSTALL_BLOCK_ROOT, ACT_REINSTALL_BLOCK, Context);
  873. }
  874. BOOL
  875. Incompatible_NoLinkRequired (
  876. IN PMIGDB_CONTEXT Context
  877. )
  878. /*++
  879. Routine Description:
  880. This is the action taken when we found an app that is incompatible with NT. The
  881. message is added to the report whenever the action is triggered; no link is required.
  882. Arguments:
  883. Context - See definition.
  884. Return value:
  885. TRUE - if operation was successful
  886. FALSE - otherwise
  887. --*/
  888. {
  889. return pNoLinkRequiredWorker (
  890. MSG_INCOMPATIBLE_ROOT,
  891. Context->Message ? MSG_INCOMPATIBLE_DETAIL_SUBGROUP : MSG_TOTALLY_INCOMPATIBLE_SUBGROUP,
  892. ACT_INC_NOBADAPPS,
  893. Context
  894. );
  895. }
  896. BOOL
  897. MinorProblems_NoLinkRequired (
  898. IN PMIGDB_CONTEXT Context
  899. )
  900. /*++
  901. Routine Description:
  902. This is the action taken when we found an app that is incompatible with NT. The
  903. message is added to the report whenever the action is triggered; no link is required.
  904. Arguments:
  905. Context - See definition.
  906. Return value:
  907. TRUE - if operation was successful
  908. FALSE - otherwise
  909. --*/
  910. {
  911. return pNoLinkRequiredWorker (
  912. MSG_MINOR_PROBLEM_ROOT,
  913. 0,
  914. ACT_MINORPROBLEMS,
  915. Context
  916. );
  917. }
  918. BOOL
  919. CompatibleShellModules (
  920. IN PMIGDB_CONTEXT Context
  921. )
  922. /*++
  923. Routine Description:
  924. This is the action taken when we found an "known good" shell module. Those modules are
  925. therefore approved to be listed in SHELL= line in SYSTEM.INI.
  926. Arguments:
  927. Context - See definition.
  928. Return value:
  929. FALSE - this will allow other actions to handle this file
  930. --*/
  931. {
  932. MULTISZ_ENUM fileEnum;
  933. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  934. do {
  935. AddCompatibleShell (fileEnum.CurrentString, (DWORD)Context);
  936. }
  937. while (EnumNextMultiSz (&fileEnum));
  938. }
  939. return FALSE;
  940. }
  941. BOOL
  942. CompatibleRunKeyModules (
  943. IN PMIGDB_CONTEXT Context
  944. )
  945. /*++
  946. Routine Description:
  947. This is the action taken when we found an "known good" RunKey module. Those modules are
  948. therefore approved to be listed in Run key.
  949. Arguments:
  950. Context - Specifies a pointer to the migdb context
  951. Return value:
  952. FALSE - this will allow other actions to handle this file
  953. --*/
  954. {
  955. MULTISZ_ENUM fileEnum;
  956. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  957. do {
  958. AddCompatibleRunKey (fileEnum.CurrentString, (DWORD)Context);
  959. } while (EnumNextMultiSz (&fileEnum));
  960. }
  961. return FALSE;
  962. }
  963. BOOL
  964. CompatibleDosModules (
  965. IN PMIGDB_CONTEXT Context
  966. )
  967. /*++
  968. Routine Description:
  969. This is the action taken when we found an "known good" Dos module. Those modules are
  970. therefore approved to be loaded in autoexec and config files.
  971. Arguments:
  972. Context - Specifies a pointer to the migdb context
  973. Return value:
  974. FALSE - this will allow other actions to handle this file
  975. --*/
  976. {
  977. MULTISZ_ENUM fileEnum;
  978. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  979. do {
  980. AddCompatibleDos (fileEnum.CurrentString, (DWORD)Context);
  981. } while (EnumNextMultiSz (&fileEnum));
  982. }
  983. return FALSE;
  984. }
  985. VOID
  986. pCommonSectionProcess (
  987. IN PMIGDB_CONTEXT Context,
  988. IN BOOL MsgLink
  989. )
  990. {
  991. MULTISZ_ENUM e;
  992. TCHAR Path[MAX_TCHAR_PATH];
  993. PTSTR p;
  994. //
  995. // Defer processing: add the section to memdb so the section is processed only once.
  996. //
  997. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  998. do {
  999. //
  1000. // Construct just the path
  1001. //
  1002. StringCopy (Path, e.CurrentString);
  1003. p = (PTSTR) GetFileNameFromPath (Path);
  1004. // Path is a full path so GetFileNameFromPath will always return something
  1005. if (p) {
  1006. p = _tcsdec2 (Path, p);
  1007. // p will always be not NULL and will point to the last wack
  1008. if (p && (*p == '\\')) {
  1009. *p = 0;
  1010. MemDbSetValueExA (
  1011. MEMDB_CATEGORY_MIGRATION_SECTION,
  1012. Context->Arguments,
  1013. Path,
  1014. NULL,
  1015. 0,
  1016. NULL
  1017. );
  1018. if (MsgLink) {
  1019. MsgMgr_LinkObjectWithContext (
  1020. Context->Arguments,
  1021. e.CurrentString
  1022. );
  1023. }
  1024. }
  1025. }
  1026. } while (EnumNextMultiSz (&e));
  1027. }
  1028. }
  1029. BOOL
  1030. Incompatible_AutoUninstall (
  1031. IN PMIGDB_CONTEXT Context
  1032. )
  1033. /*++
  1034. Routine Description:
  1035. Incompatible_AutoUninstall implements the action taken when we find a particular app and
  1036. need to process an uninstall section. The uninstall section removes files or
  1037. registry entries. This application will also be announced in the report as incompatible.
  1038. Arguments:
  1039. Context - See definition.
  1040. Return value:
  1041. TRUE - if operation was successful
  1042. FALSE - otherwise
  1043. --*/
  1044. {
  1045. PCTSTR Group;
  1046. if (!Context->Arguments) {
  1047. DEBUGMSG ((DBG_WHOOPS, "Incompatible_AutoUninstall: ARG is required"));
  1048. return TRUE;
  1049. }
  1050. Group = BuildMessageGroup (
  1051. MSG_INCOMPATIBLE_ROOT,
  1052. MSG_AUTO_UNINSTALL_SUBGROUP,
  1053. Context->SectNameForDisplay
  1054. );
  1055. if (Group) {
  1056. MsgMgr_ContextMsg_Add (
  1057. Context->Arguments,
  1058. Group,
  1059. S_EMPTY
  1060. );
  1061. FreeText (Group);
  1062. }
  1063. pCommonSectionProcess (Context, TRUE);
  1064. return TRUE;
  1065. }
  1066. BOOL
  1067. Reinstall_AutoUninstall (
  1068. IN PMIGDB_CONTEXT Context
  1069. )
  1070. /*++
  1071. Routine Description:
  1072. Reinstall_AutoUninstall implements the action taken when we find a particular app and
  1073. need to process an uninstall section. The uninstall section removes files or
  1074. registry entries. This application will also be announced in the report as reinstall.
  1075. Arguments:
  1076. Context - See definition.
  1077. Return value:
  1078. TRUE - if operation was successful
  1079. FALSE - otherwise
  1080. --*/
  1081. {
  1082. PCTSTR Group;
  1083. if (!Context->Arguments) {
  1084. DEBUGMSG ((DBG_WHOOPS, "Reinstall_AutoUninstall: ARG is required"));
  1085. return TRUE;
  1086. }
  1087. Group = BuildMessageGroup (
  1088. MSG_REINSTALL_ROOT,
  1089. Context->Message ? MSG_REINSTALL_DETAIL_SUBGROUP : MSG_REINSTALL_LIST_SUBGROUP,
  1090. Context->SectNameForDisplay
  1091. );
  1092. if (Group) {
  1093. MsgMgr_ContextMsg_Add (
  1094. Context->Arguments,
  1095. Group,
  1096. S_EMPTY
  1097. );
  1098. FreeText (Group);
  1099. }
  1100. pCommonSectionProcess (Context, TRUE);
  1101. return TRUE;
  1102. }
  1103. BOOL
  1104. SilentUninstall (
  1105. IN PMIGDB_CONTEXT Context
  1106. )
  1107. /*++
  1108. Routine Description:
  1109. SilentUninstall implements the action taken when we find a particular app and
  1110. need to process an uninstall section. The uninstall section removes files or
  1111. registry entries. No message goes in the report.
  1112. Arguments:
  1113. Context - See definition.
  1114. Return value:
  1115. Always FALSE - other actions should be processed
  1116. --*/
  1117. {
  1118. if (!Context->Arguments) {
  1119. DEBUGMSG ((DBG_WHOOPS, "SilentUninstall: ARG is required"));
  1120. return FALSE;
  1121. }
  1122. //
  1123. // Defer processing: add the section to memdb so the section is processed only once.
  1124. //
  1125. pCommonSectionProcess (Context, FALSE);
  1126. return FALSE;
  1127. }
  1128. BOOL
  1129. FileEdit (
  1130. IN PMIGDB_CONTEXT Context
  1131. )
  1132. /*++
  1133. Routine Description:
  1134. FileEdit triggers a file edit operation, allowing search/replace and path
  1135. updates.
  1136. Arguments:
  1137. Context - Specifies the context of the migdb.inf entry that triggered this
  1138. action
  1139. Return Value:
  1140. Always FALSE - allow other actions to be called
  1141. --*/
  1142. {
  1143. MULTISZ_ENUM e;
  1144. INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
  1145. PCSTR Data;
  1146. GROWBUFFER SearchList = GROWBUF_INIT;
  1147. GROWBUFFER ReplaceList = GROWBUF_INIT;
  1148. GROWBUFFER TokenArgBuf = GROWBUF_INIT;
  1149. GROWBUFFER TokenSetBuf = GROWBUF_INIT;
  1150. GROWBUFFER DataBuf = GROWBUF_INIT;
  1151. UINT u;
  1152. PTOKENARG TokenArg;
  1153. PBYTE Dest;
  1154. PTOKENSET TokenSet;
  1155. PCSTR editSection = NULL;
  1156. PCSTR CharsToIgnore = NULL;
  1157. BOOL urlMode = FALSE;
  1158. BOOL result = FALSE;
  1159. __try {
  1160. //
  1161. // If no args, then this file is just supposed to get its paths updated
  1162. //
  1163. if (!Context->Arguments) {
  1164. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1165. do {
  1166. MemDbSetValueEx (
  1167. MEMDB_CATEGORY_FILEEDIT,
  1168. e.CurrentString,
  1169. NULL,
  1170. NULL,
  1171. 0,
  1172. NULL
  1173. );
  1174. } while (EnumNextMultiSz (&e));
  1175. }
  1176. return FALSE;
  1177. }
  1178. //
  1179. // Scan the args for EnableUrlMode
  1180. //
  1181. if (EnumFirstMultiSz (&e, Context->Arguments)) {
  1182. do {
  1183. if (StringIMatch (e.CurrentString, TEXT("EnableUrlMode"))) {
  1184. urlMode = TRUE;
  1185. } else if (!editSection) {
  1186. editSection = e.CurrentString;
  1187. DEBUGMSG ((DBG_NAUSEA, "FileEdit: EditSection is %s", editSection));
  1188. } else if (!CharsToIgnore) {
  1189. CharsToIgnore = e.CurrentString;
  1190. DEBUGMSG ((DBG_NAUSEA, "FileEdit: CharsToIgnore is %s", CharsToIgnore));
  1191. } else {
  1192. DEBUGMSG ((DBG_WHOOPS, "Ignoring extra file edit arg %s", e.CurrentString));
  1193. }
  1194. } while (EnumNextMultiSz (&e));
  1195. }
  1196. //
  1197. // Parse the edit section
  1198. //
  1199. if (editSection) {
  1200. if (InfFindFirstLine (g_MigDbInf, editSection, NULL, &is)) {
  1201. do {
  1202. ReplaceList.End = 0;
  1203. SearchList.End = 0;
  1204. //
  1205. // Get the search/replace strings
  1206. //
  1207. for (u = 3 ; ; u += 2) {
  1208. Data = InfGetStringField (&is, u + 1);
  1209. if (!Data) {
  1210. break;
  1211. }
  1212. MYASSERT (*Data);
  1213. if (*Data == 0) {
  1214. continue;
  1215. }
  1216. MultiSzAppend (&ReplaceList, Data);
  1217. Data = InfGetStringField (&is, u);
  1218. MYASSERT (Data && *Data);
  1219. if (!Data || *Data == 0) {
  1220. __leave;
  1221. }
  1222. MultiSzAppend (&SearchList, Data);
  1223. }
  1224. //
  1225. // Get the detection string
  1226. //
  1227. Data = InfGetStringField (&is, 1);
  1228. if (Data && *Data) {
  1229. //
  1230. // Save the token arg into an array
  1231. //
  1232. TokenArg = (PTOKENARG) GrowBuffer (&TokenArgBuf, sizeof (TOKENARG));
  1233. TokenArg->DetectPattern = (PCSTR) (DataBuf.End + TOKEN_BASE_OFFSET);
  1234. GrowBufCopyString (&DataBuf, Data);
  1235. if (SearchList.End) {
  1236. MultiSzAppend (&SearchList, TEXT(""));
  1237. TokenArg->SearchList = (PCSTR) (DataBuf.End + TOKEN_BASE_OFFSET);
  1238. Dest = GrowBuffer (&DataBuf, SearchList.End);
  1239. CopyMemory (Dest, SearchList.Buf, SearchList.End);
  1240. MultiSzAppend (&ReplaceList, TEXT(""));
  1241. TokenArg->ReplaceWith = (PCSTR) (DataBuf.End + TOKEN_BASE_OFFSET);
  1242. Dest = GrowBuffer (&DataBuf, ReplaceList.End);
  1243. CopyMemory (Dest, ReplaceList.Buf, ReplaceList.End);
  1244. } else {
  1245. TokenArg->SearchList = 0;
  1246. TokenArg->ReplaceWith = 0;
  1247. }
  1248. Data = InfGetStringField (&is, 2);
  1249. if (_tcstoul (Data, NULL, 10)) {
  1250. TokenArg->UpdatePath = TRUE;
  1251. } else {
  1252. TokenArg->UpdatePath = FALSE;
  1253. }
  1254. }
  1255. } while (InfFindNextLine (&is));
  1256. }
  1257. ELSE_DEBUGMSG ((DBG_WHOOPS, "FileEdit action's section %s does not exist", editSection));
  1258. } else if (urlMode) {
  1259. //
  1260. // Create an argument that has a detect pattern of *
  1261. //
  1262. TokenArg = (PTOKENARG) GrowBuffer (&TokenArgBuf, sizeof (TOKENARG));
  1263. TokenArg->DetectPattern = (PCSTR) (DataBuf.End + TOKEN_BASE_OFFSET);
  1264. GrowBufCopyString (&DataBuf, TEXT("*"));
  1265. TokenArg->SearchList = NULL;
  1266. TokenArg->ReplaceWith = NULL;
  1267. TokenArg->UpdatePath = TRUE;
  1268. }
  1269. //
  1270. // Build a token set out of the token args
  1271. //
  1272. if (TokenArgBuf.End) {
  1273. TokenSet = (PTOKENSET) GrowBuffer (
  1274. &TokenSetBuf,
  1275. sizeof (TOKENSET) +
  1276. TokenArgBuf.End +
  1277. DataBuf.End
  1278. );
  1279. TokenSet->ArgCount = TokenArgBuf.End / sizeof (TOKENARG);
  1280. TokenSet->SelfRelative = TRUE;
  1281. TokenSet->UrlMode = urlMode;
  1282. if (CharsToIgnore) {
  1283. TokenSet->CharsToIgnore = (PCSTR) (DataBuf.End + TOKEN_BASE_OFFSET);
  1284. GrowBufCopyString (&TokenSetBuf, CharsToIgnore);
  1285. } else {
  1286. TokenSet->CharsToIgnore = NULL;
  1287. }
  1288. CopyMemory (TokenSet->Args, TokenArgBuf.Buf, TokenArgBuf.End);
  1289. CopyMemory (
  1290. (PBYTE) (TokenSet->Args) + TokenArgBuf.End,
  1291. DataBuf.Buf,
  1292. DataBuf.End
  1293. );
  1294. //
  1295. // Save TokenSet to memdb
  1296. //
  1297. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1298. do {
  1299. MemDbSetBinaryValueEx (
  1300. MEMDB_CATEGORY_FILEEDIT,
  1301. e.CurrentString,
  1302. NULL,
  1303. TokenSetBuf.Buf,
  1304. TokenSetBuf.End,
  1305. NULL
  1306. );
  1307. if (g_ConfigOptions.EnableBackup) {
  1308. MarkFileForBackup (e.CurrentString);
  1309. }
  1310. } while (EnumNextMultiSz (&e));
  1311. }
  1312. }
  1313. result = TRUE;
  1314. }
  1315. __finally {
  1316. InfCleanUpInfStruct (&is);
  1317. FreeGrowBuffer (&SearchList);
  1318. FreeGrowBuffer (&ReplaceList);
  1319. FreeGrowBuffer (&TokenArgBuf);
  1320. FreeGrowBuffer (&TokenSetBuf);
  1321. FreeGrowBuffer (&DataBuf);
  1322. }
  1323. return result;
  1324. }
  1325. BOOL
  1326. MigrationProcessing (
  1327. IN PMIGDB_CONTEXT Context
  1328. )
  1329. /*++
  1330. Routine Description:
  1331. MigrationProcessing implements the action taken when we find a particular app and
  1332. we need some migration DLL like processing to go on.
  1333. Arguments:
  1334. Context - See definition.
  1335. Return value:
  1336. TRUE - if operation was successful
  1337. FALSE - otherwise
  1338. --*/
  1339. {
  1340. if (!Context->Arguments) {
  1341. DEBUGMSG ((DBG_WHOOPS, "MigrationProcessing: ARG is required"));
  1342. return TRUE;
  1343. }
  1344. //
  1345. // Defer processing: add the section to memdb so the section is processed only once.
  1346. //
  1347. pCommonSectionProcess (Context, FALSE);
  1348. return TRUE;
  1349. }
  1350. BOOL
  1351. NonPnpDrivers (
  1352. IN PMIGDB_CONTEXT Context
  1353. )
  1354. /*++
  1355. Routine Description:
  1356. NonPnpDrivers adds hardware incompatibility messages for incompatible
  1357. devices found by identifying drivers.
  1358. Arguments:
  1359. Context - Specifies the context of the file found.
  1360. Return Value:
  1361. Always TRUE.
  1362. --*/
  1363. {
  1364. PCTSTR Group;
  1365. PCTSTR GroupRoot;
  1366. MULTISZ_ENUM e;
  1367. TCHAR MsgMgrContext[256];
  1368. PCTSTR OtherDevices;
  1369. MYASSERT (Context->SectNameForDisplay);
  1370. //
  1371. // Add MemDb entry, so .386 check code knows about this file
  1372. //
  1373. NonPnpDrivers_NoMessage (Context);
  1374. //
  1375. // Add incompatibility message to Hardware. This involves decorating
  1376. // the device driver name and using a context prefix of NonPnpDrv.
  1377. //
  1378. OtherDevices = GetStringResource (MSG_UNKNOWN_DEVICE_CLASS);
  1379. if (!OtherDevices) {
  1380. MYASSERT (FALSE);
  1381. return FALSE;
  1382. }
  1383. GroupRoot = BuildMessageGroup (
  1384. MSG_INCOMPATIBLE_HARDWARE_ROOT,
  1385. MSG_INCOMPATIBLE_HARDWARE_PNP_SUBGROUP,
  1386. OtherDevices
  1387. );
  1388. FreeStringResource (OtherDevices);
  1389. if (!GroupRoot) {
  1390. MYASSERT (FALSE);
  1391. return FALSE;
  1392. }
  1393. Group = JoinPaths (GroupRoot, Context->SectNameForDisplay);
  1394. FreeText (GroupRoot);
  1395. MYASSERT (TcharCount (Context->SectName) < 240);
  1396. StringCopy (MsgMgrContext, TEXT("*NonPnpDrv"));
  1397. StringCat (MsgMgrContext, Context->SectName);
  1398. MsgMgr_ContextMsg_Add (MsgMgrContext, Group, NULL);
  1399. FreePathString (Group);
  1400. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1401. do {
  1402. MsgMgr_LinkObjectWithContext (
  1403. MsgMgrContext,
  1404. e.CurrentString
  1405. );
  1406. } while (EnumNextMultiSz (&e));
  1407. }
  1408. return TRUE;
  1409. }
  1410. BOOL
  1411. NonPnpDrivers_NoMessage (
  1412. IN PMIGDB_CONTEXT Context
  1413. )
  1414. /*++
  1415. Routine Description:
  1416. NonPnpDrivers_NoMessage marks a device driver file as known, so the .386
  1417. warning does not appear because of it.
  1418. Arguments:
  1419. Context - Specifies the context of the file found.
  1420. Return Value:
  1421. Always TRUE.
  1422. --*/
  1423. {
  1424. MULTISZ_ENUM e;
  1425. MYASSERT (Context->SectNameForDisplay);
  1426. //
  1427. // Add MemDb entry, so .386 check code knows about this file
  1428. //
  1429. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1430. do {
  1431. DeleteFileWithWarning (e.CurrentString);
  1432. } while (EnumNextMultiSz (&e));
  1433. }
  1434. return TRUE;
  1435. }
  1436. extern HWND g_Winnt32Wnd;
  1437. BOOL
  1438. RequiredOSFiles (
  1439. IN PMIGDB_CONTEXT Context
  1440. )
  1441. /*++
  1442. Routine Description:
  1443. This is for now the only action function that's called when the associated action was
  1444. not triggered during the file scan.
  1445. We need to know that we are upgrading a known OS so this function is called when none
  1446. of the required files were found. The action taken is warning the user and terminating
  1447. the upgrade.
  1448. Arguments:
  1449. Context - See definition.
  1450. Return value:
  1451. TRUE - Always
  1452. --*/
  1453. {
  1454. PCTSTR group = NULL;
  1455. PCTSTR message = NULL;
  1456. if ((!g_ConfigOptions.AnyVersion) && (!CANCELLED ())) {
  1457. //
  1458. // Add a message to the Incompatibility Report.
  1459. //
  1460. g_UnknownOs = TRUE;
  1461. group = BuildMessageGroup (MSG_BLOCKING_ITEMS_ROOT, MSG_UNKNOWN_OS_WARNING_SUBGROUP, NULL);
  1462. message = GetStringResource (MSG_UNKNOWN_OS);
  1463. if (message && group) {
  1464. MsgMgr_ObjectMsg_Add (TEXT("*UnknownOs"), group, message);
  1465. }
  1466. FreeText (group);
  1467. FreeStringResource (message);
  1468. }
  1469. return TRUE;
  1470. }
  1471. BOOL
  1472. CompatibleHlpFiles (
  1473. IN PMIGDB_CONTEXT Context
  1474. )
  1475. /*++
  1476. Routine Description:
  1477. This is the action taken when we found an "known good" HLP file. We do this to prevent
  1478. help file checking routines from announcing it as incompatible.
  1479. Arguments:
  1480. Context - See definition.
  1481. Return value:
  1482. FALSE - this will allow other actions to handle this file
  1483. --*/
  1484. {
  1485. MULTISZ_ENUM fileEnum;
  1486. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  1487. do {
  1488. AddCompatibleHlp (fileEnum.CurrentString, (DWORD)Context);
  1489. } while (EnumNextMultiSz (&fileEnum));
  1490. }
  1491. return FALSE;
  1492. }
  1493. BOOL
  1494. BlockingVirusScanner (
  1495. IN PMIGDB_CONTEXT Context
  1496. )
  1497. {
  1498. MULTISZ_ENUM fileEnum;
  1499. PCTSTR message = NULL;
  1500. PCTSTR button1 = NULL;
  1501. PCTSTR button2 = NULL;
  1502. PCSTR args[1];
  1503. BOOL bStop;
  1504. if (EnumFirstMultiSz (&fileEnum, Context->FileList.Buf)) {
  1505. do {
  1506. //
  1507. // Inform the user of the problem.
  1508. //
  1509. args[0] = Context -> Message;
  1510. bStop = (Context->Arguments && StringIMatch (Context->Arguments, TEXT("1")));
  1511. if (bStop) {
  1512. ResourceMessageBox (
  1513. g_ParentWnd,
  1514. MSG_BAD_VIRUS_SCANNER_STOP,
  1515. MB_OK|MB_ICONSTOP|MB_SETFOREGROUND,
  1516. args
  1517. );
  1518. g_BadVirusScannerFound = TRUE;
  1519. } else {
  1520. message = ParseMessageID (MSG_BAD_VIRUS_SCANNER, args);
  1521. button1 = GetStringResource (MSG_KILL_APPLICATION);
  1522. button2 = GetStringResource (MSG_QUIT_SETUP);
  1523. if (!UNATTENDED() && IDBUTTON1 != TwoButtonBox (g_ParentWnd, message, button1, button2)) {
  1524. g_BadVirusScannerFound = TRUE;
  1525. }
  1526. else {
  1527. //
  1528. // Add the string to the list of files to be
  1529. // to be terminated.
  1530. //
  1531. GrowListAppendString (
  1532. &g_BadVirusScannerGrowList,
  1533. Context->FileList.Buf
  1534. );
  1535. }
  1536. FreeStringResource (message);
  1537. FreeStringResource (button1);
  1538. FreeStringResource (button2);
  1539. }
  1540. }
  1541. while (EnumNextMultiSz (&fileEnum));
  1542. }
  1543. return TRUE;
  1544. }
  1545. BOOL
  1546. BlockingFile (
  1547. IN PMIGDB_CONTEXT Context
  1548. )
  1549. {
  1550. MULTISZ_ENUM e;
  1551. PCTSTR group;
  1552. BOOL result = FALSE;
  1553. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1554. do {
  1555. if (!IsFileMarkedAsKnownGood (e.CurrentString)) {
  1556. result = TRUE;
  1557. DEBUGMSG ((DBG_WARNING, "BLOCKINGFILE: Found file %s which blocks upgrade.", e.CurrentString));
  1558. group = BuildMessageGroup (MSG_BLOCKING_ITEMS_ROOT, MSG_MUST_UNINSTALL_ROOT, Context->SectNameForDisplay);
  1559. MsgMgr_ObjectMsg_Add (e.CurrentString, group, Context->Message);
  1560. FreeText (group);
  1561. g_BlockingFileFound = TRUE;
  1562. }
  1563. } while (EnumNextMultiSz (&e));
  1564. }
  1565. return result;
  1566. }
  1567. BOOL
  1568. BlockingHardware (
  1569. IN PMIGDB_CONTEXT Context
  1570. )
  1571. {
  1572. MULTISZ_ENUM e;
  1573. PCTSTR group;
  1574. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1575. do {
  1576. DEBUGMSG ((DBG_WARNING, "BLOCKINGHARDWARE: Found file %s which blocks upgrade.", e.CurrentString));
  1577. group = BuildMessageGroup (MSG_BLOCKING_ITEMS_ROOT, MSG_BLOCKING_HARDWARE_SUBGROUP, Context->SectNameForDisplay);
  1578. MsgMgr_ObjectMsg_Add (e.CurrentString, group, Context->Message);
  1579. FreeText (group);
  1580. g_BlockingHardwareFound = TRUE;
  1581. } while (EnumNextMultiSz (&e));
  1582. }
  1583. return TRUE;
  1584. }
  1585. BOOL
  1586. CompatibleFiles (
  1587. IN PMIGDB_CONTEXT Context
  1588. )
  1589. /*++
  1590. Routine Description:
  1591. This is the action taken when an application file is considered good. Later, if we find
  1592. a link that points to a file that's not in this section, we will announce the link as
  1593. "unknown".
  1594. Arguments:
  1595. Context - See definition.
  1596. Return value:
  1597. FALSE - this will allow other actions to handle this file
  1598. --*/
  1599. {
  1600. MULTISZ_ENUM e;
  1601. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1602. do {
  1603. MarkFileAsKnownGood (e.CurrentString);
  1604. } while (EnumNextMultiSz (&e));
  1605. }
  1606. return FALSE;
  1607. }
  1608. BOOL
  1609. StoreMapi32Locations (
  1610. IN PMIGDB_CONTEXT Context
  1611. )
  1612. /*++
  1613. Routine Description:
  1614. This is the action taken for files related to MAPI32. We keep the locations
  1615. in a list, so we can later detect whether MAPI is handled or not. If it
  1616. is not handled, then we must tell the user they will lose e-mail functionality.
  1617. Arguments:
  1618. Context - See definition.
  1619. Return value:
  1620. FALSE - this will allow other actions to handle this file
  1621. --*/
  1622. {
  1623. MULTISZ_ENUM e;
  1624. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1625. do {
  1626. MemDbSetValueEx (
  1627. MEMDB_CATEGORY_MAPI32_LOCATIONS,
  1628. e.CurrentString,
  1629. NULL,
  1630. NULL,
  1631. 0,
  1632. NULL
  1633. );
  1634. } while (EnumNextMultiSz (&e));
  1635. }
  1636. return FALSE;
  1637. }
  1638. BOOL
  1639. ProductCollisions (
  1640. IN PMIGDB_CONTEXT Context
  1641. )
  1642. /*++
  1643. Routine Description:
  1644. ProductCollisions identifies files that are known good but match an NT
  1645. system file name. In this case, we want one file to be treated as an OS
  1646. file (the one in %windir% for example), while another file needs to be
  1647. treated as an application file (the one in the app dir for example). Here
  1648. we get called only when the application files are found.
  1649. Arguments:
  1650. Context - Specifies the current file context
  1651. Return Value:
  1652. Always TRUE.
  1653. --*/
  1654. {
  1655. return TRUE;
  1656. }
  1657. BOOL
  1658. MarkForBackup (
  1659. IN PMIGDB_CONTEXT Context
  1660. )
  1661. /*++
  1662. Routine Description:
  1663. MarkForBackup puts a backup operation on the file, unless backup is turned
  1664. off.
  1665. Arguments:
  1666. Context - Specifies the current file context
  1667. Return Value:
  1668. Always TRUE.
  1669. --*/
  1670. {
  1671. MULTISZ_ENUM e;
  1672. if (g_ConfigOptions.EnableBackup) {
  1673. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1674. do {
  1675. MarkFileForBackup (e.CurrentString);
  1676. } while (EnumNextMultiSz (&e));
  1677. }
  1678. }
  1679. return TRUE;
  1680. }
  1681. BOOL
  1682. ShowInSimplifiedView (
  1683. IN PMIGDB_CONTEXT Context
  1684. )
  1685. /*++
  1686. Routine Description:
  1687. ShowInSimplifiedView records the file with message manager, so that
  1688. the list view will show the incompatibility.
  1689. Arguments:
  1690. Context - Specifies the current file context
  1691. Return Value:
  1692. Always FALSE, so other actions can process the file.
  1693. --*/
  1694. {
  1695. MULTISZ_ENUM e;
  1696. if (g_ConfigOptions.ShowReport == TRISTATE_PARTIAL) {
  1697. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1698. do {
  1699. ElevateObject (e.CurrentString);
  1700. DEBUGMSG ((DBG_VERBOSE, "Elevated %s to the short view", e.CurrentString));
  1701. } while (EnumNextMultiSz (&e));
  1702. }
  1703. }
  1704. return FALSE;
  1705. }
  1706. BOOL
  1707. IniFileMappings (
  1708. IN PMIGDB_CONTEXT Context
  1709. )
  1710. /*++
  1711. Routine Description:
  1712. IniFileMappings records the locations of all INI files, so they
  1713. can be processed during GUI mode.
  1714. Arguments:
  1715. Context - See definition.
  1716. Return value:
  1717. FALSE - this will allow other actions to handle this file
  1718. --*/
  1719. {
  1720. MULTISZ_ENUM e;
  1721. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1722. do {
  1723. MemDbSetValueEx (
  1724. MEMDB_CATEGORY_INIFILES_CONVERT,
  1725. e.CurrentString,
  1726. NULL,
  1727. NULL,
  1728. 0,
  1729. NULL
  1730. );
  1731. } while (EnumNextMultiSz (&e));
  1732. }
  1733. return FALSE;
  1734. }
  1735. BOOL
  1736. IgnoreInReport (
  1737. IN PMIGDB_CONTEXT Context
  1738. )
  1739. /*++
  1740. Routine Description:
  1741. IgnoreInReport handles a file so that it will not appear in the
  1742. upgrade report. Currently this action is only for Office FindFast
  1743. version 9 and higher. Random results are likely with any other
  1744. type of file.
  1745. Arguments:
  1746. Context - See definition.
  1747. Return value:
  1748. FALSE - this will allow other actions to handle this file
  1749. --*/
  1750. {
  1751. MULTISZ_ENUM e;
  1752. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1753. do {
  1754. HandleObject (e.CurrentString, TEXT("Report"));
  1755. } while (EnumNextMultiSz (&e));
  1756. }
  1757. return FALSE;
  1758. }
  1759. BOOL
  1760. BadFusion (
  1761. IN PMIGDB_CONTEXT Context
  1762. )
  1763. /*++
  1764. Routine Description:
  1765. BadFusion removes known bad library files found in fusion dirs.
  1766. Arguments:
  1767. Context - See definition.
  1768. Return value:
  1769. FALSE - this will allow other actions to handle this file
  1770. --*/
  1771. {
  1772. MULTISZ_ENUM e;
  1773. TCHAR exeName[MAX_TCHAR_PATH * 2];
  1774. TCHAR localFile[MAX_TCHAR_PATH * 2];
  1775. PTSTR filePtr;
  1776. HANDLE h;
  1777. WIN32_FIND_DATA fd;
  1778. DWORD type;
  1779. BOOL b = FALSE;
  1780. if (g_IsFusionDir) {
  1781. if (EnumFirstMultiSz (&e, Context->FileList.Buf)) {
  1782. do {
  1783. if (IsFileMarkedForDelete (e.CurrentString)) {
  1784. continue;
  1785. }
  1786. if (SizeOfString (e.CurrentString) > sizeof (exeName)) {
  1787. MYASSERT (FALSE);
  1788. continue;
  1789. }
  1790. StringCopy (exeName, e.CurrentString);
  1791. filePtr = (PTSTR)GetFileNameFromPath (exeName);
  1792. if (!filePtr) {
  1793. MYASSERT (FALSE);
  1794. continue;
  1795. }
  1796. //
  1797. // make full path to app
  1798. //
  1799. StringCopy (filePtr, Context->Arguments);
  1800. //
  1801. // and to .local
  1802. //
  1803. StringCopyAB (localFile, exeName, filePtr);
  1804. filePtr = GetEndOfString (localFile);
  1805. //
  1806. // check if this is really a fusion case;
  1807. // both a file "exeName" and a file or dir (bug?) "exeName.local"
  1808. // must be present
  1809. //
  1810. h = FindFirstFile (exeName, &fd);
  1811. if (h == INVALID_HANDLE_VALUE) {
  1812. continue;
  1813. }
  1814. do {
  1815. if (fd.cFileName[0] == TEXT('.') &&
  1816. (fd.cFileName[1] == 0 || fd.cFileName[1] == TEXT('.') && fd.cFileName[2] == 0)
  1817. ) {
  1818. continue;
  1819. }
  1820. *filePtr = 0;
  1821. StringCopy (StringCat (filePtr, fd.cFileName), TEXT(".local"));
  1822. if (DoesFileExist (localFile)) {
  1823. type = GetExeType (localFile);
  1824. if ((type == EXE_WIN32_APP) || (type == EXE_WIN16_APP)) {
  1825. b = TRUE;
  1826. break;
  1827. }
  1828. }
  1829. } while (FindNextFile (h, &fd));
  1830. FindClose (h);
  1831. if (b) {
  1832. RemoveOperationsFromPath (e.CurrentString, ALL_OPERATIONS);
  1833. MarkFileForDelete (e.CurrentString);
  1834. LOG ((
  1835. LOG_WARNING,
  1836. "BadFusion: removed known bad component %s in fusion dir",
  1837. e.CurrentString
  1838. ));
  1839. }
  1840. } while (EnumNextMultiSz (&e));
  1841. }
  1842. }
  1843. return b;
  1844. }