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.

1271 lines
36 KiB

  1. /*****************************************************************/
  2. /** Microsoft Windows for Workgroups **/
  3. /** Copyright (C) Microsoft Corp., 1991-1992 **/
  4. /*****************************************************************/
  5. /* PROFILES.CPP -- Code for user profile management.
  6. *
  7. * History:
  8. * 01/04/94 gregj Created
  9. * 06/28/94 gregj Use sync engine for desktop, programs reconciliation
  10. * 09/05/96 gregj Snarfed from MPR for use by IE4 family logon.
  11. */
  12. #include "mslocusr.h"
  13. #include "msluglob.h"
  14. #include "resource.h"
  15. #include <npmsg.h>
  16. #include <regentry.h>
  17. #include <buffer.h>
  18. #include <shellapi.h>
  19. HMODULE g_hmodShell = NULL;
  20. typedef int (*PFNSHFILEOPERATIONA)(LPSHFILEOPSTRUCTA lpFileOp);
  21. PFNSHFILEOPERATIONA g_pfnSHFileOperationA = NULL;
  22. HRESULT LoadShellEntrypoint(void)
  23. {
  24. if (g_pfnSHFileOperationA != NULL)
  25. return S_OK;
  26. HRESULT hres;
  27. ENTERCRITICAL
  28. {
  29. if (g_hmodShell == NULL) {
  30. g_hmodShell = ::LoadLibrary("SHELL32.DLL");
  31. }
  32. if (g_hmodShell != NULL) {
  33. g_pfnSHFileOperationA = (PFNSHFILEOPERATIONA)::GetProcAddress(g_hmodShell, "SHFileOperationA");
  34. }
  35. if (g_pfnSHFileOperationA == NULL)
  36. hres = HRESULT_FROM_WIN32(::GetLastError());
  37. else
  38. hres = S_OK;
  39. }
  40. LEAVECRITICAL
  41. return hres;
  42. }
  43. void UnloadShellEntrypoint(void)
  44. {
  45. ENTERCRITICAL
  46. {
  47. if (g_hmodShell != NULL) {
  48. ::FreeLibrary(g_hmodShell);
  49. g_hmodShell = NULL;
  50. g_pfnSHFileOperationA = NULL;
  51. }
  52. }
  53. LEAVECRITICAL
  54. }
  55. const DWORD attrLocalProfile = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY;
  56. extern "C" {
  57. extern LONG __stdcall RegRemapPreDefKey(HKEY hkeyNew, HKEY hkeyPredef);
  58. };
  59. #ifdef DEBUG
  60. extern "C" {
  61. BOOL fNoisyReg = FALSE;
  62. };
  63. #endif
  64. LONG MyRegLoadKey(HKEY hKey, LPCSTR lpszSubKey, LPCSTR lpszFile)
  65. {
  66. #ifdef DEBUG
  67. if (fNoisyReg) {
  68. char buf[300];
  69. ::wsprintf(buf, "MyRegLoadKey(\"%s\", \"%s\")\r\n", lpszSubKey, lpszFile);
  70. ::OutputDebugString(buf);
  71. }
  72. #endif
  73. /* Since the registry doesn't support long filenames, get the short
  74. * alias for the path. If that succeeds, we use that path, otherwise
  75. * we just use the original one and hope it works.
  76. */
  77. CHAR szShortPath[MAX_PATH+1];
  78. if (GetShortPathName(lpszFile, szShortPath, sizeof(szShortPath)))
  79. lpszFile = szShortPath;
  80. return ::RegLoadKey(hKey, lpszSubKey, lpszFile);
  81. }
  82. #ifdef DEBUG
  83. LONG MyRegUnLoadKey(HKEY hKey, LPCSTR lpszSubKey)
  84. {
  85. if (fNoisyReg) {
  86. char buf[300];
  87. ::wsprintf(buf, "MyRegUnLoadKey(\"%s\")\r\n", lpszSubKey);
  88. ::OutputDebugString(buf);
  89. }
  90. return ::RegUnLoadKey(hKey, lpszSubKey);
  91. }
  92. #endif
  93. LONG MyRegSaveKey(HKEY hKey, LPCSTR lpszFile, LPSECURITY_ATTRIBUTES lpsa)
  94. {
  95. #ifdef DEBUG
  96. if (fNoisyReg) {
  97. char buf[300];
  98. ::wsprintf(buf, "MyRegSaveKey(\"%s\")\r\n", lpszFile);
  99. ::OutputDebugString(buf);
  100. }
  101. #endif
  102. /* Since the registry doesn't support long filenames, get the short
  103. * alias for the path. If that succeeds, we use that path, otherwise
  104. * we just use the original one and hope it works.
  105. *
  106. * GetShortPathName only works if the file exists, so we have to
  107. * create a dummy copy first.
  108. */
  109. HANDLE hTemp = ::CreateFile(lpszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
  110. FILE_ATTRIBUTE_NORMAL, NULL);
  111. if (hTemp == INVALID_HANDLE_VALUE)
  112. return ::GetLastError();
  113. ::CloseHandle(hTemp);
  114. CHAR szShortPath[MAX_PATH+1];
  115. if (::GetShortPathName(lpszFile, szShortPath, sizeof(szShortPath)))
  116. lpszFile = szShortPath;
  117. return ::RegSaveKey(hKey, lpszFile, lpsa);
  118. }
  119. #ifndef DEBUG
  120. #define MyRegUnLoadKey RegUnLoadKey
  121. #endif
  122. LONG OpenLogonKey(HKEY *phKey)
  123. {
  124. return ::RegOpenKey(HKEY_LOCAL_MACHINE, szLogonKey, phKey);
  125. }
  126. void AddBackslash(LPSTR lpPath)
  127. {
  128. LPCSTR lpBackslash = ::strrchrf(lpPath, '\\');
  129. if (lpBackslash == NULL || *(lpBackslash+1) != '\0')
  130. ::strcatf(lpPath, "\\");
  131. }
  132. void AddBackslash(NLS_STR& nlsPath)
  133. {
  134. ISTR istrBackslash(nlsPath);
  135. if (!nlsPath.strrchr(&istrBackslash, '\\') ||
  136. *nlsPath.QueryPch(++istrBackslash) != '\0')
  137. nlsPath += '\\';
  138. }
  139. void GetDirFromPath(NLS_STR& nlsTempDir, LPCSTR pszPath)
  140. {
  141. nlsTempDir = pszPath;
  142. ISTR istrBackslash(nlsTempDir);
  143. if (nlsTempDir.strrchr(&istrBackslash, '\\'))
  144. nlsTempDir.DelSubStr(istrBackslash);
  145. }
  146. BOOL FileExists(LPCSTR pszPath)
  147. {
  148. DWORD dwAttrs = ::GetFileAttributes(pszPath);
  149. if (dwAttrs != 0xffffffff && !(dwAttrs & FILE_ATTRIBUTE_DIRECTORY))
  150. return TRUE;
  151. else
  152. return FALSE;
  153. }
  154. BOOL DirExists(LPCSTR pszPath)
  155. {
  156. if (*pszPath == '\0')
  157. return FALSE;
  158. DWORD dwAttrs = ::GetFileAttributes(pszPath);
  159. if (dwAttrs != 0xffffffff && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY))
  160. return TRUE;
  161. else
  162. return FALSE;
  163. }
  164. /* CreateDirectoryPath attempts to create the specified directory; if the
  165. * create attempt fails, it tries to create each element of the path in case
  166. * any intermediate directories also don't exist.
  167. */
  168. BOOL CreateDirectoryPath(LPCSTR pszPath)
  169. {
  170. BOOL fRet = ::CreateDirectory(pszPath, NULL);
  171. if (fRet || (::GetLastError() != ERROR_PATH_NOT_FOUND))
  172. return fRet;
  173. NLS_STR nlsTemp(pszPath);
  174. if (nlsTemp.QueryError() != ERROR_SUCCESS)
  175. return FALSE;
  176. LPSTR pszTemp = nlsTemp.Party();
  177. LPSTR pszNext = pszTemp;
  178. /* If it's a drive-based path (which it should be), skip the drive
  179. * and first backslash -- we don't need to attempt to create the
  180. * root directory.
  181. */
  182. if (::strchrf(pszTemp, ':') != NULL) {
  183. pszNext = ::strchrf(pszTemp, '\\');
  184. if (pszNext != NULL)
  185. pszNext++;
  186. }
  187. /* Now walk through the path creating one directory at a time. */
  188. for (;;) {
  189. pszNext = ::strchrf(pszNext, '\\');
  190. if (pszNext != NULL) {
  191. *pszNext = '\0';
  192. }
  193. else {
  194. break; /* no more intermediate directories to create */
  195. }
  196. /* Create the intermediate directory. No error checking because we're
  197. * not extremely performance-critical, and we can get errors if the
  198. * directory already exists, etc. With security and other things,
  199. * the set of benign error codes we'd have to check for could be
  200. * large.
  201. */
  202. fRet = ::CreateDirectory(pszTemp, NULL);
  203. *pszNext = '\\';
  204. pszNext++;
  205. if (!*pszNext) /* ended with trailing slash? */
  206. return fRet; /* return last result */
  207. }
  208. /* We should have created all the intermediate directories by now.
  209. * Create the final path.
  210. */
  211. return ::CreateDirectory(pszPath, NULL);
  212. }
  213. UINT SafeCopy(LPCSTR pszSrc, LPCSTR pszDest, DWORD dwAttrs)
  214. {
  215. NLS_STR nlsTempDir(MAX_PATH);
  216. NLS_STR nlsTempFile(MAX_PATH);
  217. if (!nlsTempDir || !nlsTempFile)
  218. return ERROR_NOT_ENOUGH_MEMORY;
  219. GetDirFromPath(nlsTempDir, pszDest);
  220. if (!::GetTempFileName(nlsTempDir.QueryPch(), ::szProfilePrefix, 0,
  221. nlsTempFile.Party()))
  222. return ::GetLastError();
  223. nlsTempFile.DonePartying();
  224. if (!::CopyFile(pszSrc, nlsTempFile.QueryPch(), FALSE)) {
  225. UINT err = ::GetLastError();
  226. ::DeleteFile(nlsTempFile.QueryPch());
  227. return err;
  228. }
  229. ::SetFileAttributes(pszDest, FILE_ATTRIBUTE_NORMAL);
  230. ::DeleteFile(pszDest);
  231. // At this point, the temp file has the same attributes as the original
  232. // (usually read-only, hidden, system). Some servers, such as NetWare
  233. // servers, won't allow us to rename a read-only file. So we have to
  234. // take the attributes off, rename the file, then put back whatever the
  235. // caller wants.
  236. ::SetFileAttributes(nlsTempFile.QueryPch(), FILE_ATTRIBUTE_NORMAL);
  237. if (!::MoveFile(nlsTempFile.QueryPch(), pszDest))
  238. return ::GetLastError();
  239. ::SetFileAttributes(pszDest, dwAttrs);
  240. return ERROR_SUCCESS;
  241. }
  242. #ifdef LOAD_PROFILES
  243. void SetProfileTime(LPCSTR pszLocalPath, LPCSTR pszCentralPath)
  244. {
  245. HANDLE hFile = ::CreateFile(pszCentralPath,
  246. GENERIC_READ | GENERIC_WRITE,
  247. FILE_SHARE_READ, NULL,
  248. OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  249. if (hFile != INVALID_HANDLE_VALUE) {
  250. FILETIME ft;
  251. ::GetFileTime(hFile, NULL, NULL, &ft);
  252. ::CloseHandle(hFile);
  253. DWORD dwAttrs = ::GetFileAttributes(pszLocalPath);
  254. if (dwAttrs & FILE_ATTRIBUTE_READONLY) {
  255. ::SetFileAttributes(pszLocalPath, dwAttrs & ~FILE_ATTRIBUTE_READONLY);
  256. }
  257. hFile = ::CreateFile(pszLocalPath, GENERIC_READ | GENERIC_WRITE,
  258. FILE_SHARE_READ, NULL, OPEN_EXISTING,
  259. FILE_ATTRIBUTE_NORMAL, NULL);
  260. if (hFile != INVALID_HANDLE_VALUE) {
  261. ::SetFileTime(hFile, NULL, NULL, &ft);
  262. ::CloseHandle(hFile);
  263. }
  264. if (dwAttrs & FILE_ATTRIBUTE_READONLY) {
  265. ::SetFileAttributes(pszLocalPath, dwAttrs & ~FILE_ATTRIBUTE_READONLY);
  266. }
  267. }
  268. }
  269. UINT DefaultReconcile(LPCSTR pszCentralPath, LPCSTR pszLocalPath, DWORD dwFlags)
  270. {
  271. UINT err;
  272. if (dwFlags & RP_LOGON) {
  273. if (dwFlags & RP_INIFILE)
  274. return SafeCopy(pszCentralPath, pszLocalPath, FILE_ATTRIBUTE_NORMAL);
  275. HANDLE hFile = ::CreateFile(pszCentralPath, GENERIC_READ, FILE_SHARE_READ,
  276. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  277. FILETIME ftCentral;
  278. if (hFile != INVALID_HANDLE_VALUE) {
  279. ::GetFileTime(hFile, NULL, NULL, &ftCentral);
  280. ::CloseHandle(hFile);
  281. }
  282. else {
  283. ftCentral.dwLowDateTime = 0; /* can't open, pretend it's really old */
  284. ftCentral.dwHighDateTime = 0;
  285. }
  286. hFile = ::CreateFile(pszLocalPath, GENERIC_READ, FILE_SHARE_READ,
  287. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  288. FILETIME ftLocal;
  289. if (hFile != INVALID_HANDLE_VALUE) {
  290. ::GetFileTime(hFile, NULL, NULL, &ftLocal);
  291. ::CloseHandle(hFile);
  292. }
  293. else {
  294. ftLocal.dwLowDateTime = 0; /* can't open, pretend it's really old */
  295. ftLocal.dwHighDateTime = 0;
  296. }
  297. LPCSTR pszSrc, pszDest;
  298. /*
  299. * Find out which file is newer, and make that the source
  300. * for the copy.
  301. */
  302. LONG lCompare = ::CompareFileTime(&ftCentral, &ftLocal);
  303. if (!lCompare) {
  304. ::dwProfileFlags |= PROF_CENTRALWINS;
  305. return WN_SUCCESS; /* timestamps match, no copy to do */
  306. }
  307. else if (lCompare > 0) {
  308. pszSrc = pszCentralPath;
  309. pszDest = pszLocalPath;
  310. ::dwProfileFlags |= PROF_CENTRALWINS;
  311. }
  312. else {
  313. pszSrc = pszLocalPath;
  314. pszDest = pszCentralPath;
  315. ::dwProfileFlags &= ~PROF_CENTRALWINS;
  316. }
  317. err = SafeCopy(pszSrc, pszDest,
  318. pszDest == pszCentralPath ? FILE_ATTRIBUTE_NORMAL
  319. : attrLocalProfile);
  320. }
  321. else {
  322. err = SafeCopy(pszLocalPath, pszCentralPath, FILE_ATTRIBUTE_NORMAL);
  323. if (err == WN_SUCCESS) { /* copied back successfully */
  324. #ifdef EXTENDED_PROFILES /* chicago doesn't special-case resident profiles */
  325. if (dwFlags & PROF_RESIDENT) {
  326. DeleteProfile(pszLocalPath); /* delete temp file */
  327. }
  328. #endif
  329. SetProfileTime(pszLocalPath, pszCentralPath);
  330. }
  331. }
  332. return err;
  333. }
  334. #endif /* LOAD_PROFILES */
  335. void GetLocalProfileDirectory(NLS_STR& nlsPath)
  336. {
  337. ::GetWindowsDirectory(nlsPath.Party(), nlsPath.QueryAllocSize());
  338. nlsPath.DonePartying();
  339. AddBackslash(nlsPath);
  340. nlsPath.strcat(::szProfilesDirectory);
  341. ::CreateDirectory(nlsPath.QueryPch(), NULL);
  342. }
  343. HRESULT GiveUserDefaultProfile(LPCSTR lpszPath)
  344. {
  345. HKEY hkeyDefaultUser;
  346. LONG err = ::RegOpenKey(HKEY_USERS, ::szDefaultUserName, &hkeyDefaultUser);
  347. if (err == ERROR_SUCCESS) {
  348. err = ::MyRegSaveKey(hkeyDefaultUser, lpszPath, NULL);
  349. ::RegCloseKey(hkeyDefaultUser);
  350. }
  351. return HRESULT_FROM_WIN32(err);
  352. }
  353. void ComputeLocalProfileName(LPCSTR pszUsername, NLS_STR *pnlsLocalProfile)
  354. {
  355. GetLocalProfileDirectory(*pnlsLocalProfile);
  356. UINT cbPath = pnlsLocalProfile->strlen();
  357. LPSTR lpPath = pnlsLocalProfile->Party();
  358. LPSTR lpFilename = lpPath + cbPath;
  359. *(lpFilename++) = '\\';
  360. ::strcpyf(lpFilename, pszUsername); /* start with whole username */
  361. LPSTR lpFNStart = lpFilename;
  362. UINT iFile = 0;
  363. while (!::CreateDirectory(lpPath, NULL)) {
  364. if (!DirExists(lpPath))
  365. break;
  366. /* Couldn't use whole username, start with 5 bytes of username + numbers. */
  367. if (iFile == 0) {
  368. ::strncpyf(lpFilename, pszUsername, 5); /* copy at most 5 bytes of username */
  369. *(lpFilename+5) = '\0'; /* force null term, just in case */
  370. lpFilename += ::strlenf(lpFilename);
  371. }
  372. else if (iFile >= 4095) { /* max number expressible in 3 hex digits */
  373. lpFilename = lpFNStart; /* start using big numbers with no uname prefix */
  374. if ((int)iFile < 0) /* if we run out of numbers, abort */
  375. break;
  376. }
  377. ::wsprintf(lpFilename, "%03lx", iFile);
  378. iFile++;
  379. }
  380. pnlsLocalProfile->DonePartying();
  381. }
  382. HRESULT CopyProfile(LPCSTR pszSrcPath, LPCSTR pszDestPath)
  383. {
  384. UINT err = SafeCopy(pszSrcPath, pszDestPath, attrLocalProfile);
  385. return HRESULT_FROM_WIN32(err);
  386. }
  387. BOOL UseUserProfiles(void)
  388. {
  389. HKEY hkeyLogon;
  390. LONG err = OpenLogonKey(&hkeyLogon);
  391. if (err == ERROR_SUCCESS) {
  392. DWORD fUseProfiles = 0;
  393. DWORD cbData = sizeof(fUseProfiles);
  394. err = ::RegQueryValueEx(hkeyLogon, (LPSTR)::szUseProfiles, NULL, NULL,
  395. (LPBYTE)&fUseProfiles, &cbData);
  396. ::RegCloseKey(hkeyLogon);
  397. return (err == ERROR_SUCCESS) && fUseProfiles;
  398. }
  399. return FALSE;
  400. }
  401. void EnableProfiles(void)
  402. {
  403. HKEY hkeyLogon;
  404. LONG err = OpenLogonKey(&hkeyLogon);
  405. if (err == ERROR_SUCCESS) {
  406. DWORD fUseProfiles = 1;
  407. ::RegSetValueEx(hkeyLogon, (LPSTR)::szUseProfiles, 0, REG_DWORD,
  408. (LPBYTE)&fUseProfiles, sizeof(fUseProfiles));
  409. ::RegCloseKey(hkeyLogon);
  410. }
  411. }
  412. struct SYNCSTATE
  413. {
  414. HKEY hkeyProfile;
  415. NLS_STR *pnlsProfilePath;
  416. NLS_STR *pnlsOtherProfilePath;
  417. HKEY hkeyPrimary;
  418. };
  419. /*
  420. * PrefixMatch determines whether a given path is equal to or a descendant
  421. * of a given base path.
  422. */
  423. BOOL PrefixMatch(LPCSTR pszPath, LPCSTR pszBasePath)
  424. {
  425. UINT cchBasePath = ::strlenf(pszBasePath);
  426. if (!::strnicmpf(pszPath, pszBasePath, cchBasePath)) {
  427. /* make sure that the base path matches the whole last component */
  428. if ((pszPath[cchBasePath] == '\\' || pszPath[cchBasePath] == '\0'))
  429. return TRUE;
  430. /* check to see if the base path is a root path; if so, match */
  431. LPCSTR pszBackslash = ::strrchrf(pszBasePath, '\\');
  432. if (pszBackslash != NULL && *(pszBackslash+1) == '\0')
  433. return TRUE;
  434. else
  435. return FALSE;
  436. }
  437. else
  438. return FALSE;
  439. }
  440. #if 0
  441. void ReportReconcileError(SYNCSTATE *pSyncState, TWINRESULT tr, PRECITEM pri,
  442. PRECNODE prnSrc, PRECNODE prnDest, BOOL fSrcCentral)
  443. {
  444. /* If we're copying the file the "wrong" way, swap our idea of the
  445. * source and destination. For the purposes of other profile code,
  446. * source and destination refer to the entire profile copy direction.
  447. * For this particular error message, they refer to the direction
  448. * that this particular file was being copied.
  449. */
  450. if (prnSrc->rnaction == RNA_COPY_TO_ME) {
  451. PRECNODE prnTemp = prnSrc;
  452. prnSrc = prnDest;
  453. prnDest = prnTemp;
  454. fSrcCentral = !fSrcCentral;
  455. }
  456. /* Set the error status on this key to be the destination of the copy,
  457. * which is the copy that's now out of date because of the error and
  458. * needs to be guarded from harm next time.
  459. */
  460. pSyncState->uiRecError |= fSrcCentral ? RECERROR_LOCAL : RECERROR_CENTRAL;
  461. pSyncState->dwFlags |= SYNCSTATE_ERROR;
  462. if (pSyncState->dwFlags & SYNCSTATE_ERRORMSG)
  463. return; /* error already reported */
  464. pSyncState->dwFlags |= SYNCSTATE_ERRORMSG;
  465. RegEntry re(::szReconcileRoot, pSyncState->hkeyProfile);
  466. if (re.GetError() == ERROR_SUCCESS && !re.GetNumber(::szDisplayProfileErrors, TRUE))
  467. return; /* user doesn't want to see this error message */
  468. PCSTR pszFile;
  469. UINT uiMainMsg;
  470. switch (tr) {
  471. case TR_DEST_OPEN_FAILED:
  472. case TR_DEST_WRITE_FAILED:
  473. uiMainMsg = IERR_ProfRecWriteDest;
  474. pszFile = prnDest->pcszFolder;
  475. break;
  476. case TR_SRC_OPEN_FAILED:
  477. case TR_SRC_READ_FAILED:
  478. uiMainMsg = IERR_ProfRecOpenSrc;
  479. pszFile = prnSrc->pcszFolder;
  480. break;
  481. default:
  482. uiMainMsg = IERR_ProfRecCopy;
  483. pszFile = pri->pcszName;
  484. break;
  485. }
  486. if (DisplayGenericError(NULL, uiMainMsg, tr, pszFile, ::szNULL,
  487. MB_YESNO | MB_ICONEXCLAMATION, IDS_TRMsgBase) == IDNO) {
  488. re.SetValue(::szDisplayProfileErrors, (ULONG)FALSE);
  489. }
  490. }
  491. #ifdef DEBUG
  492. char szOutbuf[200];
  493. #endif
  494. /*
  495. * MyReconcile is a wrapper around ReconcileItem. It needs to detect merge
  496. * type operations and transform them into copies in the appropriate direction,
  497. * and recognize when the sync engine wants to replace a file that the user
  498. * really wants deleted.
  499. */
  500. void MyReconcile(PRECITEM pri, SYNCSTATE *pSyncState)
  501. {
  502. if (pri->riaction == RIA_NOTHING)
  503. return;
  504. /* Because we don't have a persistent briefcase, we can't recognize when
  505. * the user has deleted an item; the briefcase will want to replace it
  506. * with the other version, which is not what the user wants. So we use
  507. * the direction of the profile's copy, and if the sync engine wants to
  508. * copy a file from the "destination" of the profile's copy to the "source"
  509. * because the "source" doesn't exist, we recognize that as the source
  510. * having been deleted and synchronize manually by deleting the dest.
  511. *
  512. * prnSrc points to the recnode for the item that's coming from the same
  513. * side of the transaction that the more recent profile was on; prnDest
  514. * points to the recnode for the other side.
  515. *
  516. * The test is complicated because we first have to figure out which of
  517. * the two directories (nlsDir1, the local dir; or nlsDir2, the central
  518. * dir) is the source and which the destination. Then we have to figure
  519. * out which of the two RECNODEs we got matches which directory.
  520. */
  521. PRECNODE prnSrc;
  522. PRECNODE prnDest;
  523. LPCSTR pszSrcBasePath;
  524. BOOL fSrcCentral;
  525. if (pSyncState->IsMandatory() || (pSyncState->dwFlags & PROF_CENTRALWINS)) {
  526. pszSrcBasePath = pSyncState->nlsDir2.QueryPch();
  527. fSrcCentral = TRUE;
  528. }
  529. else {
  530. pszSrcBasePath = pSyncState->nlsDir1.QueryPch();
  531. fSrcCentral = FALSE;
  532. }
  533. if (PrefixMatch(pri->prnFirst->pcszFolder, pszSrcBasePath)) {
  534. prnSrc = pri->prnFirst;
  535. prnDest = prnSrc->prnNext;
  536. }
  537. else {
  538. prnDest = pri->prnFirst;
  539. prnSrc = prnDest->prnNext;
  540. }
  541. /*
  542. * If files of the same name exist in both places, the sync engine thinks
  543. * they need to be merged (since we have no persistent briefcase database,
  544. * it doesn't know that they were originally the same). The sync engine
  545. * sets the file stamp of a copied destination file to the file stamp of
  546. * the source file after copying. If the file stamps of two files to be
  547. * merged are the same, we assume that the files are already up-to-date,
  548. * and we take no reconciliation action. If the file stamps of two files
  549. * to be merged are different, we really just want a copy, so we figure out
  550. * which one is supposed to be definitive and transform the RECITEM and
  551. * RECNODEs to indicate a copy instead of a merge.
  552. *
  553. * The definitive copy is the source for mandatory or logoff cases,
  554. * otherwise it's the newer file.
  555. */
  556. if (pri->riaction == RIA_MERGE || pri->riaction == RIA_BROKEN_MERGE) {
  557. BOOL fCopyFromSrc;
  558. COMPARISONRESULT cr;
  559. if (pSyncState->IsMandatory())
  560. fCopyFromSrc = TRUE;
  561. else {
  562. fCopyFromSrc = ! pSyncState->IsLogon();
  563. if (pSyncState->CompareFileStamps(&prnSrc->fsCurrent, &prnDest->fsCurrent, &cr) == TR_SUCCESS) {
  564. if (cr == CR_EQUAL) {
  565. #ifdef MAXDEBUG
  566. ::OutputDebugString("Matching file stamps, no action taken\r\n");
  567. #endif
  568. return;
  569. }
  570. else if (cr==CR_FIRST_LARGER)
  571. fCopyFromSrc = TRUE;
  572. }
  573. }
  574. #ifdef MAXDEBUG
  575. if (fCopyFromSrc)
  576. ::OutputDebugString("Broken merge, copying from src\r\n");
  577. else
  578. ::OutputDebugString("Broken merge, copying from dest\r\n");
  579. #endif
  580. prnSrc->rnaction = fCopyFromSrc ? RNA_COPY_FROM_ME : RNA_COPY_TO_ME;
  581. prnDest->rnaction = fCopyFromSrc ? RNA_COPY_TO_ME : RNA_COPY_FROM_ME;
  582. pri->riaction = RIA_COPY;
  583. }
  584. /*
  585. * If the preferred source file doesn't exist, the sync engine is trying
  586. * to create a file to make the two trees the same, when the user/admin
  587. * really wanted to delete it (the sync engine doesn't like deleting
  588. * files). So we detect that case here and delete the "destination"
  589. * to make the two trees match that way.
  590. *
  591. * If the last reconciliation had an error, we don't do the deletion
  592. * if the site of the error is the current source (i.e., if we're
  593. * about to delete the file we couldn't copy before). Instead we'll
  594. * try the operation that the sync engine wants, since that'll be the
  595. * copy that failed before.
  596. */
  597. if (prnSrc->rnstate == RNS_DOES_NOT_EXIST &&
  598. prnSrc->rnaction == RNA_COPY_TO_ME &&
  599. !((pSyncState->uiRecError & RECERROR_CENTRAL) && fSrcCentral) &&
  600. !((pSyncState->uiRecError & RECERROR_LOCAL) && !fSrcCentral)) {
  601. if (IS_EMPTY_STRING(pri->pcszName)) {
  602. ::RemoveDirectory(prnDest->pcszFolder);
  603. }
  604. else {
  605. NLS_STR nlsTemp(prnDest->pcszFolder);
  606. AddBackslash(nlsTemp);
  607. nlsTemp.strcat(pri->pcszName);
  608. if (!nlsTemp.QueryError()) {
  609. #ifdef MAXDEBUG
  610. if (pSyncState->IsMandatory())
  611. ::OutputDebugString("Mandatory copy wrong way\r\n");
  612. wsprintf(::szOutbuf, "Deleting 'destination' file %s\r\n", nlsTemp.QueryPch());
  613. ::OutputDebugString(::szOutbuf);
  614. #endif
  615. ::DeleteFile(nlsTemp.QueryPch());
  616. }
  617. }
  618. return;
  619. }
  620. #ifdef MAXDEBUG
  621. ::OutputDebugString("Calling ReconcileItem.\r\n");
  622. #endif
  623. TWINRESULT tr;
  624. if ((tr=pSyncState->ReconcileItem(pri, NULL, 0, 0, NULL, NULL)) != TR_SUCCESS) {
  625. ReportReconcileError(pSyncState, tr, pri, prnSrc, prnDest, fSrcCentral);
  626. #ifdef MAXDEBUG
  627. ::wsprintf(::szOutbuf, "Error %d from ReconcileItem.\r\n", tr);
  628. ::OutputDebugString(::szOutbuf);
  629. #endif
  630. }
  631. else if (!IS_EMPTY_STRING(pri->pcszName))
  632. pSyncState->dwFlags |= SYNCSTATE_SOMESUCCESS;
  633. }
  634. /*
  635. * MakePathAbsolute examines a path to see whether it is absolute or relative.
  636. * If it is relative, it is prepended with the given base path.
  637. *
  638. * If the fMustBeRelative parameter is TRUE, then an error is returned if the
  639. * path was (a) absolute and (b) not a subdirectory of the old profile directory.
  640. */
  641. BOOL MakePathAbsolute(NLS_STR& nlsDir, LPCSTR lpszBasePath,
  642. NLS_STR& nlsOldProfileDir, BOOL fMustBeRelative)
  643. {
  644. /* If the path starts with a special keyword, replace it. */
  645. if (*nlsDir.QueryPch() == '*') {
  646. return ReplaceCommonPath(nlsDir);
  647. }
  648. /* If the path is absolute and is relative to whatever the old profile
  649. * directory was, transform it to a relative path. We will then make
  650. * it absolute again, using the new base path.
  651. */
  652. if (PrefixMatch(nlsDir, nlsOldProfileDir)) {
  653. UINT cchDir = nlsDir.strlen();
  654. LPSTR lpStart = nlsDir.Party();
  655. ::memmovef(lpStart, lpStart + nlsOldProfileDir.strlen(), cchDir - nlsOldProfileDir.strlen() + 1);
  656. nlsDir.DonePartying();
  657. }
  658. else if (::strchrf(nlsDir.QueryPch(), ':') != NULL || *nlsDir.QueryPch() == '\\')
  659. return !fMustBeRelative;
  660. if (*lpszBasePath == '\0') {
  661. nlsDir = lpszBasePath;
  662. return TRUE;
  663. }
  664. NLS_STR nlsBasePath(lpszBasePath);
  665. if (nlsBasePath.QueryError())
  666. return FALSE;
  667. AddBackslash(nlsBasePath);
  668. ISTR istrStart(nlsDir);
  669. nlsDir.InsertStr(nlsBasePath, istrStart);
  670. return !nlsDir.QueryError();
  671. }
  672. #endif /**** 0 ****/
  673. /*
  674. * ReplaceCommonPath takes a relative path beginning with a special keyword
  675. * and replaces the keyword with the corresponding real path. Currently the
  676. * keyword supported is:
  677. *
  678. * *windir - replaced with the Windows (user) directory
  679. */
  680. BOOL ReplaceCommonPath(NLS_STR& nlsDir)
  681. {
  682. NLS_STR *pnlsTemp;
  683. ISTR istrStart(nlsDir);
  684. ISTR istrEnd(nlsDir);
  685. nlsDir.strchr(&istrEnd, '\\');
  686. pnlsTemp = nlsDir.QuerySubStr(istrStart, istrEnd);
  687. if (pnlsTemp == NULL)
  688. return FALSE; /* out of memory, can't do anything */
  689. BOOL fSuccess = TRUE;
  690. if (!::stricmpf(pnlsTemp->QueryPch(), ::szWindirAlias)) {
  691. UINT cbBuffer = pnlsTemp->QueryAllocSize();
  692. LPSTR lpBuffer = pnlsTemp->Party();
  693. UINT cchWindir = ::GetWindowsDirectory(lpBuffer, cbBuffer);
  694. if (cchWindir >= cbBuffer)
  695. *lpBuffer = '\0';
  696. pnlsTemp->DonePartying();
  697. if (cchWindir >= cbBuffer) {
  698. pnlsTemp->realloc(cchWindir+1);
  699. if (!pnlsTemp->QueryError()) {
  700. ::GetWindowsDirectory(pnlsTemp->Party(), cchWindir+1);
  701. pnlsTemp->DonePartying();
  702. }
  703. else
  704. fSuccess = FALSE;
  705. }
  706. if (fSuccess) {
  707. nlsDir.ReplSubStr(*pnlsTemp, istrStart, istrEnd);
  708. fSuccess = !nlsDir.QueryError();
  709. }
  710. }
  711. delete pnlsTemp;
  712. return fSuccess;
  713. }
  714. /*
  715. * GetSetRegistryPath goes to the registry key and value specified by
  716. * the current reconciliations's RegKey and RegValue settings, and
  717. * retrieves or sets a path there.
  718. */
  719. void GetSetRegistryPath(HKEY hkeyProfile, RegEntry& re, NLS_STR *pnlsPath, BOOL fSet)
  720. {
  721. NLS_STR nlsKey;
  722. re.GetValue(::szReconcileRegKey, &nlsKey);
  723. if (nlsKey.strlen() > 0) {
  724. NLS_STR nlsValue;
  725. re.GetValue(::szReconcileRegValue, &nlsValue);
  726. RegEntry re2(nlsKey, hkeyProfile);
  727. if (fSet) {
  728. re2.SetValue(nlsValue, pnlsPath->QueryPch());
  729. if (!nlsKey.stricmp("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders")) {
  730. nlsKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
  731. RegEntry reShell(nlsKey, hkeyProfile);
  732. reShell.SetValue(nlsValue, pnlsPath->QueryPch());
  733. }
  734. }
  735. else
  736. re2.GetValue(nlsValue, pnlsPath);
  737. }
  738. }
  739. /* CopyFolder calls the shell's copy engine to copy files. The source is a
  740. * double-null-terminated list; the destination is a folder.
  741. */
  742. void CopyFolder(LPBYTE pbSource, LPCSTR pszDest)
  743. {
  744. CHAR szDest[MAX_PATH];
  745. ::strcpyf(szDest, pszDest);
  746. szDest[::strlenf(szDest) + 1] = '\0';
  747. SHFILEOPSTRUCT fos;
  748. fos.hwnd = NULL;
  749. fos.wFunc = FO_COPY;
  750. fos.pFrom = (LPCSTR)pbSource;
  751. fos.pTo = szDest;
  752. fos.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
  753. fos.fAnyOperationsAborted = FALSE;
  754. fos.hNameMappings = NULL;
  755. fos.lpszProgressTitle = NULL;
  756. g_pfnSHFileOperationA(&fos);
  757. }
  758. /*
  759. * ReconcileKey performs reconciliation for a particular key in the
  760. * ProfileReconciliation branch of the registry. It reads the config
  761. * parameters for the reconciliation, sets up an appropriate twin in
  762. * the temporary briefcase, and performs the reconciliation.
  763. */
  764. BOOL ReconcileKey(HKEY hkeySection, LPCSTR lpszSubKey, SYNCSTATE *pSyncState)
  765. {
  766. #ifdef DEBUG
  767. DWORD dwStart = ::GetTickCount();
  768. #endif
  769. BOOL fShouldDelete = FALSE;
  770. RegEntry re(lpszSubKey, hkeySection);
  771. if (re.GetError() == ERROR_SUCCESS) {
  772. BUFFER bufSrcStrings(MAX_PATH);
  773. NLS_STR nlsSrcPath(MAX_PATH);
  774. NLS_STR nlsDestPath(MAX_PATH);
  775. NLS_STR nlsName(MAX_PATH);
  776. if (bufSrcStrings.QueryPtr() != NULL &&
  777. nlsSrcPath.QueryError() == ERROR_SUCCESS &&
  778. nlsDestPath.QueryError() == ERROR_SUCCESS &&
  779. nlsName.QueryError() == ERROR_SUCCESS) {
  780. /* Get the source path to copy. Usually it's in the profile,
  781. * left over from the profile we cloned. If not, we take the
  782. * default local name from the ProfileReconciliation key. If
  783. * the path already in the registry is not relative to the cloned
  784. * profile directory, then it's probably set by system policies
  785. * or something, and we shouldn't touch it.
  786. */
  787. if (pSyncState->pnlsOtherProfilePath != NULL) {
  788. GetSetRegistryPath(pSyncState->hkeyProfile, re, &nlsSrcPath, FALSE);
  789. if (nlsSrcPath.strlen() &&
  790. !PrefixMatch(nlsSrcPath.QueryPch(), pSyncState->pnlsOtherProfilePath->QueryPch())) {
  791. return FALSE; /* not profile-relative, nothing to do */
  792. }
  793. }
  794. if (!nlsSrcPath.strlen()) {
  795. re.GetValue(::szDefaultDir, &nlsSrcPath);
  796. if (*nlsSrcPath.QueryPch() == '*') {
  797. ReplaceCommonPath(nlsSrcPath);
  798. }
  799. }
  800. /* Get the set of files to copy. Like NT and unlike win95, we
  801. * want to clone the entire contents, not necessarily just the
  802. * files listed (for example, the desktop -- we want all the
  803. * files and subfolders, not just links). So, unless the string
  804. * is empty, which means don't copy any content, just set the reg
  805. * path, we change any pattern containing wildcards to *.*.
  806. */
  807. re.GetValue(::szReconcileName, &nlsName);
  808. if (nlsName.strlen()) {
  809. if (::strchrf(nlsName.QueryPch(), '*') != NULL ||
  810. ::strchrf(nlsName.QueryPch(), '?') != NULL) {
  811. nlsName = "*.*";
  812. }
  813. }
  814. /* Get the destination path. This is generated from the new
  815. * profile directory and the LocalFile entry in the registry.
  816. *
  817. * Should always do this, even if we're not going to call the
  818. * copy engine, because we're going to write this path out to
  819. * the registry.
  820. */
  821. re.GetValue(::szLocalFile, &nlsDestPath);
  822. ISTR istr(nlsDestPath);
  823. nlsDestPath.InsertStr(*(pSyncState->pnlsProfilePath), istr);
  824. /* Always create the destination path, even if we don't copy
  825. * any files into it because the source directory doesn't exist.
  826. */
  827. CreateDirectoryPath(nlsDestPath.QueryPch());
  828. /* Make sure the source directory exists so we won't get useless
  829. * error messages from the shell copy engine.
  830. */
  831. DWORD dwAttr = GetFileAttributes(nlsSrcPath.QueryPch());
  832. if (dwAttr != 0xffffffff && (dwAttr & FILE_ATTRIBUTE_DIRECTORY) &&
  833. nlsName.strlen()) {
  834. AddBackslash(nlsSrcPath);
  835. /* Build up the double-null-terminated list of file specs to copy. */
  836. UINT cbUsed = 0;
  837. LPSTR lpName = nlsName.Party();
  838. do {
  839. LPSTR lpNext = ::strchrf(lpName, ',');
  840. if (lpNext != NULL) {
  841. *(lpNext++) = '\0';
  842. }
  843. UINT cbNeeded = nlsSrcPath.strlen() + ::strlenf(lpName) + 1;
  844. if (bufSrcStrings.QuerySize() - cbUsed < cbNeeded) {
  845. if (!bufSrcStrings.Resize(bufSrcStrings.QuerySize() + MAX_PATH))
  846. return FALSE;
  847. }
  848. LPSTR lpDest = ((LPSTR)bufSrcStrings.QueryPtr()) + cbUsed;
  849. ::strcpyf(lpDest, nlsSrcPath.QueryPch());
  850. lpDest += nlsSrcPath.strlen();
  851. ::strcpyf(lpDest, lpName);
  852. cbUsed += cbNeeded;
  853. lpName = lpNext;
  854. } while (lpName != NULL);
  855. *((LPSTR)bufSrcStrings.QueryPtr() + cbUsed) = '\0'; /* double null terminate */
  856. nlsName.DonePartying();
  857. CopyFolder((LPBYTE)bufSrcStrings.QueryPtr(), nlsDestPath.QueryPch());
  858. }
  859. /*
  860. * Set a registry key to point to the new local path to this directory.
  861. */
  862. GetSetRegistryPath(pSyncState->hkeyProfile, re, &nlsDestPath, TRUE);
  863. }
  864. }
  865. #ifdef MAXDEBUG
  866. ::wsprintf(::szOutbuf, "ReconcileKey duration %d ms.\r\n", ::GetTickCount() - dwStart);
  867. ::OutputDebugString(::szOutbuf);
  868. #endif
  869. return fShouldDelete;
  870. }
  871. /*
  872. * GetMaxSubkeyLength just calls RegQueryInfoKey to get the length of the
  873. * longest named subkey of the given key. The return value is the size
  874. * of buffer needed to hold the longest key name, including the null
  875. * terminator.
  876. */
  877. DWORD GetMaxSubkeyLength(HKEY hKey)
  878. {
  879. DWORD cchClass = 0;
  880. DWORD cSubKeys;
  881. DWORD cchMaxSubkey;
  882. DWORD cchMaxClass;
  883. DWORD cValues;
  884. DWORD cchMaxValueName;
  885. DWORD cbMaxValueData;
  886. DWORD cbSecurityDescriptor;
  887. FILETIME ftLastWriteTime;
  888. RegQueryInfoKey(hKey, NULL, &cchClass, NULL, &cSubKeys, &cchMaxSubkey,
  889. &cchMaxClass, &cValues, &cchMaxValueName, &cbMaxValueData,
  890. &cbSecurityDescriptor, &ftLastWriteTime);
  891. return cchMaxSubkey + 1;
  892. }
  893. /*
  894. * ReconcileSection walks through the ProfileReconciliation key and performs
  895. * reconciliation for each subkey. One-time keys are deleted after they are
  896. * processed.
  897. */
  898. void ReconcileSection(HKEY hkeyRoot, SYNCSTATE *pSyncState)
  899. {
  900. NLS_STR nlsKeyName(GetMaxSubkeyLength(hkeyRoot));
  901. if (!nlsKeyName.QueryError()) {
  902. DWORD iKey = 0;
  903. for (;;) {
  904. DWORD cchKey = nlsKeyName.QueryAllocSize();
  905. UINT err = ::RegEnumKey(hkeyRoot, iKey, nlsKeyName.Party(), cchKey);
  906. if (err != ERROR_SUCCESS)
  907. break;
  908. nlsKeyName.DonePartying();
  909. if (ReconcileKey(hkeyRoot, nlsKeyName, pSyncState)) {
  910. ::RegDeleteKey(hkeyRoot, nlsKeyName.QueryPch());
  911. }
  912. else
  913. iKey++;
  914. }
  915. }
  916. }
  917. /*
  918. * ReconcileFiles is called just after the user's profile and policies are
  919. * loaded at logon, and just before the profile is unloaded at logoff. It
  920. * performs all file type reconciliation for the user's profile, excluding
  921. * the profile itself, of course.
  922. *
  923. * nlsOtherProfilePath is the path to the profile which is being cloned,
  924. * or an empty string if the default profile is being cloned.
  925. */
  926. HRESULT ReconcileFiles(HKEY hkeyProfile, NLS_STR& nlsProfilePath,
  927. NLS_STR& nlsOtherProfilePath)
  928. {
  929. HRESULT hres = LoadShellEntrypoint();
  930. if (FAILED(hres))
  931. return hres;
  932. if (nlsOtherProfilePath.strlen())
  933. {
  934. ISTR istrBackslash(nlsOtherProfilePath);
  935. if (nlsOtherProfilePath.strrchr(&istrBackslash, '\\')) {
  936. ++istrBackslash;
  937. nlsOtherProfilePath.DelSubStr(istrBackslash);
  938. }
  939. }
  940. RegEntry re(::szReconcileRoot, hkeyProfile);
  941. if (re.GetError() == ERROR_SUCCESS) {
  942. SYNCSTATE s;
  943. s.hkeyProfile = hkeyProfile;
  944. s.pnlsProfilePath = &nlsProfilePath;
  945. s.pnlsOtherProfilePath = (nlsOtherProfilePath.strlen() != 0) ? &nlsOtherProfilePath : NULL;
  946. s.hkeyPrimary = NULL;
  947. RegEntry rePrimary(::szReconcilePrimary, re.GetKey());
  948. RegEntry reSecondary(::szReconcileSecondary, re.GetKey());
  949. if (rePrimary.GetError() == ERROR_SUCCESS) {
  950. ReconcileSection(rePrimary.GetKey(), &s);
  951. if (reSecondary.GetError() == ERROR_SUCCESS) {
  952. s.hkeyPrimary = rePrimary.GetKey();
  953. ReconcileSection(reSecondary.GetKey(), &s);
  954. }
  955. }
  956. }
  957. return ERROR_SUCCESS;
  958. }
  959. HRESULT DefaultReconcileKey(HKEY hkeyProfile, NLS_STR& nlsProfilePath,
  960. LPCSTR pszKeyName, BOOL fSecondary)
  961. {
  962. HRESULT hres = LoadShellEntrypoint();
  963. if (FAILED(hres))
  964. return hres;
  965. RegEntry re(::szReconcileRoot, hkeyProfile);
  966. if (re.GetError() == ERROR_SUCCESS) {
  967. SYNCSTATE s;
  968. s.hkeyProfile = hkeyProfile;
  969. s.pnlsProfilePath = &nlsProfilePath;
  970. s.pnlsOtherProfilePath = NULL;
  971. s.hkeyPrimary = NULL;
  972. RegEntry rePrimary(::szReconcilePrimary, re.GetKey());
  973. if (rePrimary.GetError() == ERROR_SUCCESS) {
  974. if (fSecondary) {
  975. RegEntry reSecondary(::szReconcileSecondary, re.GetKey());
  976. s.hkeyPrimary = rePrimary.GetKey();
  977. ReconcileKey(reSecondary.GetKey(), pszKeyName, &s);
  978. }
  979. else
  980. ReconcileKey(rePrimary.GetKey(), pszKeyName, &s);
  981. }
  982. }
  983. return ERROR_SUCCESS;
  984. }
  985. HRESULT DeleteProfileFiles(LPCSTR pszPath)
  986. {
  987. HRESULT hres = LoadShellEntrypoint();
  988. if (FAILED(hres))
  989. return hres;
  990. SHFILEOPSTRUCT fos;
  991. TCHAR szFrom[MAX_PATH];
  992. lstrcpy(szFrom, pszPath);
  993. /* Before we build the complete source filespec, check to see if the
  994. * directory exists. In the case of lesser-used folders such as
  995. * "Application Data", the default may not have ever been created.
  996. * In that case, we have no contents to copy.
  997. */
  998. DWORD dwAttr = GetFileAttributes(szFrom);
  999. if (dwAttr == 0xffffffff || !(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
  1000. return S_OK;
  1001. AddBackslash(szFrom);
  1002. lstrcat(szFrom, TEXT("*.*"));
  1003. szFrom[lstrlen(szFrom)+1] = '\0'; /* double null terminate from string */
  1004. fos.hwnd = NULL;
  1005. fos.wFunc = FO_DELETE;
  1006. fos.pFrom = szFrom;
  1007. fos.pTo = NULL;
  1008. fos.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
  1009. fos.fAnyOperationsAborted = FALSE;
  1010. fos.hNameMappings = NULL;
  1011. fos.lpszProgressTitle = NULL;
  1012. g_pfnSHFileOperationA(&fos);
  1013. ::RemoveDirectory(pszPath);
  1014. return NOERROR;
  1015. }
  1016. HRESULT DeleteProfile(LPCSTR pszName)
  1017. {
  1018. RegEntry re(::szProfileList, HKEY_LOCAL_MACHINE);
  1019. HRESULT hres;
  1020. if (re.GetError() == ERROR_SUCCESS) {
  1021. { /* extra scope for next RegEntry */
  1022. RegEntry reUser(pszName, re.GetKey());
  1023. if (reUser.GetError() == ERROR_SUCCESS) {
  1024. NLS_STR nlsPath(MAX_PATH);
  1025. if (nlsPath.QueryError() == ERROR_SUCCESS) {
  1026. reUser.GetValue(::szProfileImagePath, &nlsPath);
  1027. if (reUser.GetError() == ERROR_SUCCESS) {
  1028. hres = DeleteProfileFiles(nlsPath.QueryPch());
  1029. }
  1030. else
  1031. hres = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
  1032. }
  1033. else
  1034. hres = HRESULT_FROM_WIN32(nlsPath.QueryError());
  1035. }
  1036. else
  1037. hres = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
  1038. }
  1039. if (SUCCEEDED(hres)) {
  1040. ::RegDeleteKey(re.GetKey(), pszName);
  1041. NLS_STR nlsOEMName(pszName);
  1042. if (nlsOEMName.QueryError() == ERROR_SUCCESS) {
  1043. nlsOEMName.strupr();
  1044. nlsOEMName.ToOEM();
  1045. ::DeletePasswordCache(nlsOEMName.QueryPch());
  1046. }
  1047. }
  1048. }
  1049. else
  1050. hres = E_UNEXPECTED;
  1051. return hres;
  1052. }