/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    migdlls.c

Abstract:

    The functions in this module are used to support migration DLLs.

Author:

    Jim Schmidt (jimschm) 04-Feb-1997

Revision History:

    jimschm     23-Sep-1998 Changed to use new IPC mechanism
    jimschm     22-Apr-1998 Added USERPROFILE environment variable to MigrateUserNT
    jimschm     08-Jan-1997 Added alive event, giving certain DLLs up to 30 minutes
                            to complete their work.
    jimschm     08-Dec-1997 Added support for domains (MigrateUserNT's user name
                            param is multi-sz)

--*/

#include "pch.h"
#include "migmainp.h"

#ifndef UNICODE
#error UNICODE required
#endif


HANDLE g_AliveEvent;


BOOL
pConnectToDll (
    VOID
    );

VOID
pDisconnectFromDll (
    VOID
    );

DWORD
pRunMigrationDll (
    VOID
    );

DWORD
pCallInitializeNt (
    IN      PCTSTR WorkingDir,
    IN      PCTSTR *SourceDirArray,
    IN      PVOID Reserved,
    IN      DWORD ReservedBytes
    );

DWORD
pCallMigrateUserNt (
    IN      PCTSTR WorkingDir,
    IN      PCTSTR UnattendFile,
    IN      PCTSTR RootKey,
    IN      PCTSTR Win9xUserName,
    IN      PCTSTR UserDomain,
    IN      PCTSTR FixedUserName,
    IN      PVOID Reserved,
    IN      DWORD ReservedBytes
    );

DWORD
pCallMigrateSystemNt (
    IN      PCTSTR WorkingDir,
    IN      PCTSTR UnattendFile,
    IN      PVOID Reserved,
    IN      DWORD ReservedBytes
    );

static
VOID
pSetCwd (
    OUT     PTSTR SavedWorkDir,
    IN      PCTSTR NewWorkDir
    );


static TCHAR g_DllPath[MAX_TCHAR_PATH];
static TCHAR g_WorkingDir[MAX_TCHAR_PATH];
static TCHAR g_DllDesc[MAX_TCHAR_PATH];
static VENDORINFOW g_VendorInfo;
static TCHAR g_FixedUser[MAX_USER_NAME];
static TCHAR g_UserOnWin9x[MAX_USER_NAME];

static HINSTANCE g_hLibrary;
P_INITIALIZE_NT InitializeNT;
P_MIGRATE_USER_NT MigrateUserNT;
P_MIGRATE_SYSTEM_NT MigrateSystemNT;



VOID
pLogDllFailure (
    IN      HWND Parent,
    IN      UINT MessageId
    )

/*++

Routine Description:

  pLogDllFailure prepares arguments for the specified MessageId, and then
  displays a popup and adds a log entry.  This function gives the user
  information on what to do when the DLL fails.

Arguments:

  Parent    - Specifies the parent window handle of the popup, or NULL if no
              popup is to be displayed.
  MessageId - Specifies the message ID for the error.

Return Value:

  None.

--*/

{
    PCTSTR FixupPhone;
    PCTSTR FixupUrl;
    PCTSTR FixupInstructions;
    PCTSTR LineBreak = S_EMPTY;
    PCTSTR ArgArray[1];

    //
    // Generate fixup strings
    //

    if (g_VendorInfo.SupportNumber[0]) {
        ArgArray[0] = g_VendorInfo.SupportNumber;
        FixupPhone = ParseMessageID (MSG_MIGDLL_SUPPORT_PHONE_FIXUP, ArgArray);
        LineBreak = TEXT("\n");
    } else {
        FixupPhone = S_EMPTY;
    }

    if (g_VendorInfo.SupportUrl[0]) {
        ArgArray[0] = g_VendorInfo.SupportUrl;
        FixupUrl = ParseMessageID (MSG_MIGDLL_SUPPORT_URL_FIXUP, ArgArray);
        LineBreak = TEXT("\n");
    } else {
        FixupUrl = S_EMPTY;
    }

    if (g_VendorInfo.InstructionsToUser[0]) {
        ArgArray[0] = g_VendorInfo.InstructionsToUser;
        FixupInstructions = ParseMessageID (MSG_MIGDLL_INSTRUCTIONS_FIXUP, ArgArray);
        LineBreak = TEXT("\n");
    } else {
        FixupInstructions = S_EMPTY;
    }

    //
    // Display popup and log the error
    //
    LOG ((
        LOG_ERROR,
        (PCSTR) MessageId,
        g_DllDesc,
        g_VendorInfo.CompanyName,
        FixupPhone,
        FixupUrl,
        FixupInstructions,
        LineBreak
        ));
}


