1319 lines
35 KiB
C
1319 lines
35 KiB
C
/*++
|
|
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
userenum.c
|
|
|
|
Abstract:
|
|
|
|
This module implements a pair of user enumeration functions to consolidate
|
|
general-case and special-case processing of users. The caller does not
|
|
need to know how a machine's user profiles are configured because the
|
|
code here abstracts the details.
|
|
|
|
The caller gets:
|
|
|
|
- Each user name, .default for the logon prompt, and Default User for the
|
|
NT default user account
|
|
- The Win9x user.dat location for each user, including the default user
|
|
- The Win9x profile directory, or All Users for the default user
|
|
- The symbolic NT profile directory
|
|
- The account type (normal, administrator and/or default)
|
|
- Indication that the account registry is valid
|
|
- Indication that the account is the current logged-on user or last logged-on
|
|
user
|
|
|
|
Routines:
|
|
|
|
EnumFirstUser - Begins the user enumeration
|
|
|
|
EnumNextUser - Continues the user enumeration
|
|
|
|
EnumUserAbort - Cleans up an enumeration that did not complete
|
|
|
|
Author:
|
|
|
|
Jim Schmidt (jimschm) 23-Jul-1997
|
|
|
|
Revision History:
|
|
|
|
Jim Schmidt (jimschm) 08-Sep-1998 Changed to a better state machine to
|
|
clean up the evolved complexity
|
|
Jim Schmidt (jimschm) 09-Jun-1998 Revisions for dynamic user profile dir
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
#include "cmn9xp.h"
|
|
|
|
|
|
#define DBG_USERENUM "UserEnum"
|
|
|
|
#define UE_INITIALIZED 0x0001
|
|
#define UE_SF_COLLISIONS 0x0002
|
|
|
|
static DWORD g_UserEnumFlags = 0;
|
|
|
|
|
|
VOID
|
|
pMoveAndRenameProfiles (
|
|
IN PCTSTR ProfileList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pReportNonMigrateableUserAccounts adds a message to the incompatibility
|
|
report when a condition that makes user migration impossible (except current user)
|
|
is detected
|
|
|
|
Arguments:
|
|
|
|
ProfileList - Specifies the list of non-migrated user profile paths (multisz)
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
MULTISZ_ENUM msze;
|
|
PTSTR p, append;
|
|
TCHAR sourceDir[MAX_TCHAR_PATH];
|
|
TREE_ENUM e;
|
|
TCHAR newDest[MAX_TCHAR_PATH];
|
|
PTSTR profiles;
|
|
TCHAR tempFile[MAX_TCHAR_PATH];
|
|
|
|
profiles = JoinPaths (g_WinDir, TEXT("Profiles"));
|
|
|
|
if (EnumFirstMultiSz (&msze, ProfileList)) {
|
|
do {
|
|
//
|
|
// remove user.dat from the path
|
|
//
|
|
StackStringCopy (sourceDir, msze.CurrentString);
|
|
p = _tcsrchr (sourceDir, TEXT('\\'));
|
|
if (!p) {
|
|
MYASSERT (FALSE);
|
|
continue;
|
|
}
|
|
*p = 0;
|
|
MYASSERT (StringIMatch (p + 1, TEXT("user.dat")));
|
|
p = _tcsrchr (sourceDir, TEXT('\\'));
|
|
if (!p) {
|
|
MYASSERT (FALSE);
|
|
continue;
|
|
}
|
|
//
|
|
// append Win9x OS name to the target directory name
|
|
//
|
|
append = newDest + wsprintf (newDest, TEXT("%s%s.%s"), g_ProfileDirNt, p, g_Win95Name);
|
|
if (CanSetOperation (sourceDir, OPERATION_FILE_MOVE_EXTERNAL)) {
|
|
MarkFileForMoveExternal (sourceDir, newDest);
|
|
}
|
|
*append = TEXT('\\');
|
|
append++;
|
|
//
|
|
// now enumerate and move all the files
|
|
//
|
|
if (StringIPrefix (sourceDir, profiles) && EnumFirstFileInTree (&e, sourceDir, NULL, TRUE)) {
|
|
do {
|
|
StringCopy (append, e.SubPath);
|
|
if (!e.Directory) {
|
|
//
|
|
// remove old operation and set a new one
|
|
// with the updated final dest
|
|
//
|
|
if (CanSetOperation (e.FullPath, OPERATION_TEMP_PATH)) {
|
|
ComputeTemporaryPath (e.FullPath, NULL, NULL, g_TempDir, tempFile);
|
|
MarkFileForTemporaryMoveEx (e.FullPath, newDest, tempFile, TRUE);
|
|
}
|
|
} else {
|
|
if (CanSetOperation (e.FullPath, OPERATION_FILE_MOVE_EXTERNAL)) {
|
|
MarkFileForMoveExternal (e.FullPath, newDest);
|
|
}
|
|
}
|
|
|
|
} while (EnumNextFileInTree (&e));
|
|
}
|
|
} while (EnumNextMultiSz (&msze));
|
|
}
|
|
|
|
FreePathString (profiles);
|
|
}
|
|
|
|
|
|
VOID
|
|
pReportNonMigrateableUserAccounts (
|
|
IN PCTSTR UserList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pReportNonMigrateableUserAccounts adds a message to the incompatibility
|
|
report when a condition that makes user migration impossible (except current user)
|
|
is detected
|
|
|
|
Arguments:
|
|
|
|
UserList - Specifies the list of non-migrated users (multisz)
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
|
|
{
|
|
PCTSTR MsgGroup = NULL;
|
|
PCTSTR RootGroup = NULL;
|
|
PCTSTR SubGroup = NULL;
|
|
PCTSTR Message = NULL;
|
|
PCTSTR ArgArray[2];
|
|
MULTISZ_ENUM msze;
|
|
|
|
__try {
|
|
RootGroup = GetStringResource (MSG_LOSTSETTINGS_ROOT);
|
|
SubGroup = GetStringResource (MSG_SHARED_USER_ACCOUNTS);
|
|
if (!RootGroup || !SubGroup) {
|
|
MYASSERT (FALSE);
|
|
__leave;
|
|
}
|
|
|
|
//
|
|
// Build "Settings That Will Not Be Upgraded\Shared User Accounts"
|
|
//
|
|
MsgGroup = JoinPaths (RootGroup, SubGroup);
|
|
//
|
|
// Send message to report
|
|
//
|
|
ArgArray[0] = g_Win95Name;
|
|
ArgArray[1] = g_ProfileDirNt;
|
|
Message = ParseMessageID (MSG_SHARED_USER_ACCOUNTS_MESSAGE, ArgArray);
|
|
if (Message) {
|
|
MsgMgr_ObjectMsg_Add (TEXT("*SharedUserAccounts"), MsgGroup, Message);
|
|
}
|
|
|
|
if (EnumFirstMultiSz (&msze, UserList)) {
|
|
do {
|
|
//
|
|
// remove all associated messages from the report
|
|
//
|
|
HandleObject (msze.CurrentString, TEXT("UserName"));
|
|
} while (EnumNextMultiSz (&msze));
|
|
}
|
|
}
|
|
__finally {
|
|
//
|
|
// Clean up
|
|
//
|
|
FreeStringResource (Message);
|
|
FreeStringResource (RootGroup);
|
|
FreeStringResource (SubGroup);
|
|
FreePathString (MsgGroup);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pCheckShellFoldersCollision (
|
|
VOID
|
|
)
|
|
{
|
|
USERENUM e;
|
|
INFSTRUCT is = INITINFSTRUCT_GROWBUFFER;
|
|
PCTSTR idShellFolder;
|
|
PCTSTR path;
|
|
GROWBUFFER gb = GROWBUF_INIT;
|
|
GROWBUFFER users = GROWBUF_INIT;
|
|
GROWBUFFER profilesWin9x = GROWBUF_INIT;
|
|
MULTISZ_ENUM msze;
|
|
TCHAR key[MEMDB_MAX];
|
|
BOOL collisions = FALSE;
|
|
|
|
if (EnumFirstUser (&e, 0)) {
|
|
|
|
if (!e.CommonProfilesEnabled) {
|
|
|
|
if (InfFindFirstLine (g_Win95UpgInf, S_PROFILES_SF_COLLISIONS, NULL, &is)) {
|
|
do {
|
|
idShellFolder = InfGetStringField (&is, 1);
|
|
if (idShellFolder && *idShellFolder) {
|
|
MultiSzAppend (&gb, idShellFolder);
|
|
}
|
|
} while (InfFindNextLine (&is));
|
|
InfCleanUpInfStruct (&is);
|
|
}
|
|
|
|
do {
|
|
if (!EnumFirstMultiSz (&msze, (PCTSTR)gb.Buf)) {
|
|
break;
|
|
}
|
|
if (!(e.AccountType & (LOGON_PROMPT | DEFAULT_USER | INVALID_ACCOUNT))) {
|
|
if (!(e.AccountType & CURRENT_USER)) {
|
|
MultiSzAppend (&users, e.UserName);
|
|
MultiSzAppend (&profilesWin9x, e.UserDatPath);
|
|
}
|
|
if (!collisions) {
|
|
do {
|
|
path = ShellFolderGetPath (&e, msze.CurrentString);
|
|
if (path) {
|
|
MemDbBuildKey (key, MEMDB_CATEGORY_PROFILES_SF_COLLISIONS, msze.CurrentString, path, NULL);
|
|
if (MemDbGetValue (key, NULL)) {
|
|
//
|
|
// this shell folder path is shared between multiple users
|
|
//
|
|
|
|
LOG ((
|
|
LOG_INFORMATION,
|
|
"User %s shares path %s with another user for %s",
|
|
e.UserName,
|
|
path,
|
|
msze.CurrentString
|
|
));
|
|
|
|
collisions = TRUE;
|
|
break;
|
|
}
|
|
|
|
LOG ((
|
|
LOG_INFORMATION,
|
|
"User %s uses path %s for %s",
|
|
e.UserName,
|
|
path,
|
|
msze.CurrentString
|
|
));
|
|
|
|
MemDbSetValue (key, 0);
|
|
FreePathString (path);
|
|
}
|
|
} while (EnumNextMultiSz (&msze));
|
|
}
|
|
}
|
|
} while (EnumNextUser (&e));
|
|
}
|
|
|
|
EnumUserAbort (&e);
|
|
}
|
|
|
|
if (collisions) {
|
|
//
|
|
// show this in the upgrade report
|
|
//
|
|
LOG ((
|
|
LOG_WARNING,
|
|
"Some user profiles share special shell folders; only the current account will be migrated"
|
|
));
|
|
MYASSERT (users.Buf && profilesWin9x.Buf);
|
|
pReportNonMigrateableUserAccounts (users.Buf);
|
|
//
|
|
// rename their profile from <Profiles9x>\<username> to <ProfilesNT>\<username>.<Win9xOSName>
|
|
//
|
|
pMoveAndRenameProfiles (profilesWin9x.Buf);
|
|
//
|
|
// set the global flag
|
|
//
|
|
g_UserEnumFlags |= UE_SF_COLLISIONS;
|
|
}
|
|
|
|
FreeGrowBuffer (&gb);
|
|
FreeGrowBuffer (&users);
|
|
MemDbDeleteTree (MEMDB_CATEGORY_PROFILES_SF_COLLISIONS);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pUserMigrationDisabled (
|
|
IN PUSERENUM EnumPtr
|
|
)
|
|
{
|
|
return (g_UserEnumFlags & UE_SF_COLLISIONS) != 0 &&
|
|
!(EnumPtr->AccountType & (CURRENT_USER | DEFAULT_USER));
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsProfileDirInUse (
|
|
IN PVOID ProfileDirTable,
|
|
IN PCTSTR ProfileDirName,
|
|
IN PCTSTR ActualUserName
|
|
)
|
|
{
|
|
LONG rc;
|
|
|
|
if (StringIMatch (ProfileDirName, ActualUserName)) {
|
|
return FALSE;
|
|
}
|
|
|
|
rc = pSetupStringTableLookUpString (
|
|
ProfileDirTable,
|
|
(PTSTR) ProfileDirName,
|
|
STRTAB_CASE_INSENSITIVE
|
|
);
|
|
|
|
if (rc != -1) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (StringIMatch (ProfileDirName, g_AdministratorStr)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (StringIMatch (ProfileDirName, S_DEFAULT_USER)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (StringIMatch (ProfileDirName, S_ALL_USERS)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (StringIMatch (ProfileDirName, S_LOCALSERVICE_USER)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (StringIMatch (ProfileDirName, S_NETWORKSERVICE_USER)) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
pIsAdministratorUserName (
|
|
IN PCTSTR UserName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines if the specified name is the administrator account or not.
|
|
|
|
Arguments:
|
|
|
|
UserName - Specifies the user name (without a domain name)
|
|
|
|
Return Value:
|
|
|
|
TRUE if the specified string is the same as "Administrator"
|
|
FALSE if the specified string is not "Administrator"
|
|
|
|
--*/
|
|
|
|
{
|
|
return StringIMatch (UserName, g_AdministratorStr);
|
|
}
|
|
|
|
|
|
VOID
|
|
pPrepareStructForNextUser (
|
|
IN OUT PUSERENUM EnumPtr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pPrepareStructForNextUser initializes the user-specific members of the enum
|
|
struct.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies the previous enum state, receives the initialized enum
|
|
state.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Init flags
|
|
//
|
|
|
|
EnumPtr->DefaultUserHive = FALSE;
|
|
EnumPtr->CreateAccountOnly = FALSE;
|
|
|
|
//
|
|
// Init names
|
|
//
|
|
|
|
EnumPtr->UserName[0] = 0;
|
|
EnumPtr->FixedUserName[0] = 0;
|
|
|
|
// AdminUserName is the true Win9x user name of the future Administrator
|
|
EnumPtr->AdminUserName[0] = 0;
|
|
EnumPtr->FixedAdminUserName[0] = 0;
|
|
|
|
//
|
|
// Init paths
|
|
//
|
|
|
|
EnumPtr->UserDatPath[0] = 0;
|
|
EnumPtr->ProfileDirName[0] = 0;
|
|
EnumPtr->OrgProfilePath[0] = 0;
|
|
EnumPtr->NewProfilePath[0] = 0;
|
|
|
|
//
|
|
// Init values
|
|
//
|
|
|
|
EnumPtr->AccountType = 0;
|
|
|
|
//
|
|
// Init reg value
|
|
//
|
|
|
|
if (EnumPtr->UserRegKey) {
|
|
CloseRegKey (EnumPtr->UserRegKey);
|
|
EnumPtr->UserRegKey = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pPrepareStructForReturn (
|
|
IN OUT PUSERENUM EnumPtr,
|
|
IN ACCOUNTTYPE AccountType,
|
|
IN USERENUM_STATE NextState
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pPrepareStructForReturn performs processing common to any type of
|
|
enumeration. This includes:
|
|
|
|
- Identifying an actual Win9x user named Administrator (a special case)
|
|
- Finding the fixed name (i.e., NT-compatible name) for the user account
|
|
- Mapping in the hive into the registry
|
|
- Computing the full path to the profile directory, as well as the profile
|
|
dir name (i.e., joeuser.001). The profile dir is encoded as >username
|
|
because we don't know the true location until GUI mode.
|
|
- Setting flags for current user or last logged on user
|
|
|
|
The caller must set UserName and DefaultUserHive prior to calling
|
|
this function (as well as all enumeration-wide members such as current
|
|
user name).
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies the partially completed enum state. Receives the
|
|
complete enum state.
|
|
AccountType - Specifies the account type being returned.
|
|
NextState - Specifies the next state for the state machine, used when the
|
|
caller calls EnumNextUser.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD rc;
|
|
PTSTR p;
|
|
TCHAR TempDir[MAX_TCHAR_PATH];
|
|
UINT TempDirSeq;
|
|
HKEY key;
|
|
HKEY userKey;
|
|
PCTSTR data;
|
|
|
|
//
|
|
// Fill in state machine members
|
|
//
|
|
|
|
EnumPtr->AccountType = AccountType;
|
|
EnumPtr->State = UE_STATE_RETURN;
|
|
EnumPtr->NextState = NextState;
|
|
|
|
//
|
|
// Check if named user is also Administrator
|
|
//
|
|
|
|
if (AccountType & NAMED_USER) {
|
|
if (pIsAdministratorUserName (EnumPtr->UserName)) {
|
|
EnumPtr->AccountType |= ADMINISTRATOR;
|
|
StringCopy (EnumPtr->AdminUserName, EnumPtr->UserName);
|
|
}
|
|
|
|
//
|
|
// If this is a named user but there is no hive, use the default hive
|
|
//
|
|
|
|
key = OpenRegKeyStr (S_HKLM_PROFILELIST_KEY);
|
|
|
|
if (key) {
|
|
userKey = OpenRegKey (key, EnumPtr->UserName);
|
|
|
|
if (userKey) {
|
|
data = GetRegValueString (userKey, S_PROFILEIMAGEPATH);
|
|
if (data) {
|
|
FreeMem (data);
|
|
} else {
|
|
EnumPtr->DefaultUserHive = TRUE;
|
|
}
|
|
|
|
CloseRegKey (userKey);
|
|
}
|
|
|
|
CloseRegKey (key);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Generate fixed user names
|
|
//
|
|
|
|
if (EnumPtr->EnableNameFix) {
|
|
GetUpgradeUserName (EnumPtr->UserName, EnumPtr->FixedUserName);
|
|
|
|
//
|
|
// If this is Administrator, and it is coming from DefaultUser, then
|
|
// UserName is empty and we must use the name Administrator for the
|
|
// account (or Owner on PER skus).
|
|
//
|
|
|
|
if ((EnumPtr->AccountType & ADMINISTRATOR) &&
|
|
EnumPtr->FixedUserName[0] == 0
|
|
) {
|
|
StringCopy (EnumPtr->FixedUserName, g_AdministratorStr);
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_FIXEDUSERNAMES,
|
|
EnumPtr->UserName, // empty string
|
|
EnumPtr->FixedUserName, // Administrator or Owner
|
|
NULL,
|
|
0,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
if (EnumPtr->AdminUserName[0]) {
|
|
GetUpgradeUserName (EnumPtr->AdminUserName, EnumPtr->FixedAdminUserName);
|
|
}
|
|
|
|
} else {
|
|
StringCopy (EnumPtr->FixedUserName, EnumPtr->UserName);
|
|
StringCopy (EnumPtr->FixedAdminUserName, EnumPtr->AdminUserName);
|
|
}
|
|
|
|
//
|
|
// Map in the hive
|
|
//
|
|
|
|
if (!EnumPtr->DoNotMapHive) {
|
|
if (EnumPtr->DefaultUserHive) {
|
|
// The default hive
|
|
rc = Win95RegSetCurrentUser (
|
|
NULL, // User Pos -- NULL for default
|
|
NULL, // (IN OPTIONAL) Substitute %WinDir%
|
|
EnumPtr->UserDatPath // OUT
|
|
);
|
|
} else {
|
|
// A non-default hive
|
|
rc = Win95RegSetCurrentUser (
|
|
&EnumPtr->pos,
|
|
NULL, // (IN OPTIONAL) Substitute %WinDir%
|
|
EnumPtr->UserDatPath
|
|
);
|
|
}
|
|
} else {
|
|
if (!EnumPtr->pos.UseProfile || EnumPtr->DefaultUserHive) {
|
|
|
|
StringCopy (EnumPtr->UserDatPath, g_WinDir);
|
|
StringCat (EnumPtr->UserDatPath, TEXT("\\user.dat"));
|
|
rc = ERROR_SUCCESS;
|
|
|
|
} else {
|
|
//
|
|
// Call FindAndLoadHive to get the user.dat path,
|
|
// but don't actually load the hive.
|
|
//
|
|
rc = FindAndLoadHive (
|
|
&EnumPtr->pos,
|
|
NULL, // CallerSuppliedWinDir
|
|
NULL, // UserDatFromCaller
|
|
EnumPtr->UserDatPath,
|
|
FALSE // MapTheHive flag
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Resolve profile directory
|
|
//
|
|
|
|
if (rc != ERROR_SUCCESS) {
|
|
EnumPtr->AccountType |= INVALID_ACCOUNT;
|
|
|
|
DEBUGMSG ((
|
|
DBG_WARNING,
|
|
"pUpdateEnumStruct: Win95RegSetCurrentUser could not set user %s (rc=%u)",
|
|
EnumPtr->UserName,
|
|
rc
|
|
));
|
|
|
|
} else {
|
|
|
|
if (!EnumPtr->DoNotMapHive) {
|
|
//
|
|
// User's hive is valid, open the registry
|
|
//
|
|
|
|
MYASSERT (g_UserKey && *g_UserKey);
|
|
if (!g_UserKey) {
|
|
g_UserKey = S_EMPTY;
|
|
}
|
|
|
|
EnumPtr->UserRegKey = OpenRegKeyStr (g_UserKey);
|
|
|
|
if (!EnumPtr->UserRegKey) {
|
|
LOG ((LOG_ERROR, "Cannot open %s", g_UserKey));
|
|
EnumPtr->State = EnumPtr->NextState;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Save original profile directory
|
|
//
|
|
|
|
StringCopy (EnumPtr->OrgProfilePath, EnumPtr->UserDatPath);
|
|
p = _tcsrchr (EnumPtr->OrgProfilePath, TEXT('\\'));
|
|
if (p) {
|
|
*p = 0;
|
|
}
|
|
|
|
//
|
|
// now build profile directory and path
|
|
//
|
|
|
|
if (EnumPtr->AccountType & ADMINISTRATOR) {
|
|
//
|
|
// Special case: We know the NT Profile directory name for Administrator.
|
|
// It can't come from Win9x.
|
|
//
|
|
StringCopy (EnumPtr->ProfileDirName, g_AdministratorStr);
|
|
|
|
} else {
|
|
//
|
|
// General case: The profile directory is in the user.dat path
|
|
//
|
|
|
|
if (!StringMatch (EnumPtr->UserName, EnumPtr->FixedUserName)) {
|
|
//
|
|
// Use fixed user name if one exists
|
|
//
|
|
|
|
StringCopy (EnumPtr->ProfileDirName, EnumPtr->FixedUserName);
|
|
|
|
} else if (StringIMatchCharCount (EnumPtr->UserDatPath, g_ProfileDirWack, g_ProfileDirWackChars)) {
|
|
//
|
|
// If per-user profile directory exists, extract the user name from it
|
|
//
|
|
|
|
_tcssafecpy (
|
|
EnumPtr->ProfileDirName,
|
|
CharCountToPointer (EnumPtr->UserDatPath, g_ProfileDirWackChars),
|
|
MAX_TCHAR_PATH
|
|
);
|
|
|
|
p = _tcsrchr (EnumPtr->ProfileDirName, TEXT('\\'));
|
|
if (p) {
|
|
*p = 0;
|
|
|
|
//
|
|
// Unusual case: The directory name we extracted collides with
|
|
// another user, Default User, All Users or Administrator.
|
|
//
|
|
|
|
StringCopy (TempDir, EnumPtr->ProfileDirName);
|
|
TempDirSeq = 1;
|
|
|
|
p = _tcschr (TempDir, TEXT('.'));
|
|
if (p) {
|
|
*p = 0;
|
|
}
|
|
|
|
while (pIsProfileDirInUse (
|
|
EnumPtr->ProfileDirTable,
|
|
EnumPtr->ProfileDirName,
|
|
EnumPtr->UserName
|
|
)) {
|
|
wsprintf (EnumPtr->ProfileDirName, TEXT("%s.%03u"), TempDir, TempDirSeq);
|
|
TempDirSeq++;
|
|
|
|
if (TempDirSeq == 1000) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// Unusual case: No sub dir after profile directory -- copy user name
|
|
//
|
|
|
|
_tcssafecpy (EnumPtr->ProfileDirName, EnumPtr->UserName, MAX_TCHAR_PATH);
|
|
}
|
|
|
|
//
|
|
// Add to table for collision detection
|
|
//
|
|
|
|
pSetupStringTableAddString (
|
|
EnumPtr->ProfileDirTable,
|
|
EnumPtr->ProfileDirName,
|
|
STRTAB_CASE_INSENSITIVE
|
|
);
|
|
|
|
} else {
|
|
//
|
|
// No per-user profile directory -- copy user name
|
|
//
|
|
|
|
_tcssafecpy (EnumPtr->ProfileDirName, EnumPtr->UserName, MAX_TCHAR_PATH);
|
|
}
|
|
|
|
//
|
|
// If profile directory is empty, change to All Users
|
|
//
|
|
|
|
if (!EnumPtr->ProfileDirName[0]) {
|
|
StringCopy (EnumPtr->ProfileDirName, S_ALL_USERS);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Generate full path to new profile dir
|
|
//
|
|
|
|
if (*EnumPtr->FixedUserName) {
|
|
wsprintf (
|
|
EnumPtr->NewProfilePath,
|
|
TEXT(">%s"),
|
|
EnumPtr->FixedUserName
|
|
);
|
|
} else {
|
|
wsprintf (
|
|
EnumPtr->NewProfilePath,
|
|
TEXT(">%s"),
|
|
EnumPtr->ProfileDirName
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set flag for last logged on user and current user
|
|
//
|
|
|
|
if (StringIMatch (EnumPtr->UserName, EnumPtr->LastLoggedOnUserName)) {
|
|
|
|
EnumPtr->AccountType |= LAST_LOGGED_ON_USER;
|
|
|
|
}
|
|
|
|
if (StringIMatch (EnumPtr->UserName, EnumPtr->CurrentUserName)) {
|
|
|
|
EnumPtr->AccountType |= CURRENT_USER;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
pUserEnumWorker (
|
|
IN OUT PUSERENUM EnumPtr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
pUserEnumWorker implements a state machine that enumerates:
|
|
|
|
1. All named users
|
|
2. If no named users, the last logged on user (if one exists)
|
|
3. The Administrator account (if not already enumerated in step 1 or 2)
|
|
4. The logon prompt account
|
|
5. The default user (if enabled)
|
|
|
|
The caller can filter out the create-only Administrator account
|
|
and the logon prompt account.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Specifies the previous enumeration state (or an initialized
|
|
enumeration struct). Recieves the next enumerated user.
|
|
|
|
Return Value:
|
|
|
|
TRUE if another user was enumerated, or FALSE if no additional users are
|
|
left.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD rc;
|
|
HKEY Key;
|
|
PCTSTR Data;
|
|
DWORD Size;
|
|
|
|
while (EnumPtr->State != UE_STATE_END) {
|
|
|
|
switch (EnumPtr->State) {
|
|
|
|
case UE_STATE_INIT:
|
|
//
|
|
// Init table for collisions...
|
|
//
|
|
|
|
EnumPtr->ProfileDirTable = pSetupStringTableInitialize();
|
|
if (!EnumPtr->ProfileDirTable) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Get data static to the enumeration:
|
|
// - Last logged on user
|
|
// - Current user
|
|
//
|
|
|
|
Key = OpenRegKeyStr (TEXT("HKLM\\Network\\Logon"));
|
|
if (Key) {
|
|
Data = GetRegValueString (Key, TEXT("username"));
|
|
|
|
if (Data) {
|
|
_tcssafecpy (EnumPtr->LastLoggedOnUserName, Data, MAX_USER_NAME);
|
|
MemFree (g_hHeap, 0, Data);
|
|
}
|
|
|
|
CloseRegKey (Key);
|
|
}
|
|
|
|
Size = MAX_USER_NAME;
|
|
if (!GetUserName (EnumPtr->CurrentUserName, &Size)) {
|
|
EnumPtr->CurrentUserName[0] = 0;
|
|
}
|
|
|
|
//
|
|
// Check for an account named Administrator
|
|
//
|
|
|
|
rc = Win95RegGetFirstUser (&EnumPtr->pos, EnumPtr->UserName);
|
|
if (rc != ERROR_SUCCESS) {
|
|
EnumPtr->State = UE_STATE_CLEANUP;
|
|
LOG ((LOG_ERROR, "Could not enumerate first user. Error: %u.", rc));
|
|
break;
|
|
}
|
|
|
|
while (Win95RegHaveUser (&EnumPtr->pos)) {
|
|
//
|
|
// Add user name to profile dir table
|
|
//
|
|
|
|
pSetupStringTableAddString (
|
|
EnumPtr->ProfileDirTable,
|
|
EnumPtr->UserName,
|
|
STRTAB_CASE_INSENSITIVE
|
|
);
|
|
|
|
//
|
|
// If this is Administrator, set flag
|
|
//
|
|
|
|
if (pIsAdministratorUserName (EnumPtr->UserName)) {
|
|
EnumPtr->RealAdminAccountExists = TRUE;
|
|
}
|
|
|
|
Win95RegGetNextUser (&EnumPtr->pos, EnumPtr->UserName);
|
|
}
|
|
|
|
EnumPtr->State = UE_STATE_BEGIN_WIN95REG;
|
|
break;
|
|
|
|
case UE_STATE_BEGIN_WIN95REG:
|
|
|
|
pPrepareStructForNextUser (EnumPtr);
|
|
|
|
Win95RegGetFirstUser (&EnumPtr->pos, EnumPtr->UserName);
|
|
|
|
EnumPtr->CommonProfilesEnabled = !EnumPtr->pos.UseProfile;
|
|
|
|
DEBUGMSG_IF ((EnumPtr->CommonProfilesEnabled, DBG_USERENUM, "Common profiles enabled"));
|
|
DEBUGMSG_IF ((!EnumPtr->CommonProfilesEnabled, DBG_USERENUM, "Common profiles disabled"));
|
|
|
|
EnumPtr->DefaultUserHive = EnumPtr->CommonProfilesEnabled;
|
|
|
|
if (Win95RegHaveUser (&EnumPtr->pos)) {
|
|
//
|
|
// We have a user.
|
|
//
|
|
|
|
pPrepareStructForReturn (EnumPtr, NAMED_USER, UE_STATE_NEXT_WIN95REG);
|
|
|
|
} else {
|
|
//
|
|
// We have NO users.
|
|
//
|
|
|
|
EnumPtr->State = UE_STATE_NO_USERS;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case UE_STATE_NO_USERS:
|
|
//
|
|
// There are two cases, either there is no logon prompt, or the
|
|
// user hit escape and decided to upgrade.
|
|
//
|
|
|
|
pPrepareStructForNextUser (EnumPtr);
|
|
|
|
//
|
|
// No users means no hives.
|
|
//
|
|
|
|
EnumPtr->DefaultUserHive = TRUE;
|
|
|
|
if (EnumPtr->LastLoggedOnUserName[0]) {
|
|
|
|
DEBUGMSG ((DBG_USERENUM, "User is not logged on now, but was logged on before."));
|
|
StringCopy (EnumPtr->UserName, EnumPtr->LastLoggedOnUserName);
|
|
|
|
if (pIsAdministratorUserName (EnumPtr->UserName)) {
|
|
pPrepareStructForReturn (EnumPtr, NAMED_USER, UE_STATE_LOGON_PROMPT);
|
|
} else {
|
|
pPrepareStructForReturn (EnumPtr, NAMED_USER, UE_STATE_ADMINISTRATOR);
|
|
}
|
|
|
|
} else {
|
|
DEBUGMSG ((DBG_USERENUM, "Machine only has a default user."));
|
|
|
|
EnumPtr->UserName[0] = 0;
|
|
pPrepareStructForReturn (EnumPtr, DEFAULT_USER|ADMINISTRATOR|LOGON_PROMPT|CURRENT_USER, UE_STATE_LOGON_PROMPT);
|
|
}
|
|
|
|
break;
|
|
|
|
case UE_STATE_NEXT_WIN95REG:
|
|
|
|
pPrepareStructForNextUser (EnumPtr);
|
|
|
|
rc = Win95RegGetNextUser (&EnumPtr->pos, EnumPtr->UserName);
|
|
if (rc != ERROR_SUCCESS) {
|
|
EnumPtr->State = UE_STATE_CLEANUP;
|
|
LOG ((LOG_ERROR, "Could not enumerate next user. Error: %u.", rc));
|
|
break;
|
|
}
|
|
|
|
if (Win95RegHaveUser (&EnumPtr->pos)) {
|
|
//
|
|
// We have another user
|
|
//
|
|
|
|
pPrepareStructForReturn (EnumPtr, NAMED_USER, UE_STATE_NEXT_WIN95REG);
|
|
|
|
} else {
|
|
|
|
EnumPtr->State = UE_STATE_ADMINISTRATOR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case UE_STATE_ADMINISTRATOR:
|
|
//
|
|
// Until now, there has been no user named Administrator.
|
|
// Enumerate this account only if the caller wants it.
|
|
//
|
|
|
|
if (EnumPtr->WantCreateOnly) {
|
|
|
|
pPrepareStructForNextUser (EnumPtr);
|
|
|
|
//
|
|
// Enumerate Win95Reg until Administrator is found
|
|
//
|
|
|
|
Win95RegGetFirstUser (&EnumPtr->pos, EnumPtr->UserName);
|
|
|
|
while (Win95RegHaveUser (&EnumPtr->pos)) {
|
|
if (pIsAdministratorUserName (EnumPtr->UserName)) {
|
|
break;
|
|
}
|
|
|
|
Win95RegGetNextUser (&EnumPtr->pos, EnumPtr->UserName);
|
|
}
|
|
|
|
if (Win95RegHaveUser (&EnumPtr->pos)) {
|
|
//
|
|
// If an account named Administrator exists, then
|
|
// don't enumerate it again.
|
|
//
|
|
|
|
EnumPtr->State = UE_STATE_LOGON_PROMPT;
|
|
break;
|
|
|
|
}
|
|
|
|
//
|
|
// We used to set all data from the current user. We don't do that any more.
|
|
// Administrator data is pretty much similar with default user.
|
|
//
|
|
EnumPtr->DefaultUserHive = TRUE;
|
|
StringCopy (EnumPtr->UserName, g_AdministratorStr);
|
|
StringCopy (EnumPtr->AdminUserName, g_AdministratorStr);
|
|
EnumPtr->CreateAccountOnly = TRUE;
|
|
|
|
//
|
|
// Now return the user, or default user if the current user is not
|
|
// named.
|
|
//
|
|
|
|
pPrepareStructForReturn (EnumPtr, ADMINISTRATOR, UE_STATE_LOGON_PROMPT);
|
|
|
|
} else {
|
|
EnumPtr->State = UE_STATE_LOGON_PROMPT;
|
|
}
|
|
|
|
break;
|
|
|
|
case UE_STATE_LOGON_PROMPT:
|
|
if (EnumPtr->WantLogonPrompt) {
|
|
pPrepareStructForNextUser (EnumPtr);
|
|
|
|
EnumPtr->DefaultUserHive = TRUE;
|
|
StringCopy (EnumPtr->UserName, S_DOT_DEFAULT);
|
|
|
|
pPrepareStructForReturn (EnumPtr, LOGON_PROMPT, UE_STATE_DEFAULT_USER);
|
|
|
|
} else {
|
|
EnumPtr->State = UE_STATE_DEFAULT_USER;
|
|
}
|
|
|
|
break;
|
|
|
|
case UE_STATE_DEFAULT_USER:
|
|
if (g_ConfigOptions.MigrateDefaultUser) {
|
|
pPrepareStructForNextUser (EnumPtr);
|
|
|
|
EnumPtr->DefaultUserHive = TRUE;
|
|
StringCopy (EnumPtr->UserName, S_DEFAULT_USER);
|
|
|
|
pPrepareStructForReturn (EnumPtr, DEFAULT_USER, UE_STATE_CLEANUP);
|
|
|
|
} else {
|
|
EnumPtr->State = UE_STATE_CLEANUP;
|
|
}
|
|
|
|
break;
|
|
|
|
case UE_STATE_RETURN:
|
|
EnumPtr->State = EnumPtr->NextState;
|
|
//
|
|
// check if certain conditions are met that would prevent
|
|
// migration of certain user accounts (like ones that share some shell folders)
|
|
//
|
|
if (pUserMigrationDisabled (EnumPtr)) {
|
|
EnumPtr->AccountType |= INVALID_ACCOUNT;
|
|
}
|
|
return TRUE;
|
|
|
|
case UE_STATE_CLEANUP:
|
|
if (EnumPtr->UserRegKey) {
|
|
CloseRegKey (EnumPtr->UserRegKey);
|
|
}
|
|
|
|
if (EnumPtr->ProfileDirTable) {
|
|
pSetupStringTableDestroy (EnumPtr->ProfileDirTable);
|
|
}
|
|
|
|
ZeroMemory (EnumPtr, sizeof (USERENUM));
|
|
EnumPtr->State = UE_STATE_END;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
VOID
|
|
pFixBrokenNetLogonRegistry (
|
|
VOID
|
|
)
|
|
{
|
|
HKEY key;
|
|
PCTSTR data;
|
|
TCHAR userName[256];
|
|
DWORD size;
|
|
|
|
key = OpenRegKeyStr (TEXT("HKLM\\Network\\Logon"));
|
|
if (key) {
|
|
data = GetRegValueString (key, TEXT("UserName"));
|
|
if (!data) {
|
|
size = ARRAYSIZE(userName);
|
|
if (GetUserName (userName, &size) && (size > 0)) {
|
|
|
|
LOG ((
|
|
LOG_WARNING,
|
|
"HKLM\\Network\\Logon [UserName] is missing; filling it in with %s",
|
|
userName
|
|
));
|
|
|
|
RegSetValueEx (
|
|
key,
|
|
TEXT("UserName"),
|
|
0,
|
|
REG_SZ,
|
|
(PBYTE) (userName),
|
|
SizeOfString (userName)
|
|
);
|
|
}
|
|
|
|
} else {
|
|
FreeMem (data);
|
|
}
|
|
|
|
CloseRegKey (key);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
pRecordUserDoingTheUpgrade (
|
|
VOID
|
|
)
|
|
{
|
|
TCHAR userName[256];
|
|
DWORD size;
|
|
|
|
userName[0] = 0;
|
|
size = ARRAYSIZE(userName);
|
|
GetUserName (userName, &size);
|
|
|
|
if (userName[0] == 0) {
|
|
StringCopy (userName, g_AdministratorStr);
|
|
}
|
|
|
|
MemDbSetValueEx (
|
|
MEMDB_CATEGORY_ADMINISTRATOR_INFO,
|
|
MEMDB_ITEM_AI_USER_DOING_MIG,
|
|
NULL, // no field
|
|
userName,
|
|
0,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
|
|
BOOL
|
|
EnumFirstUser (
|
|
OUT PUSERENUM EnumPtr,
|
|
IN DWORD Flags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
EnumFirstUser begins the enumeration of all users to be migrated. This
|
|
includes all named users (even ones with broken registries), the
|
|
Administrator account, the logon prompt account, and the Default User
|
|
account.
|
|
|
|
Arguments:
|
|
|
|
EnumPtr - Receives the enumerated user attributes
|
|
Flags - Specifies any of the following flags:
|
|
|
|
ENUMUSER_ENABLE_NAME_FIX - Caller wants the fixed versions of
|
|
the user names
|
|
ENUMUSER_DO_NOT_MAP_HIVE - Caller wants fast enumeration (no
|
|
registry hive map)
|
|
ENUMUSER_ADMINISTRATOR_ALWAYS - Caller wants the Administrator
|
|
account, even if a user is
|
|
not named Administrator
|
|
ENUMUSER_INCLUDE_LOGON_PROMPT - Caller wants the logon prompt
|
|
account
|
|
|
|
|
|
Return Value:
|
|
|
|
TRUE if a user was enumerated, or FALSE if not.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// first initialize the enumeration engine
|
|
//
|
|
if (!(g_UserEnumFlags & UE_INITIALIZED)) {
|
|
g_UserEnumFlags |= UE_INITIALIZED;
|
|
pFixBrokenNetLogonRegistry ();
|
|
pRecordUserDoingTheUpgrade ();
|
|
pCheckShellFoldersCollision ();
|
|
}
|
|
//
|
|
// Init enum struct
|
|
//
|
|
|
|
ZeroMemory (EnumPtr, sizeof (USERENUM));
|
|
|
|
//
|
|
// Separate the flags
|
|
//
|
|
|
|
EnumPtr->EnableNameFix = (Flags & ENUMUSER_ENABLE_NAME_FIX) != 0;
|
|
EnumPtr->DoNotMapHive = (Flags & ENUMUSER_DO_NOT_MAP_HIVE) != 0;
|
|
EnumPtr->WantCreateOnly = (Flags & ENUMUSER_ADMINISTRATOR_ALWAYS) != 0;
|
|
EnumPtr->WantLogonPrompt = (Flags & ENUMUSER_NO_LOGON_PROMPT) == 0;
|
|
|
|
//
|
|
// Init the state machine
|
|
//
|
|
|
|
EnumPtr->State = UE_STATE_INIT;
|
|
|
|
//
|
|
// Enum the next item
|
|
//
|
|
|
|
return pUserEnumWorker (EnumPtr);
|
|
}
|
|
|
|
|
|
BOOL
|
|
EnumNextUser (
|
|
IN OUT PUSERENUM EnumPtr
|
|
)
|
|
{
|
|
return pUserEnumWorker (EnumPtr);
|
|
}
|
|
|
|
|
|
VOID
|
|
EnumUserAbort (
|
|
IN OUT PUSERENUM EnumPtr
|
|
)
|
|
{
|
|
if (EnumPtr->State != UE_STATE_END &&
|
|
EnumPtr->State != UE_STATE_INIT
|
|
) {
|
|
EnumPtr->State = UE_STATE_CLEANUP;
|
|
pUserEnumWorker (EnumPtr);
|
|
}
|
|
}
|