mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1430 lines
35 KiB
1430 lines
35 KiB
/*++
|
|
|
|
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;
|