VOID
pSetCwd (
    OUT     PTSTR SavedWorkDir,
    IN      PCTSTR NewWorkDir
    )
{
    GetCurrentDirectory (MAX_TCHAR_PATH, SavedWorkDir);
    SetCurrentDirectory (NewWorkDir);
}



BOOL
pCreateEnvironment (
    PVOID *BlockPtr
    )
{
    return CreateEnvironmentBlock (BlockPtr, NULL, FALSE);
}


VOID
pSetEnvironmentBlock (
    PVOID Block
    )
{
    DEBUGMSG ((DBG_VERBOSE, "Block: %s", Block));
}


DWORD
ProcessMigrationDLLs (
    DWORD Request
    )
{
    MEMDB_ENUM e;
    DWORD rc;
    DWORD Ticks = 0;

    if (Request == REQUEST_QUERYTICKS) {
        if (MemDbEnumItems (&e, MEMDB_CATEGORY_MIGRATION_DLL)) {
            do {
                Ticks += TICKS_MIGRATION_DLL;
            } while (MemDbEnumNextValue (&e));
        }

        return Ticks;
    }

#ifdef PRERELEASE

    if (g_ConfigOptions.DiffMode) {
        TakeSnapShot();
    }

#endif

    if (MemDbEnumItems (&e, MEMDB_CATEGORY_MIGRATION_DLL)) {

        do {
            //
            // Retrieve DLL location and settings
            //

            // Obtain the DLL name and working directory
            if (!MemDbGetEndpointValueEx (
                    MEMDB_CATEGORY_MIGRATION_DLL,
                    e.szName,
                    MEMDB_FIELD_DLL,
                    g_DllPath)
                ) {
                LOG ((LOG_ERROR, "DLL path for %s is not listed in memdb; DLL not processed", e.szName));
                continue;   // not expected
            }

            // Obtain the working directory
            if (!MemDbGetEndpointValueEx (
                    MEMDB_CATEGORY_MIGRATION_DLL,
                    e.szName,
                    MEMDB_FIELD_WD,
                    g_WorkingDir)
                ) {
                LOG ((LOG_ERROR, "Working Directory for %s is not listed in memdb; DLL not processed", e.szName));
                continue;   // not expected
            }

            // Obtain a description
            if (!MemDbGetEndpointValueEx (
                    MEMDB_CATEGORY_MIGRATION_DLL,
                    e.szName,
                    MEMDB_FIELD_DESC,
                    g_DllDesc
                    )) {

                StringCopy (g_DllDesc, GetString (MSG_DEFAULT_MIGDLL_DESC));
            }

            ZeroMemory (&g_VendorInfo, sizeof (g_VendorInfo));

            MemDbGetEndpointValueEx (
                MEMDB_CATEGORY_MIGRATION_DLL,
                e.szName,
                MEMDB_FIELD_COMPANY_NAME,
                g_VendorInfo.CompanyName
                );

            MemDbGetEndpointValueEx (
                MEMDB_CATEGORY_MIGRATION_DLL,
                e.szName,
                MEMDB_FIELD_SUPPORT_PHONE,
                g_VendorInfo.SupportNumber
                );

            MemDbGetEndpointValueEx (
                MEMDB_CATEGORY_MIGRATION_DLL,
                e.szName,
                MEMDB_FIELD_SUPPORT_URL,
                g_VendorInfo.SupportUrl
                );

            MemDbGetEndpointValueEx (
                MEMDB_CATEGORY_MIGRATION_DLL,
                e.szName,
                MEMDB_FIELD_SUPPORT_INSTRUCTIONS,
                g_VendorInfo.InstructionsToUser
                );

            //
            // Establish connection with migisol.exe
            //

            if (!pConnectToDll()) {
                continue;
            }

            //
            // Tell migisol.exe to load migration DLL and call NT functions
            //

            rc = pRunMigrationDll();

            //
            // If not success, return a setup failure
            //

            if (rc != ERROR_SUCCESS) {
                SetLastError (rc);
                pLogDllFailure (g_ParentWnd, MSG_MIGDLL_ERROR);
            }

            //
            // Disconnect from migisol.exe and kill the potentially
            // stalled process
            //

            pDisconnectFromDll();

            TickProgressBarDelta (TICKS_MIGRATION_DLL);

        } while (MemDbEnumNextValue (&e));

    }  /* if */

#ifdef PRERELEASE

    if (g_ConfigOptions.DiffMode) {
        CHAR szMigdllDifPath[] = "c:\\migdll.dif";
        if (ISPC98()) {
            szMigdllDifPath[0] = (CHAR)g_System32Dir[0];
        }
        GenerateDiffOutputA (szMigdllDifPath, NULL, TRUE);
    }

#endif

    return ERROR_SUCCESS;
} /* ProcessMigrationDLLs */


