/*++

Copyright (c) 1997 Microsoft Corporation
All rights reserved.

Module Name:

    Nt.c

Abstract:

    Routines to migrate Win95 printing components to NT

Author:

    Muhunthan Sivapragasam (MuhuntS) 02-Jan-1996

Revision History:

--*/


#include "precomp.h"


//
// Data structures to gather info from the text files created on Win95 to
// store the printing configuration
//
typedef struct _DRIVER_NODE {

    struct _DRIVER_NODE    *pNext;
    DRIVER_INFO_1A          DrvInfo1;
    PPSETUP_LOCAL_DATA      pLocalData;
    BOOL                    bCantAdd;
} DRIVER_NODE, *PDRIVER_NODE;

typedef struct _PRINTER_NODE {

    struct _PRINTER_NODE   *pNext;
    PRINTER_INFO_2A         PrinterInfo2;
} PRINTER_NODE, *PPRINTER_NODE;

typedef struct _PORT_NODE {

    struct _PORT_NODE   *pNext;
    LPSTR                pPortName;
} PORT_NODE, *PPORT_NODE;

LPSTR           pszDefaultPrinterString = NULL;
PPRINTER_NODE   pDefPrinter = NULL;

//
// They kill the migration dll if it does not finish in 3 minutes.
// To prevent that I need to set this handle atleast every 3 mins
//
HANDLE          hAlive = NULL;

//
// We want to lazy load ntprint.dll and mscms.dll.
//      Note : If we link to them our DLL will not run on Win9x
//
struct {

    HMODULE                     hNtPrint;

    pfPSetupCreatePrinterDeviceInfoList         pfnCreatePrinterDeviceInfoList;
    pfPSetupDestroyPrinterDeviceInfoList        pfnDestroyPrinterDeviceInfoList;
    pfPSetupBuildDriversFromPath                pfnBuildDriversFromPath;
    pfPSetupDriverInfoFromName                  pfnDriverInfoFromName;
    pfPSetupDestroySelectedDriverInfo           pfnDestroySelectedDriverInfo;
    pfPSetupGetLocalDataField                   pfnGetLocalDataField;
    pfPSetupFreeDrvField                        pfnFreeDrvField;
    pfPSetupProcessPrinterAdded                 pfnProcessPrinterAdded;
    pfPSetupInstallICMProfiles                  pfnInstallICMProfiles;
    pfPSetupAssociateICMProfiles                pfnAssociateICMProfiles;
} LAZYLOAD_INFO;


VOID
FreePrinterNode(
    IN  PPRINTER_NODE    pPrinterNode
    )
/*++

Routine Description:
    Free the memory allocated for a PRINTER_NODE element and strings in it

Arguments:
    pPrinterNode    : Points to the structure to free memory

Return Value:
    None

--*/
{

    FreePrinterInfo2Strings(&pPrinterNode->PrinterInfo2);
    FreeMem(pPrinterNode);
}


VOID
FreePrinterNodeList(
    IN  PPRINTER_NODE   pPrinterNode
    )
/*++

Routine Description:
    Free the memory allocated for elements in the PRINTER_NODE linked list

Arguments:
    pPrinterNode    : Points to the head of linked list to free memory

Return Value:
    None

--*/
{
    PPRINTER_NODE   pNext;

    while ( pPrinterNode ) {

        pNext = pPrinterNode->pNext;
        FreePrinterNode(pPrinterNode);
        pPrinterNode = pNext;
    }
}


VOID
FreeDriverNode(
    IN  PDRIVER_NODE    pDriverNode
    )
/*++

Routine Description:
    Free the memory allocated for a DRIVER_NODE element and fields in it

Arguments:
    pDriverNode : Points to the structure to free memory

Return Value:
    None

--*/
{
    if ( pDriverNode->pLocalData )
        LAZYLOAD_INFO.pfnDestroySelectedDriverInfo(pDriverNode->pLocalData);
    FreeMem(pDriverNode->DrvInfo1.pName);
    FreeMem(pDriverNode);
}


VOID
FreeDriverNodeList(
    IN  PDRIVER_NODE   pDriverNode
    )
/*++

Routine Description:
    Free the memory allocated for elements in the PDRIVER_NODE linked list

Arguments:
    pDriverNode    : Points to the head of linked list to free memory

Return Value:
    None

--*/
{
    PDRIVER_NODE   pNext;

    while ( pDriverNode ) {

        pNext = pDriverNode->pNext;
        FreeDriverNode(pDriverNode);
        pDriverNode = pNext;
    }
}

VOID
FreePortNode(
    IN  PPORT_NODE   pPortNode
    )
/*++

Routine Description:
    Free the memory allocated for a PORT_NODE element and fields in it

Arguments:
    PPORT_NODE : Points to the structure to free memory

Return Value:
    None

--*/
{
    if (pPortNode->pPortName)
    {
        FreeMem(pPortNode->pPortName);
    }

    FreeMem(pPortNode);
}

