profiles.cpp
Upload User: xhy777
Upload Date: 2007-02-14
Package Size: 24088k
Code Size: 38k
Category:

Windows Kernel

Development Platform:

Visual C++

  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")rn", 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")rn", 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")rn", 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) != '')
  130. ::strcatf(lpPath, "\");
  131. }
  132. void AddBackslash(NLS_STR& nlsPath)
  133. {
  134. ISTR istrBackslash(nlsPath);
  135. if (!nlsPath.strrchr(&istrBackslash, '\') ||
  136. *nlsPath.QueryPch(++istrBackslash) != '')
  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 == '')
  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 = '';
  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) = ''; /* 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 (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] == ''))
  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) == '')
  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 takenrn");  
  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 srcrn");
  577. else
  578. ::OutputDebugString("Broken merge, copying from destrn");
  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 wayrn");
  612. wsprintf(::szOutbuf, "Deleting 'destination' file %srn", nlsTemp.QueryPch());
  613. ::OutputDebugString(::szOutbuf);
  614. #endif
  615. ::DeleteFile(nlsTemp.QueryPch());
  616. }
  617. }
  618. return;
  619. }
  620. #ifdef MAXDEBUG
  621. ::OutputDebugString("Calling ReconcileItem.rn");
  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.rn", 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 == '') {
  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 = '';
  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] = '';
  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++) = '';
  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) = '';    /* 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.rn", ::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] = '';   /* 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. }