BOOL
pConnectToDll (
    VOID
    )
{
    BOOL b = TRUE;
    TCHAR MigIsolPath[MAX_TCHAR_PATH];

    g_AliveEvent = CreateEvent (NULL, FALSE, FALSE, TEXT("MigDllAlive"));
    DEBUGMSG_IF ((!g_AliveEvent, DBG_WHOOPS, "Could not create MigDllAlive event"));

    if (!g_ConfigOptions.TestDlls) {
        //
        // Establish IPC connection
        //

        wsprintf (MigIsolPath, TEXT("%s\\%s"), g_System32Dir, S_MIGISOL_EXE);

        b = OpenIpc (FALSE, MigIsolPath, g_DllPath, g_System32Dir);

        if (!b) {
            LOG ((LOG_WARNING, "Can't establish IPC connection for %s, wd=%s", g_DllPath, g_System32Dir));
            pLogDllFailure (g_ParentWnd, MSG_CREATE_PROCESS_ERROR);
        }
    } else {
        g_hLibrary = LoadLibrary (g_DllPath);

        // If it fails, assume the DLL does not want to be loaded
        if (!g_hLibrary) {
            LOG ((LOG_ERROR, "Cannot load %s", g_DllPath));
            return FALSE;
        }

        // Get proc addresses for NT-side functions
        InitializeNT    = (P_INITIALIZE_NT)     GetProcAddress (g_hLibrary, PLUGIN_INITIALIZE_NT);
        MigrateUserNT   = (P_MIGRATE_USER_NT)   GetProcAddress (g_hLibrary, PLUGIN_MIGRATE_USER_NT);
        MigrateSystemNT = (P_MIGRATE_SYSTEM_NT) GetProcAddress (g_hLibrary, PLUGIN_MIGRATE_SYSTEM_NT);

        if (!InitializeNT || !MigrateUserNT || !MigrateSystemNT) {
            b = FALSE;
        }
    }

    return b;
}


VOID
pDisconnectFromDll (
    VOID
    )
{
    if (g_AliveEvent) {
        CloseHandle (g_AliveEvent);
        g_AliveEvent = NULL;
    }

    if (!g_ConfigOptions.TestDlls) {
        CloseIpc();
    }
    else {
        if (g_hLibrary) {
            FreeLibrary (g_hLibrary);
            g_hLibrary = NULL;
        }
    }
}