VOID
FreePortNodeList(
    IN  PPORT_NODE   pPortNode
    )
/*++

Routine Description:
    Free the memory allocated for elements in the PORT_NODE linked list

Arguments:
    pPortNode    : Points to the head of linked list to free memory

Return Value:
    None

--*/
{
    PPORT_NODE   pNext;

    while ( pPortNode ) {

        pNext = pPortNode->pNext;
        FreePortNode(pPortNode);
        pPortNode = pNext;
    }
}

PPSETUP_LOCAL_DATA
FindLocalDataForDriver(
    IN  PDRIVER_NODE    pDriverList,
    IN  LPSTR           pszDriverName
    )
/*++

Routine Description:
    Find the local data for a given driver name from the list

Arguments:

Return Value:
    Valid PPSETUP_LOCAL_DATA on success, else NULL

--*/
{

    while ( pDriverList ) {

        if ( !_strcmpi(pszDriverName, pDriverList->DrvInfo1.pName) )
            return pDriverList->pLocalData;

        pDriverList = pDriverList->pNext;
    }

    return NULL;

}


BOOL
InitLazyLoadInfo(
    VOID
    )
/*++

Routine Description:
    Initializes the LAZYLOAD_INFO structure with LoadLibrary & GetProcAddress

Arguments:
    None

Return Value:
    TRUE on success, FALSE else

--*/
{
    if ( LAZYLOAD_INFO.hNtPrint = LoadLibraryUsingFullPathA("ntprint.dll") ) {

        (FARPROC)LAZYLOAD_INFO.pfnCreatePrinterDeviceInfoList
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupCreatePrinterDeviceInfoList");

        (FARPROC)LAZYLOAD_INFO.pfnDestroyPrinterDeviceInfoList
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupDestroyPrinterDeviceInfoList");

        (FARPROC)LAZYLOAD_INFO.pfnBuildDriversFromPath
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupBuildDriversFromPath");

        (FARPROC)LAZYLOAD_INFO.pfnDriverInfoFromName
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupDriverInfoFromName");

        (FARPROC)LAZYLOAD_INFO.pfnDestroySelectedDriverInfo
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupDestroySelectedDriverInfo");

        (FARPROC)LAZYLOAD_INFO.pfnGetLocalDataField
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupGetLocalDataField");

        (FARPROC)LAZYLOAD_INFO.pfnFreeDrvField
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupFreeDrvField");

        (FARPROC)LAZYLOAD_INFO.pfnProcessPrinterAdded
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupProcessPrinterAdded");

        (FARPROC)LAZYLOAD_INFO.pfnInstallICMProfiles
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupInstallICMProfiles");

        (FARPROC)LAZYLOAD_INFO.pfnAssociateICMProfiles
            = GetProcAddress(LAZYLOAD_INFO.hNtPrint,
                             "PSetupAssociateICMProfiles");

        if ( LAZYLOAD_INFO.pfnCreatePrinterDeviceInfoList   &&
             LAZYLOAD_INFO.pfnDestroyPrinterDeviceInfoList  &&
             LAZYLOAD_INFO.pfnBuildDriversFromPath          &&
             LAZYLOAD_INFO.pfnDriverInfoFromName            &&
             LAZYLOAD_INFO.pfnDestroySelectedDriverInfo     &&
             LAZYLOAD_INFO.pfnGetLocalDataField             &&
             LAZYLOAD_INFO.pfnFreeDrvField                  &&
             LAZYLOAD_INFO.pfnProcessPrinterAdded           &&
             LAZYLOAD_INFO.pfnInstallICMProfiles            &&
             LAZYLOAD_INFO.pfnAssociateICMProfiles ) {

#ifdef VERBOSE
    DebugMsg("Succesfully loaded Ntprint.dll");
#endif
            return TRUE;
    }

    }

    if ( LAZYLOAD_INFO.hNtPrint )
    {
        FreeLibrary(LAZYLOAD_INFO.hNtPrint);
        LAZYLOAD_INFO.hNtPrint = NULL;
    }

    return FALSE;
}


VOID
DeleteWin95Files(
    )
/*++

Routine Description:
    Read the migrate.inf and delete the files which are not needed on NT.

Arguments:
    None

Return Value:
    None

--*/
{
    HINF            hInf;
    CHAR            szPath[MAX_PATH];
    LONG            Count, Index;
    INFCONTEXT      InfContext;

    sprintf(szPath, "%s\\%s", UpgradeData.pszDir, "migrate.inf");

    hInf = SetupOpenInfFileA(szPath, NULL, INF_STYLE_WIN4, NULL);

    if ( hInf == INVALID_HANDLE_VALUE )
        return;

    //
    // We will only do the deleting part here. Files which are handled by
    // the core migration dll do not have a destination directory since we
    // are recreating the printing environment from scratch
    //
    if ( (Count = SetupGetLineCountA(hInf, "Moved")) != -1 ) {

        for ( Index = 0 ; Index < Count ; ++Index ) {

            if ( SetupGetLineByIndexA(hInf, "Moved", Index, &InfContext)    &&
                 SetupGetStringFieldA(&InfContext, 0, szPath,
                                      SIZECHARS(szPath), NULL) )
                DeleteFileA(szPath);
        }
    }

    SetupCloseInfFile(hInf);
}


