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
38 KiB
1271 lines
38 KiB
/*****************************************************************/
|
|
/** Microsoft Windows for Workgroups **/
|
|
/** Copyright (C) Microsoft Corp., 1991-1992 **/
|
|
/*****************************************************************/
|
|
|
|
/* PROFILES.CPP -- Code for user profile management.
|
|
*
|
|
* History:
|
|
* 01/04/94 gregj Created
|
|
* 06/28/94 gregj Use sync engine for desktop, programs reconciliation
|
|
* 09/05/96 gregj Snarfed from MPR for use by IE4 family logon.
|
|
*/
|
|
|
|
#include "mslocusr.h"
|
|
#include "msluglob.h"
|
|
|
|
#include "resource.h"
|
|
|
|
#include <npmsg.h>
|
|
#include <regentry.h>
|
|
#include <buffer.h>
|
|
#include <shellapi.h>
|
|
|
|
HMODULE g_hmodShell = NULL;
|
|
typedef int (*PFNSHFILEOPERATIONA)(LPSHFILEOPSTRUCTA lpFileOp);
|
|
PFNSHFILEOPERATIONA g_pfnSHFileOperationA = NULL;
|
|
|
|
|
|
HRESULT LoadShellEntrypoint(void)
|
|
{
|
|
if (g_pfnSHFileOperationA != NULL)
|
|
return S_OK;
|
|
|
|
HRESULT hres;
|
|
ENTERCRITICAL
|
|
{
|
|
if (g_hmodShell == NULL) {
|
|
g_hmodShell = ::LoadLibrary("SHELL32.DLL");
|
|
}
|
|
if (g_hmodShell != NULL) {
|
|
g_pfnSHFileOperationA = (PFNSHFILEOPERATIONA)::GetProcAddress(g_hmodShell, "SHFileOperationA");
|
|
}
|
|
if (g_pfnSHFileOperationA == NULL)
|
|
hres = HRESULT_FROM_WIN32(::GetLastError());
|
|
else
|
|
hres = S_OK;
|
|
}
|
|
LEAVECRITICAL
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
void UnloadShellEntrypoint(void)
|
|
{
|
|
ENTERCRITICAL
|
|
{
|
|
if (g_hmodShell != NULL) {
|
|
::FreeLibrary(g_hmodShell);
|
|
g_hmodShell = NULL;
|
|
g_pfnSHFileOperationA = NULL;
|
|
}
|
|
}
|
|
LEAVECRITICAL
|
|
}
|
|
|
|
|
|
const DWORD attrLocalProfile = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY;
|
|
|
|
extern "C" {
|
|
extern LONG __stdcall RegRemapPreDefKey(HKEY hkeyNew, HKEY hkeyPredef);
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
|
|
extern "C" {
|
|
BOOL fNoisyReg = FALSE;
|
|
};
|
|
|
|
#endif
|
|
|
|
LONG MyRegLoadKey(HKEY hKey, LPCSTR lpszSubKey, LPCSTR lpszFile)
|
|
{
|
|
#ifdef DEBUG
|
|
if (fNoisyReg) {
|
|
char buf[300];
|
|
::wsprintf(buf, "MyRegLoadKey(\"%s\", \"%s\")\r\n", lpszSubKey, lpszFile);
|
|
::OutputDebugString(buf);
|
|
}
|
|
#endif
|
|
|
|
/* Since the registry doesn't support long filenames, get the short
|
|
* alias for the path. If that succeeds, we use that path, otherwise
|
|
* we just use the original one and hope it works.
|
|
*/
|
|
CHAR szShortPath[MAX_PATH+1];
|
|
if (GetShortPathName(lpszFile, szShortPath, sizeof(szShortPath)))
|
|
lpszFile = szShortPath;
|
|
|
|
return ::RegLoadKey(hKey, lpszSubKey, lpszFile);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
LONG MyRegUnLoadKey(HKEY hKey, LPCSTR lpszSubKey)
|
|
{
|
|
if (fNoisyReg) {
|
|
char buf[300];
|
|
::wsprintf(buf, "MyRegUnLoadKey(\"%s\")\r\n", lpszSubKey);
|
|
::OutputDebugString(buf);
|
|
}
|
|
return ::RegUnLoadKey(hKey, lpszSubKey);
|
|
}
|
|
#endif
|
|
|
|
|
|
LONG MyRegSaveKey(HKEY hKey, LPCSTR lpszFile, LPSECURITY_ATTRIBUTES lpsa)
|
|
{
|
|
#ifdef DEBUG
|
|
if (fNoisyReg) {
|
|
char buf[300];
|
|
::wsprintf(buf, "MyRegSaveKey(\"%s\")\r\n", lpszFile);
|
|
::OutputDebugString(buf);
|
|
}
|
|
#endif
|
|
|
|
/* Since the registry doesn't support long filenames, get the short
|
|
* alias for the path. If that succeeds, we use that path, otherwise
|
|
* we just use the original one and hope it works.
|
|
*
|
|
* GetShortPathName only works if the file exists, so we have to
|
|
* create a dummy copy first.
|
|
*/
|
|
|
|
HANDLE hTemp = ::CreateFile(lpszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hTemp == INVALID_HANDLE_VALUE)
|
|
return ::GetLastError();
|
|
::CloseHandle(hTemp);
|
|
|
|
CHAR szShortPath[MAX_PATH+1];
|
|
if (::GetShortPathName(lpszFile, szShortPath, sizeof(szShortPath)))
|
|
lpszFile = szShortPath;
|
|
|
|
return ::RegSaveKey(hKey, lpszFile, lpsa);
|
|
}
|
|
|
|
#ifndef DEBUG
|
|
#define MyRegUnLoadKey RegUnLoadKey
|
|
#endif
|
|
|
|
LONG OpenLogonKey(HKEY *phKey)
|
|
{
|
|
return ::RegOpenKey(HKEY_LOCAL_MACHINE, szLogonKey, phKey);
|
|
}
|
|
|
|
|
|
void AddBackslash(LPSTR lpPath)
|
|
{
|
|
LPCSTR lpBackslash = ::strrchrf(lpPath, '\\');
|
|
if (lpBackslash == NULL || *(lpBackslash+1) != '\0')
|
|
::strcatf(lpPath, "\\");
|
|
}
|
|
|
|
|
|
void AddBackslash(NLS_STR& nlsPath)
|
|
{
|
|
ISTR istrBackslash(nlsPath);
|
|
if (!nlsPath.strrchr(&istrBackslash, '\\') ||
|
|
*nlsPath.QueryPch(++istrBackslash) != '\0')
|
|
nlsPath += '\\';
|
|
}
|
|
|
|
|
|
void GetDirFromPath(NLS_STR& nlsTempDir, LPCSTR pszPath)
|
|
{
|
|
nlsTempDir = pszPath;
|
|
|
|
ISTR istrBackslash(nlsTempDir);
|
|
if (nlsTempDir.strrchr(&istrBackslash, '\\'))
|
|
nlsTempDir.DelSubStr(istrBackslash);
|
|
}
|
|
|
|
|
|
BOOL FileExists(LPCSTR pszPath)
|
|
{
|
|
DWORD dwAttrs = ::GetFileAttributes(pszPath);
|
|
|
|
if (dwAttrs != 0xffffffff && !(dwAttrs & FILE_ATTRIBUTE_DIRECTORY))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL DirExists(LPCSTR pszPath)
|
|
{
|
|
if (*pszPath == '\0')
|
|
return FALSE;
|
|
|
|
DWORD dwAttrs = ::GetFileAttributes(pszPath);
|
|
|
|
if (dwAttrs != 0xffffffff && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* CreateDirectoryPath attempts to create the specified directory; if the
|
|
* create attempt fails, it tries to create each element of the path in case
|
|
* any intermediate directories also don't exist.
|
|
*/
|
|
BOOL CreateDirectoryPath(LPCSTR pszPath)
|
|
{
|
|
BOOL fRet = ::CreateDirectory(pszPath, NULL);
|
|
|
|
if (fRet || (::GetLastError() != ERROR_PATH_NOT_FOUND))
|
|
return fRet;
|
|
|
|
NLS_STR nlsTemp(pszPath);
|
|
if (nlsTemp.QueryError() != ERROR_SUCCESS)
|
|
return FALSE;
|
|
|
|
LPSTR pszTemp = nlsTemp.Party();
|
|
LPSTR pszNext = pszTemp;
|
|
|
|
/* If it's a drive-based path (which it should be), skip the drive
|
|
* and first backslash -- we don't need to attempt to create the
|
|
* root directory.
|
|
*/
|
|
if (::strchrf(pszTemp, ':') != NULL) {
|
|
pszNext = ::strchrf(pszTemp, '\\');
|
|
if (pszNext != NULL)
|
|
pszNext++;
|
|
}
|
|
|
|
/* Now walk through the path creating one directory at a time. */
|
|
|
|
for (;;) {
|
|
pszNext = ::strchrf(pszNext, '\\');
|
|
if (pszNext != NULL) {
|
|
*pszNext = '\0';
|
|
}
|
|
else {
|
|
break; /* no more intermediate directories to create */
|
|
}
|
|
|
|
/* Create the intermediate directory. No error checking because we're
|
|
* not extremely performance-critical, and we can get errors if the
|
|
* directory already exists, etc. With security and other things,
|
|
* the set of benign error codes we'd have to check for could be
|
|
* large.
|
|
*/
|
|
fRet = ::CreateDirectory(pszTemp, NULL);
|
|
|
|
*pszNext = '\\';
|
|
pszNext++;
|
|
if (!*pszNext) /* ended with trailing slash? */
|
|
return fRet; /* return last result */
|
|
}
|
|
|
|
/* We should have created all the intermediate directories by now.
|
|
* Create the final path.
|
|
*/
|
|
|
|
return ::CreateDirectory(pszPath, NULL);
|
|
}
|
|
|
|
|
|
UINT SafeCopy(LPCSTR pszSrc, LPCSTR pszDest, DWORD dwAttrs)
|
|
{
|
|
NLS_STR nlsTempDir(MAX_PATH);
|
|
NLS_STR nlsTempFile(MAX_PATH);
|
|
if (!nlsTempDir || !nlsTempFile)
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
GetDirFromPath(nlsTempDir, pszDest);
|
|
|
|
if (!::GetTempFileName(nlsTempDir.QueryPch(), ::szProfilePrefix, 0,
|
|
nlsTempFile.Party()))
|
|
return ::GetLastError();
|
|
|
|
nlsTempFile.DonePartying();
|
|
|
|
if (!::CopyFile(pszSrc, nlsTempFile.QueryPch(), FALSE)) {
|
|
UINT err = ::GetLastError();
|
|
::DeleteFile(nlsTempFile.QueryPch());
|
|
return err;
|
|
}
|
|
|
|
::SetFileAttributes(pszDest, FILE_ATTRIBUTE_NORMAL);
|
|
|
|
::DeleteFile(pszDest);
|
|
|
|
// At this point, the temp file has the same attributes as the original
|
|
// (usually read-only, hidden, system). Some servers, such as NetWare
|
|
// servers, won't allow us to rename a read-only file. So we have to
|
|
// take the attributes off, rename the file, then put back whatever the
|
|
// caller wants.
|
|
::SetFileAttributes(nlsTempFile.QueryPch(), FILE_ATTRIBUTE_NORMAL);
|
|
|
|
if (!::MoveFile(nlsTempFile.QueryPch(), pszDest))
|
|
return ::GetLastError();
|
|
|
|
::SetFileAttributes(pszDest, dwAttrs);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
#ifdef LOAD_PROFILES
|
|
|
|
void SetProfileTime(LPCSTR pszLocalPath, LPCSTR pszCentralPath)
|
|
{
|
|
HANDLE hFile = ::CreateFile(pszCentralPath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ, NULL,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
FILETIME ft;
|
|
|
|
::GetFileTime(hFile, NULL, NULL, &ft);
|
|
::CloseHandle(hFile);
|
|
|
|
DWORD dwAttrs = ::GetFileAttributes(pszLocalPath);
|
|
if (dwAttrs & FILE_ATTRIBUTE_READONLY) {
|
|
::SetFileAttributes(pszLocalPath, dwAttrs & ~FILE_ATTRIBUTE_READONLY);
|
|
}
|
|
hFile = ::CreateFile(pszLocalPath, GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
::SetFileTime(hFile, NULL, NULL, &ft);
|
|
::CloseHandle(hFile);
|
|
}
|
|
if (dwAttrs & FILE_ATTRIBUTE_READONLY) {
|
|
::SetFileAttributes(pszLocalPath, dwAttrs & ~FILE_ATTRIBUTE_READONLY);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
UINT DefaultReconcile(LPCSTR pszCentralPath, LPCSTR pszLocalPath, DWORD dwFlags)
|
|
{
|
|
UINT err;
|
|
|
|
if (dwFlags & RP_LOGON) {
|
|
if (dwFlags & RP_INIFILE)
|
|
return SafeCopy(pszCentralPath, pszLocalPath, FILE_ATTRIBUTE_NORMAL);
|
|
|
|
HANDLE hFile = ::CreateFile(pszCentralPath, GENERIC_READ, FILE_SHARE_READ,
|
|
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
FILETIME ftCentral;
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
::GetFileTime(hFile, NULL, NULL, &ftCentral);
|
|
::CloseHandle(hFile);
|
|
}
|
|
else {
|
|
ftCentral.dwLowDateTime = 0; /* can't open, pretend it's really old */
|
|
ftCentral.dwHighDateTime = 0;
|
|
}
|
|
|
|
hFile = ::CreateFile(pszLocalPath, GENERIC_READ, FILE_SHARE_READ,
|
|
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
FILETIME ftLocal;
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
::GetFileTime(hFile, NULL, NULL, &ftLocal);
|
|
::CloseHandle(hFile);
|
|
}
|
|
else {
|
|
ftLocal.dwLowDateTime = 0; /* can't open, pretend it's really old */
|
|
ftLocal.dwHighDateTime = 0;
|
|
}
|
|
|
|
LPCSTR pszSrc, pszDest;
|
|
|
|
/*
|
|
* Find out which file is newer, and make that the source
|
|
* for the copy.
|
|
*/
|
|
|
|
LONG lCompare = ::CompareFileTime(&ftCentral, &ftLocal);
|
|
if (!lCompare) {
|
|
::dwProfileFlags |= PROF_CENTRALWINS;
|
|
return WN_SUCCESS; /* timestamps match, no copy to do */
|
|
}
|
|
else if (lCompare > 0) {
|
|
pszSrc = pszCentralPath;
|
|
pszDest = pszLocalPath;
|
|
::dwProfileFlags |= PROF_CENTRALWINS;
|
|
}
|
|
else {
|
|
pszSrc = pszLocalPath;
|
|
pszDest = pszCentralPath;
|
|
::dwProfileFlags &= ~PROF_CENTRALWINS;
|
|
}
|
|
|
|
err = SafeCopy(pszSrc, pszDest,
|
|
pszDest == pszCentralPath ? FILE_ATTRIBUTE_NORMAL
|
|
: attrLocalProfile);
|
|
}
|
|
else {
|
|
err = SafeCopy(pszLocalPath, pszCentralPath, FILE_ATTRIBUTE_NORMAL);
|
|
if (err == WN_SUCCESS) { /* copied back successfully */
|
|
|
|
#ifdef EXTENDED_PROFILES /* chicago doesn't special-case resident profiles */
|
|
if (dwFlags & PROF_RESIDENT) {
|
|
DeleteProfile(pszLocalPath); /* delete temp file */
|
|
}
|
|
#endif
|
|
|
|
SetProfileTime(pszLocalPath, pszCentralPath);
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
#endif /* LOAD_PROFILES */
|
|
|
|
|
|
void GetLocalProfileDirectory(NLS_STR& nlsPath)
|
|
{
|
|
::GetWindowsDirectory(nlsPath.Party(), nlsPath.QueryAllocSize());
|
|
nlsPath.DonePartying();
|
|
|
|
AddBackslash(nlsPath);
|
|
|
|
nlsPath.strcat(::szProfilesDirectory);
|
|
|
|
::CreateDirectory(nlsPath.QueryPch(), NULL);
|
|
}
|
|
|
|
|
|
HRESULT GiveUserDefaultProfile(LPCSTR lpszPath)
|
|
{
|
|
HKEY hkeyDefaultUser;
|
|
LONG err = ::RegOpenKey(HKEY_USERS, ::szDefaultUserName, &hkeyDefaultUser);
|
|
if (err == ERROR_SUCCESS) {
|
|
err = ::MyRegSaveKey(hkeyDefaultUser, lpszPath, NULL);
|
|
::RegCloseKey(hkeyDefaultUser);
|
|
}
|
|
return HRESULT_FROM_WIN32(err);
|
|
}
|
|
|
|
|
|
void ComputeLocalProfileName(LPCSTR pszUsername, NLS_STR *pnlsLocalProfile)
|
|
{
|
|
GetLocalProfileDirectory(*pnlsLocalProfile);
|
|
|
|
UINT cbPath = pnlsLocalProfile->strlen();
|
|
LPSTR lpPath = pnlsLocalProfile->Party();
|
|
LPSTR lpFilename = lpPath + cbPath;
|
|
|
|
*(lpFilename++) = '\\';
|
|
::strcpyf(lpFilename, pszUsername); /* start with whole username */
|
|
|
|
LPSTR lpFNStart = lpFilename;
|
|
|
|
UINT iFile = 0;
|
|
while (!::CreateDirectory(lpPath, NULL)) {
|
|
if (!DirExists(lpPath))
|
|
break;
|
|
|
|
/* Couldn't use whole username, start with 5 bytes of username + numbers. */
|
|
if (iFile == 0) {
|
|
::strncpyf(lpFilename, pszUsername, 5); /* copy at most 5 bytes of username */
|
|
*(lpFilename+5) = '\0'; /* force null term, just in case */
|
|
lpFilename += ::strlenf(lpFilename);
|
|
}
|
|
else if (iFile >= 4095) { /* max number expressible in 3 hex digits */
|
|
lpFilename = lpFNStart; /* start using big numbers with no uname prefix */
|
|
if ((int)iFile < 0) /* if we run out of numbers, abort */
|
|
break;
|
|
}
|
|
|
|
::wsprintf(lpFilename, "%03lx", iFile);
|
|
|
|
iFile++;
|
|
}
|
|
|
|
pnlsLocalProfile->DonePartying();
|
|
}
|
|
|
|
|
|
HRESULT CopyProfile(LPCSTR pszSrcPath, LPCSTR pszDestPath)
|
|
{
|
|
UINT err = SafeCopy(pszSrcPath, pszDestPath, attrLocalProfile);
|
|
|
|
return HRESULT_FROM_WIN32(err);
|
|
}
|
|
|
|
|
|
BOOL UseUserProfiles(void)
|
|
{
|
|
HKEY hkeyLogon;
|
|
|
|
LONG err = OpenLogonKey(&hkeyLogon);
|
|
if (err == ERROR_SUCCESS) {
|
|
DWORD fUseProfiles = 0;
|
|
DWORD cbData = sizeof(fUseProfiles);
|
|
err = ::RegQueryValueEx(hkeyLogon, (LPSTR)::szUseProfiles, NULL, NULL,
|
|
(LPBYTE)&fUseProfiles, &cbData);
|
|
::RegCloseKey(hkeyLogon);
|
|
return (err == ERROR_SUCCESS) && fUseProfiles;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void EnableProfiles(void)
|
|
{
|
|
HKEY hkeyLogon;
|
|
|
|
LONG err = OpenLogonKey(&hkeyLogon);
|
|
if (err == ERROR_SUCCESS) {
|
|
DWORD fUseProfiles = 1;
|
|
::RegSetValueEx(hkeyLogon, (LPSTR)::szUseProfiles, 0, REG_DWORD,
|
|
(LPBYTE)&fUseProfiles, sizeof(fUseProfiles));
|
|
::RegCloseKey(hkeyLogon);
|
|
}
|
|
}
|
|
|
|
|
|
struct SYNCSTATE
|
|
{
|
|
HKEY hkeyProfile;
|
|
NLS_STR *pnlsProfilePath;
|
|
NLS_STR *pnlsOtherProfilePath;
|
|
HKEY hkeyPrimary;
|
|
};
|
|
|
|
|
|
/*
|
|
* PrefixMatch determines whether a given path is equal to or a descendant
|
|
* of a given base path.
|
|
*/
|
|
BOOL PrefixMatch(LPCSTR pszPath, LPCSTR pszBasePath)
|
|
{
|
|
UINT cchBasePath = ::strlenf(pszBasePath);
|
|
if (!::strnicmpf(pszPath, pszBasePath, cchBasePath)) {
|
|
/* make sure that the base path matches the whole last component */
|
|
if ((pszPath[cchBasePath] == '\\' || pszPath[cchBasePath] == '\0'))
|
|
return TRUE;
|
|
/* check to see if the base path is a root path; if so, match */
|
|
LPCSTR pszBackslash = ::strrchrf(pszBasePath, '\\');
|
|
if (pszBackslash != NULL && *(pszBackslash+1) == '\0')
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#if 0
|
|
void ReportReconcileError(SYNCSTATE *pSyncState, TWINRESULT tr, PRECITEM pri,
|
|
PRECNODE prnSrc, PRECNODE prnDest, BOOL fSrcCentral)
|
|
{
|
|
/* If we're copying the file the "wrong" way, swap our idea of the
|
|
* source and destination. For the purposes of other profile code,
|
|
* source and destination refer to the entire profile copy direction.
|
|
* For this particular error message, they refer to the direction
|
|
* that this particular file was being copied.
|
|
*/
|
|
if (prnSrc->rnaction == RNA_COPY_TO_ME) {
|
|
PRECNODE prnTemp = prnSrc;
|
|
prnSrc = prnDest;
|
|
prnDest = prnTemp;
|
|
fSrcCentral = !fSrcCentral;
|
|
}
|
|
|
|
/* Set the error status on this key to be the destination of the copy,
|
|
* which is the copy that's now out of date because of the error and
|
|
* needs to be guarded from harm next time.
|
|
*/
|
|
pSyncState->uiRecError |= fSrcCentral ? RECERROR_LOCAL : RECERROR_CENTRAL;
|
|
|
|
pSyncState->dwFlags |= SYNCSTATE_ERROR;
|
|
|
|
if (pSyncState->dwFlags & SYNCSTATE_ERRORMSG)
|
|
return; /* error already reported */
|
|
|
|
pSyncState->dwFlags |= SYNCSTATE_ERRORMSG;
|
|
|
|
RegEntry re(::szReconcileRoot, pSyncState->hkeyProfile);
|
|
if (re.GetError() == ERROR_SUCCESS && !re.GetNumber(::szDisplayProfileErrors, TRUE))
|
|
return; /* user doesn't want to see this error message */
|
|
|
|
PCSTR pszFile;
|
|
UINT uiMainMsg;
|
|
|
|
switch (tr) {
|
|
case TR_DEST_OPEN_FAILED:
|
|
case TR_DEST_WRITE_FAILED:
|
|
uiMainMsg = IERR_ProfRecWriteDest;
|
|
pszFile = prnDest->pcszFolder;
|
|
break;
|
|
case TR_SRC_OPEN_FAILED:
|
|
case TR_SRC_READ_FAILED:
|
|
uiMainMsg = IERR_ProfRecOpenSrc;
|
|
pszFile = prnSrc->pcszFolder;
|
|
break;
|
|
default:
|
|
uiMainMsg = IERR_ProfRecCopy;
|
|
pszFile = pri->pcszName;
|
|
break;
|
|
}
|
|
|
|
if (DisplayGenericError(NULL, uiMainMsg, tr, pszFile, ::szNULL,
|
|
MB_YESNO | MB_ICONEXCLAMATION, IDS_TRMsgBase) == IDNO) {
|
|
re.SetValue(::szDisplayProfileErrors, (ULONG)FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
char szOutbuf[200];
|
|
#endif
|
|
|
|
|
|
/*
|
|
* MyReconcile is a wrapper around ReconcileItem. It needs to detect merge
|
|
* type operations and transform them into copies in the appropriate direction,
|
|
* and recognize when the sync engine wants to replace a file that the user
|
|
* really wants deleted.
|
|
*/
|
|
void MyReconcile(PRECITEM pri, SYNCSTATE *pSyncState)
|
|
{
|
|
if (pri->riaction == RIA_NOTHING)
|
|
return;
|
|
|
|
/* Because we don't have a persistent briefcase, we can't recognize when
|
|
* the user has deleted an item; the briefcase will want to replace it
|
|
* with the other version, which is not what the user wants. So we use
|
|
* the direction of the profile's copy, and if the sync engine wants to
|
|
* copy a file from the "destination" of the profile's copy to the "source"
|
|
* because the "source" doesn't exist, we recognize that as the source
|
|
* having been deleted and synchronize manually by deleting the dest.
|
|
*
|
|
* prnSrc points to the recnode for the item that's coming from the same
|
|
* side of the transaction that the more recent profile was on; prnDest
|
|
* points to the recnode for the other side.
|
|
*
|
|
* The test is complicated because we first have to figure out which of
|
|
* the two directories (nlsDir1, the local dir; or nlsDir2, the central
|
|
* dir) is the source and which the destination. Then we have to figure
|
|
* out which of the two RECNODEs we got matches which directory.
|
|
*/
|
|
PRECNODE prnSrc;
|
|
PRECNODE prnDest;
|
|
LPCSTR pszSrcBasePath;
|
|
BOOL fSrcCentral;
|
|
if (pSyncState->IsMandatory() || (pSyncState->dwFlags & PROF_CENTRALWINS)) {
|
|
pszSrcBasePath = pSyncState->nlsDir2.QueryPch();
|
|
fSrcCentral = TRUE;
|
|
}
|
|
else {
|
|
pszSrcBasePath = pSyncState->nlsDir1.QueryPch();
|
|
fSrcCentral = FALSE;
|
|
}
|
|
|
|
if (PrefixMatch(pri->prnFirst->pcszFolder, pszSrcBasePath)) {
|
|
prnSrc = pri->prnFirst;
|
|
prnDest = prnSrc->prnNext;
|
|
}
|
|
else {
|
|
prnDest = pri->prnFirst;
|
|
prnSrc = prnDest->prnNext;
|
|
}
|
|
|
|
/*
|
|
* If files of the same name exist in both places, the sync engine thinks
|
|
* they need to be merged (since we have no persistent briefcase database,
|
|
* it doesn't know that they were originally the same). The sync engine
|
|
* sets the file stamp of a copied destination file to the file stamp of
|
|
* the source file after copying. If the file stamps of two files to be
|
|
* merged are the same, we assume that the files are already up-to-date,
|
|
* and we take no reconciliation action. If the file stamps of two files
|
|
* to be merged are different, we really just want a copy, so we figure out
|
|
* which one is supposed to be definitive and transform the RECITEM and
|
|
* RECNODEs to indicate a copy instead of a merge.
|
|
*
|
|
* The definitive copy is the source for mandatory or logoff cases,
|
|
* otherwise it's the newer file.
|
|
*/
|
|
if (pri->riaction == RIA_MERGE || pri->riaction == RIA_BROKEN_MERGE) {
|
|
BOOL fCopyFromSrc;
|
|
COMPARISONRESULT cr;
|
|
|
|
if (pSyncState->IsMandatory())
|
|
fCopyFromSrc = TRUE;
|
|
else {
|
|
fCopyFromSrc = ! pSyncState->IsLogon();
|
|
|
|
if (pSyncState->CompareFileStamps(&prnSrc->fsCurrent, &prnDest->fsCurrent, &cr) == TR_SUCCESS) {
|
|
if (cr == CR_EQUAL) {
|
|
#ifdef MAXDEBUG
|
|
::OutputDebugString("Matching file stamps, no action taken\r\n");
|
|
#endif
|
|
return;
|
|
}
|
|
else if (cr==CR_FIRST_LARGER)
|
|
fCopyFromSrc = TRUE;
|
|
}
|
|
}
|
|
|
|
#ifdef MAXDEBUG
|
|
if (fCopyFromSrc)
|
|
::OutputDebugString("Broken merge, copying from src\r\n");
|
|
else
|
|
::OutputDebugString("Broken merge, copying from dest\r\n");
|
|
#endif
|
|
|
|
prnSrc->rnaction = fCopyFromSrc ? RNA_COPY_FROM_ME : RNA_COPY_TO_ME;
|
|
prnDest->rnaction = fCopyFromSrc ? RNA_COPY_TO_ME : RNA_COPY_FROM_ME;
|
|
pri->riaction = RIA_COPY;
|
|
}
|
|
|
|
/*
|
|
* If the preferred source file doesn't exist, the sync engine is trying
|
|
* to create a file to make the two trees the same, when the user/admin
|
|
* really wanted to delete it (the sync engine doesn't like deleting
|
|
* files). So we detect that case here and delete the "destination"
|
|
* to make the two trees match that way.
|
|
*
|
|
* If the last reconciliation had an error, we don't do the deletion
|
|
* if the site of the error is the current source (i.e., if we're
|
|
* about to delete the file we couldn't copy before). Instead we'll
|
|
* try the operation that the sync engine wants, since that'll be the
|
|
* copy that failed before.
|
|
*/
|
|
if (prnSrc->rnstate == RNS_DOES_NOT_EXIST &&
|
|
prnSrc->rnaction == RNA_COPY_TO_ME &&
|
|
!((pSyncState->uiRecError & RECERROR_CENTRAL) && fSrcCentral) &&
|
|
!((pSyncState->uiRecError & RECERROR_LOCAL) && !fSrcCentral)) {
|
|
if (IS_EMPTY_STRING(pri->pcszName)) {
|
|
::RemoveDirectory(prnDest->pcszFolder);
|
|
}
|
|
else {
|
|
NLS_STR nlsTemp(prnDest->pcszFolder);
|
|
AddBackslash(nlsTemp);
|
|
nlsTemp.strcat(pri->pcszName);
|
|
if (!nlsTemp.QueryError()) {
|
|
#ifdef MAXDEBUG
|
|
if (pSyncState->IsMandatory())
|
|
::OutputDebugString("Mandatory copy wrong way\r\n");
|
|
|
|
wsprintf(::szOutbuf, "Deleting 'destination' file %s\r\n", nlsTemp.QueryPch());
|
|
::OutputDebugString(::szOutbuf);
|
|
#endif
|
|
::DeleteFile(nlsTemp.QueryPch());
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#ifdef MAXDEBUG
|
|
::OutputDebugString("Calling ReconcileItem.\r\n");
|
|
#endif
|
|
|
|
TWINRESULT tr;
|
|
if ((tr=pSyncState->ReconcileItem(pri, NULL, 0, 0, NULL, NULL)) != TR_SUCCESS) {
|
|
ReportReconcileError(pSyncState, tr, pri, prnSrc, prnDest, fSrcCentral);
|
|
#ifdef MAXDEBUG
|
|
::wsprintf(::szOutbuf, "Error %d from ReconcileItem.\r\n", tr);
|
|
::OutputDebugString(::szOutbuf);
|
|
#endif
|
|
}
|
|
else if (!IS_EMPTY_STRING(pri->pcszName))
|
|
pSyncState->dwFlags |= SYNCSTATE_SOMESUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* MakePathAbsolute examines a path to see whether it is absolute or relative.
|
|
* If it is relative, it is prepended with the given base path.
|
|
*
|
|
* If the fMustBeRelative parameter is TRUE, then an error is returned if the
|
|
* path was (a) absolute and (b) not a subdirectory of the old profile directory.
|
|
*/
|
|
BOOL MakePathAbsolute(NLS_STR& nlsDir, LPCSTR lpszBasePath,
|
|
NLS_STR& nlsOldProfileDir, BOOL fMustBeRelative)
|
|
{
|
|
/* If the path starts with a special keyword, replace it. */
|
|
|
|
if (*nlsDir.QueryPch() == '*') {
|
|
return ReplaceCommonPath(nlsDir);
|
|
}
|
|
|
|
/* If the path is absolute and is relative to whatever the old profile
|
|
* directory was, transform it to a relative path. We will then make
|
|
* it absolute again, using the new base path.
|
|
*/
|
|
if (PrefixMatch(nlsDir, nlsOldProfileDir)) {
|
|
UINT cchDir = nlsDir.strlen();
|
|
LPSTR lpStart = nlsDir.Party();
|
|
::memmovef(lpStart, lpStart + nlsOldProfileDir.strlen(), cchDir - nlsOldProfileDir.strlen() + 1);
|
|
nlsDir.DonePartying();
|
|
}
|
|
else if (::strchrf(nlsDir.QueryPch(), ':') != NULL || *nlsDir.QueryPch() == '\\')
|
|
return !fMustBeRelative;
|
|
|
|
if (*lpszBasePath == '\0') {
|
|
nlsDir = lpszBasePath;
|
|
return TRUE;
|
|
}
|
|
|
|
NLS_STR nlsBasePath(lpszBasePath);
|
|
if (nlsBasePath.QueryError())
|
|
return FALSE;
|
|
AddBackslash(nlsBasePath);
|
|
|
|
ISTR istrStart(nlsDir);
|
|
nlsDir.InsertStr(nlsBasePath, istrStart);
|
|
return !nlsDir.QueryError();
|
|
}
|
|
#endif /**** 0 ****/
|
|
|
|
|
|
/*
|
|
* ReplaceCommonPath takes a relative path beginning with a special keyword
|
|
* and replaces the keyword with the corresponding real path. Currently the
|
|
* keyword supported is:
|
|
*
|
|
* *windir - replaced with the Windows (user) directory
|
|
*/
|
|
BOOL ReplaceCommonPath(NLS_STR& nlsDir)
|
|
{
|
|
NLS_STR *pnlsTemp;
|
|
ISTR istrStart(nlsDir);
|
|
ISTR istrEnd(nlsDir);
|
|
|
|
nlsDir.strchr(&istrEnd, '\\');
|
|
pnlsTemp = nlsDir.QuerySubStr(istrStart, istrEnd);
|
|
if (pnlsTemp == NULL)
|
|
return FALSE; /* out of memory, can't do anything */
|
|
|
|
BOOL fSuccess = TRUE;
|
|
if (!::stricmpf(pnlsTemp->QueryPch(), ::szWindirAlias)) {
|
|
UINT cbBuffer = pnlsTemp->QueryAllocSize();
|
|
LPSTR lpBuffer = pnlsTemp->Party();
|
|
UINT cchWindir = ::GetWindowsDirectory(lpBuffer, cbBuffer);
|
|
if (cchWindir >= cbBuffer)
|
|
*lpBuffer = '\0';
|
|
pnlsTemp->DonePartying();
|
|
if (cchWindir >= cbBuffer) {
|
|
pnlsTemp->realloc(cchWindir+1);
|
|
if (!pnlsTemp->QueryError()) {
|
|
::GetWindowsDirectory(pnlsTemp->Party(), cchWindir+1);
|
|
pnlsTemp->DonePartying();
|
|
}
|
|
else
|
|
fSuccess = FALSE;
|
|
}
|
|
if (fSuccess) {
|
|
nlsDir.ReplSubStr(*pnlsTemp, istrStart, istrEnd);
|
|
fSuccess = !nlsDir.QueryError();
|
|
}
|
|
}
|
|
delete pnlsTemp;
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetSetRegistryPath goes to the registry key and value specified by
|
|
* the current reconciliations's RegKey and RegValue settings, and
|
|
* retrieves or sets a path there.
|
|
*/
|
|
void GetSetRegistryPath(HKEY hkeyProfile, RegEntry& re, NLS_STR *pnlsPath, BOOL fSet)
|
|
{
|
|
NLS_STR nlsKey;
|
|
|
|
re.GetValue(::szReconcileRegKey, &nlsKey);
|
|
if (nlsKey.strlen() > 0) {
|
|
NLS_STR nlsValue;
|
|
re.GetValue(::szReconcileRegValue, &nlsValue);
|
|
RegEntry re2(nlsKey, hkeyProfile);
|
|
if (fSet) {
|
|
re2.SetValue(nlsValue, pnlsPath->QueryPch());
|
|
if (!nlsKey.stricmp("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders")) {
|
|
nlsKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
|
|
RegEntry reShell(nlsKey, hkeyProfile);
|
|
reShell.SetValue(nlsValue, pnlsPath->QueryPch());
|
|
}
|
|
}
|
|
else
|
|
re2.GetValue(nlsValue, pnlsPath);
|
|
}
|
|
}
|
|
|
|
|
|
/* CopyFolder calls the shell's copy engine to copy files. The source is a
|
|
* double-null-terminated list; the destination is a folder.
|
|
*/
|
|
void CopyFolder(LPBYTE pbSource, LPCSTR pszDest)
|
|
{
|
|
CHAR szDest[MAX_PATH];
|
|
|
|
::strcpyf(szDest, pszDest);
|
|
szDest[::strlenf(szDest) + 1] = '\0';
|
|
|
|
SHFILEOPSTRUCT fos;
|
|
|
|
fos.hwnd = NULL;
|
|
fos.wFunc = FO_COPY;
|
|
fos.pFrom = (LPCSTR)pbSource;
|
|
fos.pTo = szDest;
|
|
fos.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
|
|
fos.fAnyOperationsAborted = FALSE;
|
|
fos.hNameMappings = NULL;
|
|
fos.lpszProgressTitle = NULL;
|
|
|
|
g_pfnSHFileOperationA(&fos);
|
|
}
|
|
|
|
|
|
/*
|
|
* ReconcileKey performs reconciliation for a particular key in the
|
|
* ProfileReconciliation branch of the registry. It reads the config
|
|
* parameters for the reconciliation, sets up an appropriate twin in
|
|
* the temporary briefcase, and performs the reconciliation.
|
|
*/
|
|
BOOL ReconcileKey(HKEY hkeySection, LPCSTR lpszSubKey, SYNCSTATE *pSyncState)
|
|
{
|
|
#ifdef DEBUG
|
|
DWORD dwStart = ::GetTickCount();
|
|
#endif
|
|
|
|
BOOL fShouldDelete = FALSE;
|
|
|
|
RegEntry re(lpszSubKey, hkeySection);
|
|
if (re.GetError() == ERROR_SUCCESS) {
|
|
BUFFER bufSrcStrings(MAX_PATH);
|
|
NLS_STR nlsSrcPath(MAX_PATH);
|
|
NLS_STR nlsDestPath(MAX_PATH);
|
|
NLS_STR nlsName(MAX_PATH);
|
|
if (bufSrcStrings.QueryPtr() != NULL &&
|
|
nlsSrcPath.QueryError() == ERROR_SUCCESS &&
|
|
nlsDestPath.QueryError() == ERROR_SUCCESS &&
|
|
nlsName.QueryError() == ERROR_SUCCESS) {
|
|
|
|
/* Get the source path to copy. Usually it's in the profile,
|
|
* left over from the profile we cloned. If not, we take the
|
|
* default local name from the ProfileReconciliation key. If
|
|
* the path already in the registry is not relative to the cloned
|
|
* profile directory, then it's probably set by system policies
|
|
* or something, and we shouldn't touch it.
|
|
*/
|
|
if (pSyncState->pnlsOtherProfilePath != NULL) {
|
|
GetSetRegistryPath(pSyncState->hkeyProfile, re, &nlsSrcPath, FALSE);
|
|
if (nlsSrcPath.strlen() &&
|
|
!PrefixMatch(nlsSrcPath.QueryPch(), pSyncState->pnlsOtherProfilePath->QueryPch())) {
|
|
return FALSE; /* not profile-relative, nothing to do */
|
|
}
|
|
}
|
|
if (!nlsSrcPath.strlen()) {
|
|
re.GetValue(::szDefaultDir, &nlsSrcPath);
|
|
if (*nlsSrcPath.QueryPch() == '*') {
|
|
ReplaceCommonPath(nlsSrcPath);
|
|
}
|
|
}
|
|
|
|
/* Get the set of files to copy. Like NT and unlike win95, we
|
|
* want to clone the entire contents, not necessarily just the
|
|
* files listed (for example, the desktop -- we want all the
|
|
* files and subfolders, not just links). So, unless the string
|
|
* is empty, which means don't copy any content, just set the reg
|
|
* path, we change any pattern containing wildcards to *.*.
|
|
*/
|
|
re.GetValue(::szReconcileName, &nlsName);
|
|
if (nlsName.strlen()) {
|
|
if (::strchrf(nlsName.QueryPch(), '*') != NULL ||
|
|
::strchrf(nlsName.QueryPch(), '?') != NULL) {
|
|
nlsName = "*.*";
|
|
}
|
|
}
|
|
|
|
/* Get the destination path. This is generated from the new
|
|
* profile directory and the LocalFile entry in the registry.
|
|
*
|
|
* Should always do this, even if we're not going to call the
|
|
* copy engine, because we're going to write this path out to
|
|
* the registry.
|
|
*/
|
|
re.GetValue(::szLocalFile, &nlsDestPath);
|
|
ISTR istr(nlsDestPath);
|
|
nlsDestPath.InsertStr(*(pSyncState->pnlsProfilePath), istr);
|
|
|
|
/* Always create the destination path, even if we don't copy
|
|
* any files into it because the source directory doesn't exist.
|
|
*/
|
|
CreateDirectoryPath(nlsDestPath.QueryPch());
|
|
|
|
/* Make sure the source directory exists so we won't get useless
|
|
* error messages from the shell copy engine.
|
|
*/
|
|
DWORD dwAttr = GetFileAttributes(nlsSrcPath.QueryPch());
|
|
if (dwAttr != 0xffffffff && (dwAttr & FILE_ATTRIBUTE_DIRECTORY) &&
|
|
nlsName.strlen()) {
|
|
|
|
AddBackslash(nlsSrcPath);
|
|
|
|
/* Build up the double-null-terminated list of file specs to copy. */
|
|
|
|
UINT cbUsed = 0;
|
|
|
|
LPSTR lpName = nlsName.Party();
|
|
do {
|
|
LPSTR lpNext = ::strchrf(lpName, ',');
|
|
if (lpNext != NULL) {
|
|
*(lpNext++) = '\0';
|
|
}
|
|
|
|
UINT cbNeeded = nlsSrcPath.strlen() + ::strlenf(lpName) + 1;
|
|
if (bufSrcStrings.QuerySize() - cbUsed < cbNeeded) {
|
|
if (!bufSrcStrings.Resize(bufSrcStrings.QuerySize() + MAX_PATH))
|
|
return FALSE;
|
|
}
|
|
LPSTR lpDest = ((LPSTR)bufSrcStrings.QueryPtr()) + cbUsed;
|
|
::strcpyf(lpDest, nlsSrcPath.QueryPch());
|
|
lpDest += nlsSrcPath.strlen();
|
|
::strcpyf(lpDest, lpName);
|
|
cbUsed += cbNeeded;
|
|
|
|
lpName = lpNext;
|
|
} while (lpName != NULL);
|
|
|
|
*((LPSTR)bufSrcStrings.QueryPtr() + cbUsed) = '\0'; /* double null terminate */
|
|
nlsName.DonePartying();
|
|
|
|
CopyFolder((LPBYTE)bufSrcStrings.QueryPtr(), nlsDestPath.QueryPch());
|
|
}
|
|
|
|
/*
|
|
* Set a registry key to point to the new local path to this directory.
|
|
*/
|
|
GetSetRegistryPath(pSyncState->hkeyProfile, re, &nlsDestPath, TRUE);
|
|
}
|
|
}
|
|
|
|
#ifdef MAXDEBUG
|
|
::wsprintf(::szOutbuf, "ReconcileKey duration %d ms.\r\n", ::GetTickCount() - dwStart);
|
|
::OutputDebugString(::szOutbuf);
|
|
#endif
|
|
|
|
return fShouldDelete;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetMaxSubkeyLength just calls RegQueryInfoKey to get the length of the
|
|
* longest named subkey of the given key. The return value is the size
|
|
* of buffer needed to hold the longest key name, including the null
|
|
* terminator.
|
|
*/
|
|
DWORD GetMaxSubkeyLength(HKEY hKey)
|
|
{
|
|
DWORD cchClass = 0;
|
|
DWORD cSubKeys;
|
|
DWORD cchMaxSubkey;
|
|
DWORD cchMaxClass;
|
|
DWORD cValues;
|
|
DWORD cchMaxValueName;
|
|
DWORD cbMaxValueData;
|
|
DWORD cbSecurityDescriptor;
|
|
FILETIME ftLastWriteTime;
|
|
|
|
RegQueryInfoKey(hKey, NULL, &cchClass, NULL, &cSubKeys, &cchMaxSubkey,
|
|
&cchMaxClass, &cValues, &cchMaxValueName, &cbMaxValueData,
|
|
&cbSecurityDescriptor, &ftLastWriteTime);
|
|
return cchMaxSubkey + 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* ReconcileSection walks through the ProfileReconciliation key and performs
|
|
* reconciliation for each subkey. One-time keys are deleted after they are
|
|
* processed.
|
|
*/
|
|
void ReconcileSection(HKEY hkeyRoot, SYNCSTATE *pSyncState)
|
|
{
|
|
NLS_STR nlsKeyName(GetMaxSubkeyLength(hkeyRoot));
|
|
if (!nlsKeyName.QueryError()) {
|
|
DWORD iKey = 0;
|
|
|
|
for (;;) {
|
|
DWORD cchKey = nlsKeyName.QueryAllocSize();
|
|
|
|
UINT err = ::RegEnumKey(hkeyRoot, iKey, nlsKeyName.Party(), cchKey);
|
|
if (err != ERROR_SUCCESS)
|
|
break;
|
|
|
|
nlsKeyName.DonePartying();
|
|
if (ReconcileKey(hkeyRoot, nlsKeyName, pSyncState)) {
|
|
::RegDeleteKey(hkeyRoot, nlsKeyName.QueryPch());
|
|
}
|
|
else
|
|
iKey++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ReconcileFiles is called just after the user's profile and policies are
|
|
* loaded at logon, and just before the profile is unloaded at logoff. It
|
|
* performs all file type reconciliation for the user's profile, excluding
|
|
* the profile itself, of course.
|
|
*
|
|
* nlsOtherProfilePath is the path to the profile which is being cloned,
|
|
* or an empty string if the default profile is being cloned.
|
|
*/
|
|
HRESULT ReconcileFiles(HKEY hkeyProfile, NLS_STR& nlsProfilePath,
|
|
NLS_STR& nlsOtherProfilePath)
|
|
{
|
|
HRESULT hres = LoadShellEntrypoint();
|
|
if (FAILED(hres))
|
|
return hres;
|
|
|
|
if (nlsOtherProfilePath.strlen())
|
|
{
|
|
ISTR istrBackslash(nlsOtherProfilePath);
|
|
if (nlsOtherProfilePath.strrchr(&istrBackslash, '\\')) {
|
|
++istrBackslash;
|
|
nlsOtherProfilePath.DelSubStr(istrBackslash);
|
|
}
|
|
}
|
|
|
|
RegEntry re(::szReconcileRoot, hkeyProfile);
|
|
if (re.GetError() == ERROR_SUCCESS) {
|
|
SYNCSTATE s;
|
|
s.hkeyProfile = hkeyProfile;
|
|
s.pnlsProfilePath = &nlsProfilePath;
|
|
s.pnlsOtherProfilePath = (nlsOtherProfilePath.strlen() != 0) ? &nlsOtherProfilePath : NULL;
|
|
s.hkeyPrimary = NULL;
|
|
|
|
RegEntry rePrimary(::szReconcilePrimary, re.GetKey());
|
|
RegEntry reSecondary(::szReconcileSecondary, re.GetKey());
|
|
if (rePrimary.GetError() == ERROR_SUCCESS) {
|
|
ReconcileSection(rePrimary.GetKey(), &s);
|
|
|
|
if (reSecondary.GetError() == ERROR_SUCCESS) {
|
|
s.hkeyPrimary = rePrimary.GetKey();
|
|
ReconcileSection(reSecondary.GetKey(), &s);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
HRESULT DefaultReconcileKey(HKEY hkeyProfile, NLS_STR& nlsProfilePath,
|
|
LPCSTR pszKeyName, BOOL fSecondary)
|
|
{
|
|
HRESULT hres = LoadShellEntrypoint();
|
|
if (FAILED(hres))
|
|
return hres;
|
|
|
|
RegEntry re(::szReconcileRoot, hkeyProfile);
|
|
if (re.GetError() == ERROR_SUCCESS) {
|
|
SYNCSTATE s;
|
|
s.hkeyProfile = hkeyProfile;
|
|
s.pnlsProfilePath = &nlsProfilePath;
|
|
s.pnlsOtherProfilePath = NULL;
|
|
s.hkeyPrimary = NULL;
|
|
|
|
RegEntry rePrimary(::szReconcilePrimary, re.GetKey());
|
|
if (rePrimary.GetError() == ERROR_SUCCESS) {
|
|
if (fSecondary) {
|
|
RegEntry reSecondary(::szReconcileSecondary, re.GetKey());
|
|
s.hkeyPrimary = rePrimary.GetKey();
|
|
ReconcileKey(reSecondary.GetKey(), pszKeyName, &s);
|
|
}
|
|
else
|
|
ReconcileKey(rePrimary.GetKey(), pszKeyName, &s);
|
|
}
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
HRESULT DeleteProfileFiles(LPCSTR pszPath)
|
|
{
|
|
HRESULT hres = LoadShellEntrypoint();
|
|
if (FAILED(hres))
|
|
return hres;
|
|
|
|
SHFILEOPSTRUCT fos;
|
|
TCHAR szFrom[MAX_PATH];
|
|
|
|
lstrcpy(szFrom, pszPath);
|
|
|
|
/* Before we build the complete source filespec, check to see if the
|
|
* directory exists. In the case of lesser-used folders such as
|
|
* "Application Data", the default may not have ever been created.
|
|
* In that case, we have no contents to copy.
|
|
*/
|
|
DWORD dwAttr = GetFileAttributes(szFrom);
|
|
if (dwAttr == 0xffffffff || !(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
|
|
return S_OK;
|
|
|
|
AddBackslash(szFrom);
|
|
lstrcat(szFrom, TEXT("*.*"));
|
|
szFrom[lstrlen(szFrom)+1] = '\0'; /* double null terminate from string */
|
|
|
|
fos.hwnd = NULL;
|
|
fos.wFunc = FO_DELETE;
|
|
fos.pFrom = szFrom;
|
|
fos.pTo = NULL;
|
|
fos.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI;
|
|
fos.fAnyOperationsAborted = FALSE;
|
|
fos.hNameMappings = NULL;
|
|
fos.lpszProgressTitle = NULL;
|
|
|
|
g_pfnSHFileOperationA(&fos);
|
|
|
|
::RemoveDirectory(pszPath);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT DeleteProfile(LPCSTR pszName)
|
|
{
|
|
RegEntry re(::szProfileList, HKEY_LOCAL_MACHINE);
|
|
|
|
HRESULT hres;
|
|
|
|
if (re.GetError() == ERROR_SUCCESS) {
|
|
{ /* extra scope for next RegEntry */
|
|
RegEntry reUser(pszName, re.GetKey());
|
|
if (reUser.GetError() == ERROR_SUCCESS) {
|
|
NLS_STR nlsPath(MAX_PATH);
|
|
if (nlsPath.QueryError() == ERROR_SUCCESS) {
|
|
reUser.GetValue(::szProfileImagePath, &nlsPath);
|
|
if (reUser.GetError() == ERROR_SUCCESS) {
|
|
hres = DeleteProfileFiles(nlsPath.QueryPch());
|
|
}
|
|
else
|
|
hres = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
|
|
|
|
}
|
|
else
|
|
hres = HRESULT_FROM_WIN32(nlsPath.QueryError());
|
|
}
|
|
else
|
|
hres = HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER);
|
|
}
|
|
if (SUCCEEDED(hres)) {
|
|
::RegDeleteKey(re.GetKey(), pszName);
|
|
NLS_STR nlsOEMName(pszName);
|
|
if (nlsOEMName.QueryError() == ERROR_SUCCESS) {
|
|
nlsOEMName.strupr();
|
|
nlsOEMName.ToOEM();
|
|
::DeletePasswordCache(nlsOEMName.QueryPch());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
hres = E_UNEXPECTED;
|
|
|
|
return hres;
|
|
}
|