BOOL
pGetUserFromIndex (
    DWORD Index
    )

{
    USERPOSITION up;
    DWORD rc;

    g_UserOnWin9x[0] = 0;

    if (Index == INDEX_DEFAULT_USER) {

        StringCopy (g_FixedUser, g_DefaultUserName);

    } else if (Index == INDEX_LOGON_PROMPT) {

        StringCopy (g_FixedUser, S_DOT_DEFAULT);

    } else if (Index == INDEX_ADMINISTRATOR) {

        StringCopy (g_FixedUser, g_AdministratorStr);

    } else {

        rc = Win95RegGetFirstUser (&up, g_FixedUser);
        if (rc != ERROR_SUCCESS) {
            LOG ((LOG_ERROR, "Get User From Index: Win95RegGetFirstUser failed"));
            return FALSE;
        }

        for (Index -= INDEX_MAX ; Win95RegHaveUser(&up) && Index > 0 ; Index--) {
            Win95RegGetNextUser (&up, g_FixedUser);
        }

        if (!Win95RegHaveUser(&up)) {
            return FALSE;
        }

        StringCopy (g_UserOnWin9x, g_FixedUser);
        GetFixedUserName (g_FixedUser);
    }

    if (!g_UserOnWin9x[0]) {
        StringCopy (g_UserOnWin9x, g_FixedUser);
    }

    return TRUE;
}