BOOL
ReadWin9xPrintConfig(
    IN  OUT PDRIVER_NODE   *ppDriverNode,
    IN  OUT PPRINTER_NODE  *ppPrinterNode,
    IN  OUT PPORT_NODE  *ppPortNode
    )
/*++

Routine Description:
    Reads the Win9x printing configuration we stored in the text file
    so that printing components can be upgraded

Arguments:
    ppDriverNode            : Gives the list of drivers on Win9x
    ppPrinterNode           : Gives the list of printers on Win9x

Return Value:
    TRUE on successfully reading the config information, FALSE else

--*/
{
    BOOL                bFail = FALSE, bRet = FALSE;
    HANDLE              hFile;
    CHAR                c, szLine[2*MAX_PATH];
    DWORD               dwCount, dwIndex, dwSize;
    PDRIVER_NODE        pDrv = NULL;
    PPRINTER_NODE       pPrn;
    PPORT_NODE          pPort;

    sprintf(szLine, "%s\\%s", UpgradeData.pszDir, "print95.txt");

    hFile = CreateFileA(szLine,
                        GENERIC_READ,
                        FILE_SHARE_READ,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL |
                        FILE_FLAG_SEQUENTIAL_SCAN,
                        NULL);

    if ( hFile == INVALID_HANDLE_VALUE )
        goto Cleanup;

    dwSize = sizeof(szLine)/sizeof(szLine[0]);

    //
    // First we have the drivers
    //
    if ( My_fgets(szLine, dwSize, hFile) == NULL    ||
         strncmp(szLine, "[PrinterDrivers]", strlen("[PrinterDrivers]")) )
        goto Cleanup;

    do {

        //
        // Skip blanks
        //
        do {
            c = (CHAR) My_fgetc(hFile);
        } while ( c == ' ');

        //
        // If we hit EOF it is an error. Configuration was not written properly
        // If we hit a new-line then we are at the end of the section
        //
        if ( c == EOF )
            goto Cleanup;
        else if ( c == '\n' )
            break;  // This is the normal exit from the do loop

        if ( isdigit(c) ) {

            //
            // Put the string lengh digit back
            //
            if ( !My_ungetc(hFile) )
                goto Cleanup;
        }

        if ( !(pDrv = AllocMem(sizeof(DRIVER_NODE))) )
            goto Cleanup;

        ReadString(hFile, "", &pDrv->DrvInfo1.pName, FALSE, &bFail);

        if ( bFail ) {

            FreeDriverNode(pDrv);
            goto Cleanup;
        }

        pDrv->pNext     = *ppDriverNode;
        *ppDriverNode   = pDrv;
    } while ( !bFail );


    //
    // Now we have port info
    //

    if ( My_fgets(szLine, dwSize, hFile) == NULL    ||
         strncmp(szLine, "[Ports]", strlen("[Ports]")) )
        goto Cleanup;

    do {

        //
        // Skip blanks
        //
        do {
            c = (CHAR) My_fgetc(hFile);
        } while ( isspace(c)  && c != '\n' );

        //
        // EOF can happen if no ports and no printers, else it's an error
        //
        if ( c == EOF)
        {
            if (!pDrv)
            {
                bRet = TRUE;
            }
            goto Cleanup;
        }

        //
        // a blank line means the end of the port info section
        //
        if (c == '\n')
            break;

        if ( c != 'P' || !My_ungetc(hFile) )
            goto Cleanup;

        //
        // Create port node
        //
        if ( !(pPort = AllocMem(sizeof(PORT_NODE))) )
        {
            goto Cleanup;
        }

        ReadString(hFile, "PortName:", &pPort->pPortName, FALSE, &bFail);

        if (bFail)
        {
            FreePortNode(pPort);
            goto Cleanup;
        }

        pPort->pNext = *ppPortNode;
        *ppPortNode = pPort;

    } while ( !bFail );

    //
    // Now we have printer info
    //
    if ( My_fgets(szLine, dwSize, hFile) == NULL    ||
         strncmp(szLine, "[Printers]", strlen("[Printers]")) )
        goto Cleanup;

    do {

        c = (CHAR) My_fgetc(hFile);

        if ( c == EOF || c == '\n' )
            break;  // Normal exit

        if ( c != 'S' || !My_ungetc(hFile) )
            goto Cleanup;

        if ( !(pPrn = AllocMem(sizeof(PRINTER_NODE))) )
            goto Cleanup;

        ReadPrinterInfo2(hFile, &pPrn->PrinterInfo2, &bFail);

        if ( bFail ) {

            FreePrinterNode(pPrn);
            goto Cleanup;
        }

        pPrn->pNext = *ppPrinterNode;
        *ppPrinterNode = pPrn;
    } while ( !bFail );

    bRet = TRUE;

Cleanup:

    if ( hFile != INVALID_HANDLE_VALUE )
        CloseHandle(hFile);

    return bRet && !bFail;
}