DWORD
pRunMigrationDll (
    VOID
    )
{
    DWORD rc;
    BOOL AbortThisDll;
    BOOL UnloadRegKey;
    TCHAR UnattendFile[MAX_TCHAR_PATH];
    TCHAR RootKey[MAX_REGISTRY_KEY];
    TCHAR HiveFile[MAX_TCHAR_PATH];
    DWORD Index;
    BOOL IsLogonPromptAccount;
    PCTSTR SourceDirArray[2];
    PCTSTR I386SourceDir;
    PCTSTR p;
    TCHAR Domain[MAX_USER_NAME];
    BOOL Env;
    PVOID Block;
    HKEY NewHkcu;
    LONG MapResult;

    //
    // Initialize unattend file and root key
    //

    wsprintf (UnattendFile, TEXT("%s\\system32\\$winnt$.inf"), g_WinDir);

    //
    // Call InitializeNT
    //

    if (ISPC98()) {
        I386SourceDir = JoinPaths (g_SourceDir, TEXT("NEC98"));
    } else {
        I386SourceDir = JoinPaths (g_SourceDir, TEXT("I386"));
    }

    if (!I386SourceDir) {
        return GetLastError();
    }

    SourceDirArray[0] = I386SourceDir;
    SourceDirArray[1] = NULL;
    rc = pCallInitializeNt (g_WorkingDir, SourceDirArray, NULL, 0);
    if (rc != ERROR_SUCCESS) {
        return rc;
    }

    FreePathString (I386SourceDir);

    //
    // The user loop
    //

    // For each user, call DLL's MigrateUser function
    AbortThisDll = FALSE;
    Index = 0;

    while (!AbortThisDll) {

        if (Index == INDEX_LOGON_PROMPT) {
            wsprintf (RootKey, TEXT("HKU\\%s"), S_DOT_DEFAULT);
            IsLogonPromptAccount = TRUE;
        } else {
            wsprintf (RootKey, TEXT("HKU\\%s"), S_TEMP_USER_KEY);
            IsLogonPromptAccount = FALSE;
        }

        if (!pGetUserFromIndex (Index)) {
            break;
        }

        Index++;

        //
        // If the following lookup fails, it is because the user isn't supposed to
        // migrate, or migration failed.
        //

        if (!IsLogonPromptAccount) {
            if (-1 == pSetupStringTableLookUpStringEx (
                            g_HiveTable,
                            g_FixedUser,
                            STRTAB_CASE_INSENSITIVE,
                            HiveFile,
                            sizeof (HiveFile)
                            )
                ) {
                DEBUGMSG ((
                    DBG_VERBOSE,
                    "pRunMigrationDll: pSetupStringTableLookUpStringEx could not find name of hive for user %s",
                    g_FixedUser
                    ));

                continue;
            }
        }

        //
        // Load NT user hive
        //

        UnloadRegKey = FALSE;
        Env = FALSE;
        NewHkcu = NULL;
        MapResult = 0;

        if (!AbortThisDll) {

            // Don't load .default
            if (!IsLogonPromptAccount) {

                rc = RegUnLoadKey (HKEY_USERS, S_TEMP_USER_KEY);

                if (rc != ERROR_SUCCESS) {
                    DumpOpenKeys ();
                    SetLastError (rc);
                    DEBUGMSG_IF ((rc != ERROR_INVALID_PARAMETER, DBG_ERROR, "Error unloading regkey!"));
                }

                rc = RegLoadKey (HKEY_USERS, S_TEMP_USER_KEY, HiveFile);

                if (rc != ERROR_SUCCESS) {
                    SetLastError(rc);
                    LOG ((
                        LOG_ERROR,
                        "Run Migration Dll: RegLoadKey could not load user hive for %s (%s)",
                        g_FixedUser,
                        HiveFile
                        ));

                    AbortThisDll = TRUE;
                } else {
                    UnloadRegKey = TRUE;
                }
            }
        }

        if (!AbortThisDll) {
            NewHkcu = OpenRegKeyStr (RootKey);
            if (NewHkcu) {
                MapResult = RegOverridePredefKey (HKEY_CURRENT_USER, NewHkcu);
                if (MapResult != ERROR_SUCCESS) {
                    LOG ((LOG_ERROR, "Can't override HKCU"));
                }
            }

            Env = pCreateEnvironment (&Block);
            if (Env) {
                pSetEnvironmentBlock (&Block);
                DestroyEnvironmentBlock (&Block);
            }
        }

        // Call loaded DLL's MigrateUser function
        if (!AbortThisDll) {

            if (g_DomainUserName) {
                p = _tcschr (g_DomainUserName, TEXT('\\'));
            } else {
                p = NULL;
            }

            if (p) {
                StringCopyAB (Domain, g_DomainUserName, p);
            } else {
                Domain[0] = 0;
            }

            rc = pCallMigrateUserNt (
                    g_WorkingDir,
                    UnattendFile,
                    RootKey,
                    IsLogonPromptAccount ? TEXT("") : g_UserOnWin9x,
                    Domain,
                    IsLogonPromptAccount ? TEXT("") : g_FixedUser,
                    NULL,
                    0
                    );

            if (rc != ERROR_SUCCESS) {
                AbortThisDll = TRUE;
            }
        }

        // Restore predefined key
        if (NewHkcu && MapResult == ERROR_SUCCESS) {
            MapResult = RegOverridePredefKey (HKEY_CURRENT_USER, NULL);
            if (MapResult != ERROR_SUCCESS) {
                LOG ((LOG_ERROR, "Can't restore HKCU"));
            }

            CloseRegKey (NewHkcu);
        }

        // Unload temporary key
        if (UnloadRegKey) {
            UnloadRegKey = FALSE;
            rc = RegUnLoadKey (HKEY_USERS, S_TEMP_USER_KEY);
            if (rc != ERROR_SUCCESS) {
                DumpOpenKeys ();
                SetLastError (rc);
                DEBUGMSG_IF ((rc != ERROR_INVALID_PARAMETER, DBG_ERROR, "Error unloading regkey (second case)!"));

            }
        }
    } /* while */

    //
    // System processing
    //

    Env = pCreateEnvironment (&Block);
    if (Env) {
        pSetEnvironmentBlock (&Block);
        DestroyEnvironmentBlock (&Block);
    }

    // Call MigrateSystemNT
    if (!AbortThisDll) {
        rc = pCallMigrateSystemNt (
                g_WorkingDir,
                UnattendFile,
                NULL,
                0
                );

        if (rc != ERROR_SUCCESS) {
            AbortThisDll = TRUE;
        }
    }

    return rc;
}

DWORD
pFinishHandshake (
    IN      PCTSTR FunctionName
    )
{
    DWORD TechnicalLogId;
    DWORD GuiLogId;
    DWORD rc = ERROR_SUCCESS;
    BOOL b;
    UINT Count = 40;            // about 5 minutes
    UINT AliveAllowance = 10;   // about 30 minutes

    do {
        //
        // No OUT parameters on the NT side, so we don't care
        // about the return data
        //

        b = GetIpcCommandResults (
                IPC_GET_RESULTS_NT,
                NULL,
                NULL,
                &rc,
                &TechnicalLogId,
                &GuiLogId
                );

        //
        // If error code is returned, stuff it in setupact.log
        //

        if (b && rc != ERROR_SUCCESS) {
            LOG ((
                LOG_WARNING,
                "Migration DLL %s returned %u (0x%08X) in %s",
                g_DllDesc,
                rc,
                rc,
                FunctionName
                ));
        }


        //
        // Loop if no data received, but process is alive
        //

        if (!b) {
            if (!IsIpcProcessAlive()) {
                rc = ERROR_NOACCESS;
                break;
            }

            // continue if command was not sent yet but exe is still OK
            Count--;
            if (Count == 0) {
                if (WaitForSingleObject (g_AliveEvent, 0) == WAIT_OBJECT_0) {
                    DEBUGMSG ((DBG_WARNING, "Alive allowance given to migration DLL"));

                    AliveAllowance--;
                    if (AliveAllowance) {
                        Count = 24;        // about 3 minutes
                    }
                }

                if (Count == 0) {
                    rc = ERROR_SEM_TIMEOUT;
                    break;
                }
            }
        }

    } while (!b);

    if (b) {
        //
        // Recognize log messages
        //
        if (TechnicalLogId) {
            //
            // LOG message with three args: DllDesc, DllPath, User
            //

            LOG ((
                LOG_ERROR,
                (PCSTR) TechnicalLogId,
                g_DllDesc,
                g_DllPath,
                g_FixedUser
                ));
        }
        if (GuiLogId) {
            LOG ((
                LOG_ERROR,
                (PCSTR) GuiLogId,
                g_DllDesc,
                g_DllPath,
                g_FixedUser
                ));
        }
    }

    return rc;
}


DWORD
pCallInitializeNt (
    IN      PCTSTR WorkingDir,
    IN      PCTSTR *SourceDirArray,
    IN      PVOID Reserved,
    IN      DWORD ReservedBytes
    )
{
    DWORD rc = ERROR_SUCCESS;
    GROWBUFFER GrowBuf = GROWBUF_INIT;
    INT Count;
    PBYTE BufPtr;
    PDWORD ReservedBytesPtr;
    TCHAR SavedCwd [MAX_TCHAR_PATH];

    if (!g_ConfigOptions.TestDlls) {
        __try {
            MultiSzAppend (&GrowBuf, WorkingDir);

            //
            // Prepare multi-sz directory list
            //

            for (Count = 0 ; SourceDirArray[Count] ; Count++) {
                MultiSzAppend (&GrowBuf, SourceDirArray[Count]);
            }

            MultiSzAppend (&GrowBuf, S_EMPTY);

            ReservedBytesPtr = (PDWORD) GrowBuffer (&GrowBuf, sizeof (ReservedBytes));
            *ReservedBytesPtr = ReservedBytes;

            if (ReservedBytes) {
                BufPtr = GrowBuffer (&GrowBuf, ReservedBytes);
                CopyMemory (BufPtr, Reserved, ReservedBytes);
            }

            if (!SendIpcCommand (
                    IPC_INITIALIZE,
                    GrowBuf.Buf,
                    GrowBuf.End
                    )) {

                LOG ((LOG_ERROR, "Call InitializeNT failed to send command"));
                rc = GetLastError();
                __leave;
            }

            rc = pFinishHandshake (TEXT("InitializeNT"));
            if (rc != ERROR_SUCCESS) {
                LOG ((
                    LOG_ERROR,
                    "Call InitializeNT failed to complete handshake, rc=%u",
                    rc
                    ));
            }
        }
        __finally {
            FreeGrowBuffer (&GrowBuf);
        }
    }
    else {

        pSetCwd (
            SavedCwd,       // old
            WorkingDir      // new
            );

        __try {
            //
            // Prepare multi-sz directory list
            //

            for (Count = 0 ; SourceDirArray[Count] ; Count++) {
                MultiSzAppend (&GrowBuf, SourceDirArray[Count]);
            }

            MultiSzAppend (&GrowBuf, S_EMPTY);

            rc = InitializeNT (WorkingDir, (PCTSTR) GrowBuf.Buf, Reserved);

            FreeGrowBuffer (&GrowBuf);
        }
        __finally {
            SetCurrentDirectory (SavedCwd);
        }
    }

    return rc;
}