BOOL
CheckAndAddMonitor(
    IN  LPDRIVER_INFO_6W    pDrvInfo6
    )
/*++

Routine Description:
    Check if there is a language monitor associated with the given driver
    and add it.

Arguments:

Return Value:
    TRUE on success, FALSE on failure
    None

--*/
{
    MONITOR_INFO_2W MonitorInfo2;
    LPWSTR          psz = pDrvInfo6->pMonitorName;
    LPSTR           pszStr;

    if ( psz && *psz ) {

        MonitorInfo2.pName          = psz;
        MonitorInfo2.pEnvironment   = NULL;
        MonitorInfo2.pDLLName       = (LPWSTR) (psz+wcslen(psz)+1);

        //
        // Add is succesful, or monitor is already installed?
        //
        if ( AddMonitorW(NULL, 2, (LPBYTE) &MonitorInfo2) ||
            GetLastError() == ERROR_PRINT_MONITOR_ALREADY_INSTALLED ) {

            return TRUE;
        } else {

            if ( pszStr = ErrorMsg() ) {

                LogError(LogSevError, IDS_ADDMONITOR_FAILED,
                         psz, pszStr);
                FreeMem(pszStr);
            }
            return FALSE;
        }
    }

    return TRUE;
}


VOID
KeepAliveThread(
    HANDLE  hRunning
    )
/*++

Routine Description:
    Printing migration may take a long time depending on number of printers and
    how long spooler takes to return. To inform setup that we are still alive
    I need to set a named event atleast once every 3 minutes

Arguments:
    hRunning    : When this gets closed we know processing is done

Return Value:
    None

--*/
{
    //
    // Every 30 seconds set the global event telling we are still alive
    //
    do {

        SetEvent(hAlive);
    } while ( WAIT_TIMEOUT == WaitForSingleObject(hRunning, 1000*30) );

    CloseHandle(hAlive);
    hAlive = NULL;
}


VOID
UpgradePrinterDrivers(
    IN      PDRIVER_NODE    pDriverNode,
    IN      HDEVINFO        hDevInfo,
    IN  OUT LPBOOL          pbFail
    )
/*++

Routine Description:
    Upgrades printer drivers by doing the file copy operations and calling
    AddPrinterDriver on spooler

Arguments:
    pUpgradableDrvNode      : List of drivers to upgrade
    pbFail                  : Set on an error -- no more processing needed

Return Value:
    None

--*/
{
    BOOL            bDriverToUpgrade = FALSE;
    LPWSTR          pszDriverW, pszICMW;
    LPSTR           pszDriverA, pszStr;
    PDRIVER_NODE    pCur;
    DRIVER_FIELD    DrvField;

    //
    // Set device install parameters so ntprint.dll will just queue up the
    // driver files and return without doing the copy. We will commit the
    // file queue at the end
    //
    if ( !InitFileCopyOnNT(hDevInfo) ) {

        *pbFail = TRUE;
        goto Cleanup;
    }

    //
    // Now for each printer driver call ntprint.dll to queue up the driver files
    // If it fails log an error
    //
    for ( pCur = pDriverNode ; pCur ; pCur = pCur->pNext ) {

        pszDriverA = pCur->DrvInfo1.pName;

        if ( (pszDriverW = AllocStrWFromStrA(pszDriverA))                   &&
             (pCur->pLocalData = LAZYLOAD_INFO.pfnDriverInfoFromName(
                                            hDevInfo, (LPSTR)pszDriverW))   &&
             SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES,
                                       hDevInfo,
                                       NULL) ) {

            bDriverToUpgrade = TRUE;
        } else {

            pCur->bCantAdd = TRUE;
        }

        FreeMem(pszDriverW);
    }

    if ( !bDriverToUpgrade )
        goto Cleanup;


#ifdef  VERBOSE
    DebugMsg("Starting file copy ...");
#endif

    //
    // Now commit the file queue to copy the files
    //
    if ( !CommitFileQueueToCopyFiles(hDevInfo) ) {

        *pbFail = TRUE;
        if ( pszStr = ErrorMsg() ) {

            LogError(LogSevError, IDS_DRIVERS_UPGRADE_FAILED, pszStr);
            FreeMem(pszStr);
        }
        goto Cleanup;
    }

#ifdef  VERBOSE
    DebugMsg("... files copied successfully");