DWORD
pCallMigrateUserNt (
    IN      PCTSTR WorkingDir,
    IN      PCTSTR UnattendFile,
    IN      PCTSTR RootKey,
    IN      PCTSTR Win9xUserName,
    IN      PCTSTR UserDomain,
    IN      PCTSTR FixedUserName,
    IN      PVOID Reserved,
    IN      DWORD ReservedBytes
    )
{
    DWORD rc = ERROR_SUCCESS;
    GROWBUFFER GrowBuf = GROWBUF_INIT;
    PDWORD ReservedBytesPtr;
    PVOID BufPtr;
    TCHAR SavedCwd [MAX_TCHAR_PATH];
    TCHAR UserBuf[MAX_USER_NAME * 3];
    PTSTR p;
    TCHAR OrgUserProfilePath[MAX_TCHAR_PATH];
    TCHAR UserProfilePath[MAX_TCHAR_PATH];

    if (FixedUserName[0]) {
        GetUserProfilePath (FixedUserName, &p);
        StackStringCopy (UserProfilePath, p);
        FreePathString (p);

        DEBUGMSG ((DBG_VERBOSE, "Profile path for %s is %s", FixedUserName, UserProfilePath));
    } else {
        UserProfilePath[0] = 0;
    }

    GetEnvironmentVariable (S_USERPROFILE, OrgUserProfilePath, MAX_TCHAR_PATH);
    SetEnvironmentVariable (S_USERPROFILE, UserProfilePath);

    if (!g_ConfigOptions.TestDlls) {
        __try {
            MultiSzAppend (&GrowBuf, UnattendFile);
            MultiSzAppend (&GrowBuf, RootKey);
            MultiSzAppend (&GrowBuf, Win9xUserName);
            MultiSzAppend (&GrowBuf, UserDomain);
            MultiSzAppend (&GrowBuf, FixedUserName);
            MultiSzAppend (&GrowBuf, UserProfilePath);

            ReservedBytesPtr = (PDWORD) GrowBuffer (&GrowBuf, sizeof (ReservedBytes));
            *ReservedBytesPtr = ReservedBytes;

            if (ReservedBytes) {
                BufPtr = GrowBuffer (&GrowBuf, ReservedBytes);
                CopyMemory (BufPtr, Reserved, ReservedBytes);
            }

            if (!SendIpcCommand (
                    IPC_MIGRATEUSER,
                    GrowBuf.Buf,
                    GrowBuf.End
                    )) {

                LOG ((LOG_ERROR, "Call MigrateUserNT failed to send command"));
                rc = GetLastError();
                __leave;
            }

            rc = pFinishHandshake (TEXT("MigrateUserNT"));
            if (rc != ERROR_SUCCESS) {
                LOG ((
                    LOG_ERROR,
                    "Call MigrateUserNT failed to complete handshake, rc=%u",
                    rc
                    ));
            }
        }
        __finally {
            FreeGrowBuffer (&GrowBuf);
        }

    } else {

        pSetCwd (
            SavedCwd,       // old
            WorkingDir      // new
            );

        __try {

            HINF UnattendHandle;
            HKEY UserRegHandle;

            UnattendHandle = InfOpenInfFile (UnattendFile);
            if (UnattendHandle == INVALID_HANDLE_VALUE) {
                __leave;
            }

            UserRegHandle = OpenRegKeyStr (RootKey);
            if (!UserRegHandle) {
                rc = GetLastError();
                InfCloseInfFile (UnattendHandle);
                __leave;
            }

            //
            // Transfer user, user domain and fixed name to a buffer
            //

            if (Win9xUserName) {
                StringCopy (UserBuf, Win9xUserName);
            } else {
                UserBuf[0] = 0;
            }

            p = GetEndOfString (UserBuf) + 1;

            if (UserDomain) {
                StringCopy (p, UserDomain);
            } else {
                p[0] = 0;
            }

            p = GetEndOfString (p) + 1;

            if (UserDomain) {
                StringCopy (p, FixedUserName);
            } else {
                p[0] = 0;
            }

            //
            // Call the entry point
            //

            rc = MigrateUserNT (
                        UnattendHandle,
                        UserRegHandle,
                        UserBuf[0] ? UserBuf : NULL,
                        Reserved
                        );

            CloseRegKey (UserRegHandle);
            InfCloseInfFile (UnattendHandle);
        }
        __finally {
            SetCurrentDirectory (SavedCwd);
        }
    }

    SetEnvironmentVariable (S_USERPROFILE, OrgUserProfilePath);

    return rc;
}


DWORD
pCallMigrateSystemNt (
    IN      PCTSTR WorkingDir,
    IN      PCTSTR UnattendFile,
    IN      PVOID Reserved,
    IN      DWORD ReservedBytes
    )
{
    DWORD rc = ERROR_SUCCESS;
    GROWBUFFER GrowBuf = GROWBUF_INIT;
    PDWORD ReservedBytesPtr;
    PVOID BufPtr;
    TCHAR SavedCwd [MAX_TCHAR_PATH];

    if (!g_ConfigOptions.TestDlls) {
        __try {
            MultiSzAppend (&GrowBuf, UnattendFile);

            ReservedBytesPtr = (PDWORD) GrowBuffer (&GrowBuf, sizeof (ReservedBytes));
            *ReservedBytesPtr = ReservedBytes;

            if (ReservedBytes) {
                BufPtr = GrowBuffer (&GrowBuf, ReservedBytes);
                CopyMemory (BufPtr, Reserved, ReservedBytes);
            }

            if (!SendIpcCommand (IPC_MIGRATESYSTEM, GrowBuf.Buf, GrowBuf.End)) {
                LOG ((LOG_ERROR, "Call MigrateSystemNT failed to send command"));
                rc = GetLastError();
                __leave;
            }

            rc = pFinishHandshake (TEXT("MigrateSystemNT"));
            if (rc != ERROR_SUCCESS) {
                LOG ((
                    LOG_ERROR,
                    "Call MigrateSystemNT failed to complete handshake, rc=%u",
                    rc
                    ));
            }
        }
        __finally {
            FreeGrowBuffer (&GrowBuf);
        }
    }
    else {
        pSetCwd (
            SavedCwd,       // old
            WorkingDir      // new
            );

        __try {
            HINF UnattendHandle;

            UnattendHandle = InfOpenInfFile (UnattendFile);
            if (UnattendHandle == INVALID_HANDLE_VALUE) {
                rc = GetLastError();
                __leave;
            }

            rc = MigrateSystemNT (UnattendHandle, Reserved);

            InfCloseInfFile (UnattendHandle);
        }
        __finally {
            SetCurrentDirectory (SavedCwd);
        }
    }

    return rc;
 }