#endif

    //
    // Now call spooler to install the printer driver. Also install the
    // ICM profiles associated with the printer driver
    //
    for ( pCur = pDriverNode ; pCur ; pCur = pCur->pNext ) {

        //
        // We already logged an error if bCantAdd is TRUE
        //
        if ( pCur->bCantAdd )
            continue;

        DrvField.Index          = DRV_INFO_6;
        DrvField.pDriverInfo4   = NULL;

        if ( !LAZYLOAD_INFO.pfnGetLocalDataField(pCur->pLocalData,
                                                 PlatformX86,
                                                 &DrvField)                 ||
             !CheckAndAddMonitor((LPDRIVER_INFO_6W) DrvField.pDriverInfo6)  ||
             !AddPrinterDriverW(NULL,
                                6,
                                (LPBYTE)DrvField.pDriverInfo6) ) {

            if ( pszStr = ErrorMsg() ) {

                LogError(LogSevError, IDS_ADDDRIVER_FAILED, pCur->DrvInfo1.pName, pszStr);
                FreeMem(pszStr);
            }
        }

        LAZYLOAD_INFO.pfnFreeDrvField(&DrvField);

        DrvField.Index          = ICM_FILES;
        DrvField.pszzICMFiles   = NULL;

        if ( !LAZYLOAD_INFO.pfnGetLocalDataField(pCur->pLocalData,
                                                 PlatformX86,
                                                 &DrvField) ) {

            continue;
        }

        if ( DrvField.pszzICMFiles )
            LAZYLOAD_INFO.pfnInstallICMProfiles(NULL,
                                                DrvField.pszzICMFiles);

        LAZYLOAD_INFO.pfnFreeDrvField(&DrvField);

    }

Cleanup:
    return;
}


PSECURITY_DESCRIPTOR
GetSecurityDescriptor(
    IN  LPCSTR  pszUser
    )
/*++

Routine Description:
    Get the users security

Arguments:
    pszUser     : sub key under HKEY_USER

Return Value:
    NULL on error, else a valid SECURITY_DESCRIPTOR.
    Memory is allocated in the heap and caller should free it.

--*/
{
    HKEY                    hKey = NULL;
    DWORD                   dwSize;
    PSECURITY_DESCRIPTOR    pSD = NULL;

    if ( RegOpenKeyExA(HKEY_USERS,
                       pszUser,
                       0,
                       KEY_READ|KEY_WRITE,
                       &hKey)                                       ||
         RegGetKeySecurity(hKey,
                           DACL_SECURITY_INFORMATION,
                           NULL,
                           &dwSize) != ERROR_INSUFFICIENT_BUFFER    ||
         !(pSD = (PSECURITY_DESCRIPTOR) AllocMem(dwSize))           ||
         RegGetKeySecurity(hKey,
                           DACL_SECURITY_INFORMATION,
                           pSD,
                           &dwSize) ) {

        if ( hKey )
            RegCloseKey(hKey);

        FreeMem(pSD);
        pSD = NULL;
    }

    return pSD;
}


typedef BOOL (WINAPI *P_XCV_DATA_W)(
                                    IN HANDLE  hXcv,
                                    IN PCWSTR  pszDataName,
                                    IN PBYTE   pInputData,
                                    IN DWORD   cbInputData,
                                    OUT PBYTE   pOutputData,
                                    IN DWORD   cbOutputData,
                                    OUT PDWORD  pcbOutputNeeded,
                                    OUT PDWORD  pdwStatus
                                );

BOOL
AddLocalPort(
    IN  LPSTR           pPortName
)
/*++

Routine Description:
    Adds a local port

Arguments:
    pPortName    : Name of the local port to add

Return Value:
    FALSE if a port can't be added.

--*/

{
    PRINTER_DEFAULTS    PrinterDefault = {NULL, NULL, SERVER_ACCESS_ADMINISTER};
    HANDLE  hXcvMon = NULL;
    BOOL  bReturn = FALSE;

    if (OpenPrinterA(",XcvMonitor Local Port", &hXcvMon, &PrinterDefault))
    {
        DWORD cbOutputNeeded = 0;
        DWORD Status         = NO_ERROR;
        WCHAR *pUnicodePortName = NULL;
        P_XCV_DATA_W pXcvData = NULL;
        HMODULE hWinSpool = NULL;

        //
        // if I implib-link to XcvData, loading the migrate.dll on Win9x will fail !
        //
        hWinSpool = LoadLibraryUsingFullPathA("winspool.drv");

        if (!hWinSpool)
        {
            DebugMsg("LoadLibrary on winspool.drv failed");
            goto Done;
        }

        pXcvData = (P_XCV_DATA_W) GetProcAddress(hWinSpool, "XcvDataW");

        if (!pXcvData)
        {
            DebugMsg("GetProcAddress on winspool.drv failed");
            goto Done;
        }

        pUnicodePortName = AllocStrWFromStrA(pPortName);
        if (pUnicodePortName)
        {
            bReturn = (*pXcvData)(hXcvMon,
                              L"AddPort",
                              (LPBYTE) pUnicodePortName,
                              (wcslen(pUnicodePortName) +1) * sizeof(WCHAR),
                              NULL,
                              0,
                              &cbOutputNeeded,
                              &Status
                              );

            FreeMem(pUnicodePortName);
        }

    Done:
        if (hWinSpool)
        {
            FreeLibrary(hWinSpool);
        }
        ClosePrinter(hXcvMon);
   }

   return bReturn;
}

VOID
UpgradePrinters(
    IN  PPRINTER_NODE   pPrinterNode,
    IN  PDRIVER_NODE    pDriverNode,
    IN  PPORT_NODE     *ppPortNode,
    IN  HDEVINFO        hDevInfo
    )
/*++

Routine Description:
    Upgrade printers on NT

Arguments:
    pPrinterNode    : Gives the list giving information about the printers
                      which existed on Win9x

Return Value:
    None

--*/
{
    DWORD               dwLen, dwLastError;
    LPSTR               pszStr, pszPrinterNameA;
    LPWSTR              pszPrinterNameW;
    HANDLE              hPrinter;
    DRIVER_FIELD        DrvField;
    PPSETUP_LOCAL_DATA  pLocalData;
    PPORT_NODE          pCurPort, pPrevPort = NULL;
    DWORD               dwSize;
    LPSTR               pszVendorSetupA = NULL;


    for ( ; pPrinterNode ; pPrinterNode = pPrinterNode->pNext ) {

        pszPrinterNameA = pPrinterNode->PrinterInfo2.pPrinterName;

        //
        // check whether this printer uses a non-standard local file port
        //
        for (pCurPort = *ppPortNode; pCurPort != NULL; pPrevPort = pCurPort, pCurPort = pCurPort->pNext)
        {
            if (lstrcmpi(pPrinterNode->PrinterInfo2.pPortName, pCurPort->pPortName) == 0)
            {
                //
                // Create the port
                //
                AddLocalPort(pCurPort->pPortName);

                //
                // remove it from the list
                //
                if (pCurPort == *ppPortNode)
                {
                    *ppPortNode = pCurPort->pNext;
                }
                else
                {
                    pPrevPort->pNext = pCurPort->pNext;
                }

                FreePortNode(pCurPort);

                break;
            }
        }

        hPrinter = AddPrinterA(NULL,
                               2,
                               (LPBYTE)&pPrinterNode->PrinterInfo2);

        if ( !hPrinter ) {

            dwLastError = GetLastError();

            //
            // If driver is unknown we already logged warned the user
            // If printer already exists it is ok (for Fax printer this is true)
            //
            if ( dwLastError != ERROR_UNKNOWN_PRINTER_DRIVER        &&
                 dwLastError != ERROR_INVALID_PRINTER_NAME          &&
                 dwLastError != ERROR_PRINTER_ALREADY_EXISTS        &&
                 (pszStr = ErrorMsg()) ) {

                LogError(LogSevError,
                         IDS_ADDPRINTER_FAILED,
                         pszPrinterNameA,
                         pszStr);
                FreeMem(pszStr);
            }
            continue;
        }

        pLocalData = FindLocalDataForDriver(pDriverNode,
                                            pPrinterNode->PrinterInfo2.pDriverName);
        pszPrinterNameW = AllocStrWFromStrA(pszPrinterNameA);

        if ( pLocalData && pszPrinterNameW ) {

            DrvField.Index          = ICM_FILES;
            DrvField.pszzICMFiles   = NULL;

            if ( LAZYLOAD_INFO.pfnGetLocalDataField(pLocalData,
                                                    PlatformX86,
                                                    &DrvField) ) {

                if ( DrvField.pszzICMFiles )
                    LAZYLOAD_INFO.pfnAssociateICMProfiles(
                                            (LPTSTR)pszPrinterNameW,
                                            DrvField.pszzICMFiles);

                LAZYLOAD_INFO.pfnFreeDrvField(&DrvField);
            }

            LAZYLOAD_INFO.pfnProcessPrinterAdded(hDevInfo,
                                                 pLocalData,
                                                 (LPTSTR)pszPrinterNameW,
                                                 INVALID_HANDLE_VALUE);

            dwSize = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)(pLocalData->InfInfo.pszVendorSetup), 
                                         -1, NULL, 0, NULL, NULL);
            if (dwSize > 0)
            {
                pszVendorSetupA = (LPSTR)AllocMem( dwSize );
                if (pszVendorSetupA)
                {
                    if (WideCharToMultiByte (CP_ACP, 0, (LPCWSTR)(pLocalData->InfInfo.pszVendorSetup),
                                             -1, pszVendorSetupA, dwSize, NULL, NULL))
                    {
                        WriteVendorSetupInfoInRegistry( pszVendorSetupA, pszPrinterNameA );

                    }
                    FreeMem( pszVendorSetupA );
                }
            }
        }

        //
        // Default printer will be the one with PRINTER_ATTRIBUTE_DEFAULT attribute
        // If the Win95 default printer could not be added to NT we will set the
        // first printer as the default printer
        //
        if ( (pPrinterNode->PrinterInfo2.Attributes
                                    & PRINTER_ATTRIBUTE_DEFAULT) ||
             !pDefPrinter )
            pDefPrinter = pPrinterNode;


        FreeMem(pszPrinterNameW);
        ClosePrinter(hPrinter);
    }

    if ( pDefPrinter )
        pszDefaultPrinterString = GetDefPrnString(
                                    pDefPrinter->PrinterInfo2.pPrinterName);
}


HDEVINFO
PrinterDevInfo(
    IN OUT  LPBOOL  pbFail
    )
/*++

--*/
{
    HDEVINFO                hDevInfo = INVALID_HANDLE_VALUE;

    if ( *pbFail || !InitLazyLoadInfo() ) {

        *pbFail = TRUE;
        goto Cleanup;
    }

    hDevInfo = LAZYLOAD_INFO.pfnCreatePrinterDeviceInfoList(INVALID_HANDLE_VALUE);
    if ( hDevInfo == INVALID_HANDLE_VALUE   ||
         !LAZYLOAD_INFO.pfnBuildDriversFromPath(hDevInfo,
                                                (LPSTR)L"ntprint.inf",
                                                TRUE) ) {

        *pbFail = TRUE;
        goto Cleanup;
    }

#ifdef  VERBOSE
    DebugMsg("Built the list of printer drivers from ntprint.inf");
#endif

    if ( *pbFail && hDevInfo != INVALID_HANDLE_VALUE ) {

        LAZYLOAD_INFO.pfnDestroyPrinterDeviceInfoList(hDevInfo);
        hDevInfo = INVALID_HANDLE_VALUE;
    }

Cleanup:
    return hDevInfo;
}


LONG
CALLBACK
InitializeNT(
    IN  LPCWSTR pszWorkingDir,
    IN  LPCWSTR pszSourceDir,
    LPVOID      Reserved
    )
/*++

Routine Description:
    Setup calls this to intialize us on NT side

Arguments:
    pszWorkingDir   : Gives the working directory assigned for printing
    pszSourceDir    : Source location for NT distribution files
    Reserved        : Leave it alone

Return Value:
    Win32 error code

--*/
{
    BOOL                    bFail = FALSE;
    DWORD                   dwReturn, ThreadId;
    HANDLE                  hRunning = NULL, hThread;
    HDSKSPC                 DiskSpace;
    LPSTR                   pszStr;
    HDEVINFO                hDevInfo = INVALID_HANDLE_VALUE;
    PDRIVER_NODE            pDriverNode = NULL;
    PPRINTER_NODE           pPrinterNode = NULL;
    PPORT_NODE              pPortNode = NULL;


#ifdef VERBOSE
    DebugMsg("InitializeNT : %ws, %ws", pszSourceDir, pszWorkingDir);
#endif

    UpgradeData.pszDir      = AllocStrAFromStrW(pszWorkingDir);
    UpgradeData.pszSourceW  = AllocStrW(pszSourceDir);
    UpgradeData.pszSourceA  = AllocStrAFromStrW(pszSourceDir);

    if ( !UpgradeData.pszDir        ||
         !UpgradeData.pszSourceW    ||
         !UpgradeData.pszSourceA ) {

        return GetLastError();
    }

    if ( (hAlive = OpenEventA(EVENT_MODIFY_STATE, FALSE, "MigDllAlive"))    &&
         (hRunning = CreateEventA(NULL, FALSE, FALSE, NULL))                &&
         (hThread = CreateThread(NULL, 0,
                                 (LPTHREAD_START_ROUTINE)KeepAliveThread,
                                 hRunning,
                                 0, &ThreadId)) )
        CloseHandle(hThread);

    SetupOpenLog(FALSE);

    DeleteWin95Files();

    if ( !ReadWin9xPrintConfig(&pDriverNode, &pPrinterNode, &pPortNode) ) {

        bFail = TRUE;
        DebugMsg("Unable to read Windows 9x printing configuration");
        goto Cleanup;
    }

#ifdef  VERBOSE
    DebugMsg("Succesfully read Windows 9x printing configuration");
#endif

    //
    // If no printers or drivers found nothing to do
    //
    if ( !pDriverNode && !pPrinterNode )
        goto Cleanup;

    if ( (hDevInfo = PrinterDevInfo(&bFail)) == INVALID_HANDLE_VALUE )
        goto Cleanup;

    UpgradePrinterDrivers(pDriverNode, hDevInfo, &bFail);

    UpgradePrinters(pPrinterNode, pDriverNode, &pPortNode, hDevInfo);

    MakeACopyOfMigrateDll( UpgradeData.pszDir );

Cleanup:

    SetupCloseLog();

    if ( bFail && (pszStr = ErrorMsg()) ) {

        DebugMsg("Printing migration failed. %s", pszStr);
        FreeMem(pszStr);
    }

    FreePrinterNodeList(pPrinterNode);
    FreeDriverNodeList(pDriverNode);
    FreePortNodeList(pPortNode);

    if ( hDevInfo != INVALID_HANDLE_VALUE )
        LAZYLOAD_INFO.pfnDestroyPrinterDeviceInfoList(hDevInfo);

    if ( LAZYLOAD_INFO.hNtPrint )
        FreeLibrary(LAZYLOAD_INFO.hNtPrint);

    if ( bFail ) {

        if ( (dwReturn = GetLastError()) == ERROR_SUCCESS ) {

            ASSERT(dwReturn != ERROR_SUCCESS);
            dwReturn = STG_E_UNKNOWN;
        }
    } else {

        SetupNetworkPrinterUpgrade(UpgradeData.pszDir);
        dwReturn = ERROR_SUCCESS;

#ifdef VERBOSE
        DebugMsg("InitializeNT returning success");
#endif

    }

    if ( hRunning )
        CloseHandle(hRunning);

    while (hAlive)
        Sleep(100); // Check after 0.1 second for the main thread to die

    return  dwReturn;
}


DWORD
MySetDefaultPrinter(
    IN  HKEY    hUserRegKey,
    IN  LPSTR   pszDefaultPrinterString
    )
/*++

Routine Description:
    Sets the default printer for the user by writing it to the registry

Arguments:

Return Value:

--*/
{
    DWORD   dwReturn;
    HKEY    hKey = NULL;

    //
    // Create the printers key in the user hive and write DeviceOld value
    //
    dwReturn = RegCreateKeyExA(hUserRegKey,
                               "Printers",
                               0,
                               NULL,
                               0,
                               KEY_ALL_ACCESS,
                               NULL,
                               &hKey,
                               NULL);

    if ( dwReturn == ERROR_SUCCESS ) {

        dwReturn = RegSetValueExA(hKey,
                                  "DeviceOld",
                                  0,
                                  REG_SZ,
                                  (LPBYTE)pszDefaultPrinterString,
                                  (strlen(pszDefaultPrinterString) + 1)
                                            * sizeof(CHAR));

        RegCloseKey(hKey);
    }

    return dwReturn;
}


LONG
CALLBACK
MigrateUserNT(
    IN  HINF        hUnattendInf,
    IN  HKEY        hUserRegKey,
    IN  LPCWSTR     pszUserName,
        LPVOID      Reserved
    )
/*++

Routine Description:
    Migrate user settings

Arguments:

Return Value:

--*/
{
    LPSTR   pszStr;
    DWORD   dwReturn = ERROR_SUCCESS;

#ifdef  VERBOSE
        DebugMsg("Migrating settings for %ws", pszUserName);
#endif

    if ( pszDefaultPrinterString ) {

         dwReturn = MySetDefaultPrinter(hUserRegKey,
                                        pszDefaultPrinterString);

        if ( dwReturn )
            DebugMsg("MySetDefaultPrinter failed with %d", dwReturn);
    }

    if ( bDoNetPrnUpgrade ) {

        if ( ProcessNetPrnUpgradeForUser(hUserRegKey) )
            ++dwRunOnceCount;
        else {

            if ( dwReturn == ERROR_SUCCESS )
                dwReturn = GetLastError();
            DebugMsg("ProcessNetPrnUpgradeForUser failed with %d", dwReturn);
        }
    }

#ifdef  VERBOSE
    if ( dwReturn )
        DebugMsg("MigrateUserNT failed with %d", dwReturn);
    else
        DebugMsg("MigrateUserNT succesful");
#endif

    return  dwReturn;
}


LONG
CALLBACK
MigrateSystemNT(
    IN  HINF    hUnattendInf,
        LPVOID  Reserved
    )
/*++

Routine Description:
    Process system setttings for printing. All the printing setting are
    migrated in InitializeNT since we need to know the default printer for
    each user in the MigrateSystemNT call

Arguments:
    hUnattendInf    : Handle to the unattended INF

Return Value:
    Win32 error code

--*/
{
    WriteRunOnceCount();
    return ERROR_SUCCESS;
}


//
// The following are to make sure if setup changes the header file they
// first tell me (otherwise they will break build of this)
//
P_INITIALIZE_NT     pfnInitializeNT         = InitializeNT;
P_MIGRATE_USER_NT   pfnMigrateUserNt        = MigrateUserNT;
P_MIGRATE_SYSTEM_NT pfnMigrateSystemNT      = MigrateSystemNT;