816 lines
18 KiB
C++
816 lines
18 KiB
C++
/*++
|
|
|
|
Copyright (C) Microsoft Corporation, 1995 - 1999
|
|
All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
detect.cxx
|
|
|
|
Abstract:
|
|
|
|
PnP printer autodetection.
|
|
|
|
Author:
|
|
|
|
Lazar Ivanov (LazarI) May-06-1999
|
|
|
|
Revision History:
|
|
|
|
May-06-1999 - Created.
|
|
Larry Zhu (LZhu) Mar-12-2000 - Rewrote PnP detection code.
|
|
|
|
--*/
|
|
|
|
#include "precomp.hxx"
|
|
#pragma hdrstop
|
|
|
|
#include "detect.hxx"
|
|
|
|
#include <initguid.h>
|
|
#include <ntddpar.h> // for GUID_PARALLEL_DEVICE
|
|
#include <regstr.h>
|
|
|
|
#if DBG
|
|
|
|
#define DBGCHKMSG(bShowMessage, MsgAndArgs) \
|
|
{ \
|
|
if (bShowMessage) \
|
|
{ \
|
|
DBGMSG(DBG_ERROR, MsgAndArgs); \
|
|
} \
|
|
} \
|
|
|
|
#else // DBG
|
|
|
|
#define DBGCHKMSG(bShowMessage, MsgAndArgs)
|
|
|
|
#endif // DBG
|
|
|
|
/********************************************************************
|
|
|
|
PnP private stuff
|
|
|
|
********************************************************************/
|
|
//
|
|
// current 1394 printers are enumerated under LPTENUM
|
|
//
|
|
#define szParallelClassEnumerator TEXT("LPTENUM")
|
|
#define szParallelDot4PrintClassEnumerator TEXT("DOT4PRT")
|
|
#define szUsbPrintClassEnumerator TEXT("USBPRINT")
|
|
#define szInfraRedPrintClassEnumerator TEXT("IRENUM")
|
|
|
|
// This timeout is per recommendation of the PnP guys
|
|
// 1 second should be enough
|
|
#define DELAY_KICKOFF_TIMEOUT 1000
|
|
|
|
extern "C" {
|
|
|
|
//
|
|
// config mgr privates
|
|
//
|
|
DWORD
|
|
CMP_WaitNoPendingInstallEvents(
|
|
IN DWORD dwTimeout
|
|
);
|
|
|
|
}
|
|
|
|
/********************************************************************
|
|
|
|
Global functions
|
|
|
|
********************************************************************/
|
|
|
|
#if FALSE // comment out the old enum code
|
|
#define szParalelPortDevNodePath TEXT("Root\\ParallelClass\\0000")
|
|
BOOL
|
|
PnP_Enum_KickOff(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Kicks off the PNP enumeration event over the
|
|
LPT ports only.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE - on success
|
|
FALSE - if a failure occurs
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
BOOL bReturn;
|
|
DEVINST hLPTDevInst;
|
|
CONFIGRET result;
|
|
|
|
//
|
|
// Find the root dev node
|
|
//
|
|
result = CM_Locate_DevNode_Ex( &hLPTDevInst,
|
|
szParalelPortDevNodePath, CM_LOCATE_DEVNODE_NORMAL, NULL );
|
|
|
|
if( result == CR_SUCCESS )
|
|
{
|
|
//
|
|
// Reenumerate from the root of the devnode tree
|
|
//
|
|
result = CM_Reenumerate_DevNode_Ex( hLPTDevInst, CM_REENUMERATE_NORMAL, NULL );
|
|
|
|
if( result == CR_SUCCESS )
|
|
{
|
|
bReturn = TRUE;
|
|
}
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
#endif // comment out the old enum code
|
|
|
|
BOOL
|
|
PnP_Enum_KickOff(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Kicks off the PNP enumeration event.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE - on success
|
|
FALSE - if a failure occurs
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
DWORD crStatus = CR_DEFAULT;
|
|
DEVINST devRoot = {0};
|
|
|
|
crStatus = CM_Locate_DevNode(&devRoot, NULL, CM_LOCATE_DEVNODE_NORMAL);
|
|
DBGCHKMSG((CR_SUCCESS != crStatus), ("CM_Locate_Devnode failed with 0x%x\n", crStatus));
|
|
|
|
if (CR_SUCCESS == crStatus)
|
|
{
|
|
crStatus = CM_Reenumerate_DevNode(devRoot, CM_REENUMERATE_RETRY_INSTALLATION);
|
|
DBGCHKMSG((CR_SUCCESS != crStatus), ("CM_Reenumerate_DevNode failed with 0x%x\n", crStatus));
|
|
}
|
|
|
|
//
|
|
// There is no point to return CR_XXX code
|
|
//
|
|
SetLastError(CR_SUCCESS == crStatus ? ERROR_SUCCESS : ERROR_INVALID_DATA);
|
|
return CR_SUCCESS == crStatus;
|
|
}
|
|
|
|
VOID
|
|
PnP_Enumerate_Sync(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enumerates the LPT devices sync.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Kick off PnP enumeration
|
|
//
|
|
if( PnP_Enum_KickOff( ) )
|
|
{
|
|
for( ;; )
|
|
{
|
|
//
|
|
// We must wait about 1 sec (DELAY_KICKOFF_TIMEOUT) to make sure the enumeration
|
|
// event has been kicked off completely, so we can wait successfully use the
|
|
// CMP_WaitNoPendingInstallEvents() private API. This is by LonnyM recommendation.
|
|
// This delay also solves the problem with multiple installs like DOT4 printers.
|
|
//
|
|
Sleep( DELAY_KICKOFF_TIMEOUT );
|
|
|
|
//
|
|
// Check to see if there are pending install events
|
|
//
|
|
if( WAIT_TIMEOUT != CMP_WaitNoPendingInstallEvents( 0 ) )
|
|
break;
|
|
|
|
//
|
|
// Wait untill no more pending install events
|
|
//
|
|
CMP_WaitNoPendingInstallEvents( INFINITE );
|
|
}
|
|
}
|
|
}
|
|
|
|
/********************************************************************
|
|
|
|
TPnPDetect - PnP printer detector class
|
|
|
|
********************************************************************/
|
|
|
|
TPnPDetect::
|
|
TPnPDetect(
|
|
VOID
|
|
)
|
|
:
|
|
_bDetectionInProgress( FALSE ),
|
|
_pInfo4Before( NULL ),
|
|
_cInfo4Before( 0 ),
|
|
_hEventDone( NULL )
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
TPnPDetect constructor
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
}
|
|
|
|
TPnPDetect::
|
|
~TPnPDetect(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
TPnPDetect destructor
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Check to free memory
|
|
//
|
|
Reset( );
|
|
}
|
|
|
|
typedef bool PI4_less_type(const PRINTER_INFO_4 p1, const PRINTER_INFO_4 p2);
|
|
static bool PI4_less(const PRINTER_INFO_4 p1, const PRINTER_INFO_4 p2)
|
|
{
|
|
return (lstrcmp(p1.pPrinterName, p2.pPrinterName) < 0);
|
|
}
|
|
|
|
BOOL
|
|
TPnPDetect::
|
|
bKickOffPnPEnumeration(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Kicks off PnP enumeration event on the LPT ports.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE - on success
|
|
FALSE - if a failure occurs
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
DWORD cbInfo4Before = 0;
|
|
TStatusB bStatus;
|
|
|
|
bStatus DBGNOCHK = FALSE;
|
|
|
|
//
|
|
// Check to free memory
|
|
//
|
|
Reset( );
|
|
|
|
//
|
|
// Enumerate the printers before PnP enumeration
|
|
//
|
|
bStatus DBGCHK = VDataRefresh::bEnumPrinters(
|
|
PRINTER_ENUM_LOCAL, NULL, 4, reinterpret_cast<PVOID *>( &_pInfo4Before ),
|
|
&cbInfo4Before, &_cInfo4Before );
|
|
|
|
if( bStatus )
|
|
{
|
|
//
|
|
// Sort out the PRINTER_INFO_4 structures here
|
|
//
|
|
std::sort<PRINTER_INFO_4*, PI4_less_type*>(
|
|
_pInfo4Before,
|
|
_pInfo4Before + _cInfo4Before,
|
|
PI4_less);
|
|
|
|
//
|
|
// Kick off the PnP enumeration
|
|
//
|
|
_hEventDone = CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
|
|
if( _hEventDone )
|
|
{
|
|
HANDLE hEventOut = NULL;
|
|
const HANDLE hCurrentProcess = GetCurrentProcess();
|
|
bStatus DBGCHK = DuplicateHandle( hCurrentProcess,
|
|
_hEventDone,
|
|
hCurrentProcess,
|
|
&hEventOut,
|
|
0,
|
|
TRUE,
|
|
DUPLICATE_SAME_ACCESS );
|
|
|
|
if( bStatus )
|
|
{
|
|
//
|
|
// Kick off the enumeration thread.
|
|
//
|
|
DWORD dwThreadId;
|
|
HANDLE hThread = TSafeThread::Create( NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE)TPnPDetect::EnumThreadProc,
|
|
hEventOut,
|
|
0,
|
|
&dwThreadId );
|
|
|
|
if( hThread )
|
|
{
|
|
//
|
|
// We don't need the thread handle any more.
|
|
//
|
|
CloseHandle( hThread );
|
|
|
|
//
|
|
// Detection initiated successfully.
|
|
//
|
|
_bDetectionInProgress = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Failed to create the thread. Close the event.
|
|
//
|
|
CloseHandle( hEventOut );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check to free the allocated resources if something has failed.
|
|
//
|
|
if( !_bDetectionInProgress )
|
|
{
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
BOOL
|
|
TPnPDetect::
|
|
bDetectionInProgress(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks whether there is pending detection/installation
|
|
process.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE - yes there is pending detect/install process.
|
|
FALSE - otherwise.
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
return _bDetectionInProgress;
|
|
}
|
|
|
|
BOOL
|
|
TPnPDetect::
|
|
bFinished(
|
|
DWORD dwTimeout
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks whether there is no more pending detect/install events
|
|
after the last PnP enumeration event.
|
|
|
|
Arguments:
|
|
|
|
dwTimeout - Time to wait. By default is zero (no wait - just ping)
|
|
|
|
Return Value:
|
|
|
|
TRUE - on success
|
|
FALSE - if a failure occurs
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Check to see if the enum process has been kicked off at all.
|
|
//
|
|
if( _bDetectionInProgress && _pInfo4Before && _hEventDone )
|
|
{
|
|
//
|
|
// Ping to see whether the PnP detect/install process has finished
|
|
//
|
|
if( WAIT_OBJECT_0 == WaitForSingleObject( _hEventDone, 0 ) )
|
|
{
|
|
_bDetectionInProgress = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We must check for all the three conditions below as _bDetectionInProgress
|
|
// may be FALSE, but the enum process has never kicked off. We want to make
|
|
// sure we are in the case _bDetectionInProgress is set to FALSE after the enum
|
|
// process is finished.
|
|
//
|
|
return ( !_bDetectionInProgress && _pInfo4Before && _hEventDone );
|
|
}
|
|
|
|
BOOL
|
|
TPnPDetect::
|
|
bGetDetectedPrinterName(
|
|
TString *pstrPrinterName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enum the printers after the PnP enumeration has finished to
|
|
check whether new local (LPT) printers have been detected.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE - on success
|
|
FALSE - if a failure occurs
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
TStatusB bStatus;
|
|
bStatus DBGNOCHK = FALSE;
|
|
|
|
if( pstrPrinterName && !_bDetectionInProgress && _pInfo4Before )
|
|
{
|
|
//
|
|
// The detection is done here. Enum the printers after the PnP
|
|
// detect/install to see whether new printer has been detected.
|
|
//
|
|
PRINTER_INFO_4 *pInfo4After = NULL;
|
|
DWORD cInfo4After = 0;
|
|
DWORD cbInfo4After = 0;
|
|
|
|
bStatus DBGCHK = VDataRefresh::bEnumPrinters(
|
|
PRINTER_ENUM_LOCAL, NULL, 4, reinterpret_cast<PVOID *>( &pInfo4After ),
|
|
&cbInfo4After, &cInfo4After );
|
|
|
|
if( bStatus && cInfo4After > _cInfo4Before )
|
|
{
|
|
for( UINT uAfter = 0; uAfter < cInfo4After; uAfter++ )
|
|
{
|
|
if( !std::binary_search<PRINTER_INFO_4*, PRINTER_INFO_4, PI4_less_type*>(
|
|
_pInfo4Before,
|
|
_pInfo4Before + _cInfo4Before,
|
|
pInfo4After[uAfter],
|
|
PI4_less) )
|
|
{
|
|
//
|
|
// This printer hasn't been found in the before list
|
|
// so it must be the new printer detected. I know this is partial
|
|
// solution because we are not considering the case we detect more
|
|
// than one local PnP printer, but it is not our intention for now.
|
|
//
|
|
bStatus DBGCHK = pstrPrinterName->bUpdate( pInfo4After[uAfter].pPrinterName );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bStatus DBGNOCHK = FALSE;
|
|
}
|
|
|
|
FreeMem( pInfo4After );
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
DWORD WINAPI
|
|
TPnPDetect::
|
|
EnumThreadProc(
|
|
LPVOID lpParameter
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Invokes the PnP enumeration routine
|
|
|
|
Arguments:
|
|
|
|
lpParameter - Standard (see MSDN)
|
|
|
|
Return Value:
|
|
|
|
Standard (see MSDN)
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
DWORD dwStatus = EXIT_FAILURE;
|
|
HANDLE hEventDone = (HANDLE )lpParameter;
|
|
|
|
dwStatus = hEventDone ? ERROR_SUCCESS : EXIT_FAILURE;
|
|
|
|
//
|
|
// If there is a NULL driver (meaning no driver) associated with a devnode,
|
|
// we want to set CONFIGFLAG_FINISH_INSTALL flag so that PnP manager will
|
|
// try to reinstall a driver for this device.
|
|
//
|
|
if (ERROR_SUCCESS == dwStatus)
|
|
{
|
|
dwStatus = ProcessDevNodesWithNullDriversAll();
|
|
}
|
|
|
|
//
|
|
// Invokes the enumeration routine. It executes syncroniously.
|
|
//
|
|
if (ERROR_SUCCESS == dwStatus)
|
|
{
|
|
(void)PnP_Enumerate_Sync();
|
|
}
|
|
|
|
//
|
|
// Notify the client we are done.
|
|
//
|
|
if (hEventDone)
|
|
{
|
|
SetEvent( hEventDone );
|
|
|
|
//
|
|
// We own the event handle, so we must close it now.
|
|
//
|
|
CloseHandle( hEventDone );
|
|
}
|
|
|
|
return dwStatus;
|
|
}
|
|
|
|
DWORD WINAPI
|
|
TPnPDetect::
|
|
ProcessDevNodesWithNullDriversForOneEnumerator(
|
|
IN PCTSTR pszEnumerator
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine enumerates devnodes of one enumerator and it sets
|
|
CONFIGFLAG_FINISH_INSTALL for devnode that has no driver (or NULL driver)
|
|
so that this device will be re-detected by PnP manager.
|
|
|
|
Arguments:
|
|
|
|
pszEnumerator - The enumerator for a particular bus, this can be
|
|
"LPTENUM", "USBPRINT", "DOT4PRT" etc.
|
|
|
|
Return Value:
|
|
|
|
Standard (see MSDN)
|
|
|
|
Notes:
|
|
|
|
*/
|
|
{
|
|
DWORD dwStatus = ERROR_SUCCESS;
|
|
HDEVINFO hDevInfoSet = INVALID_HANDLE_VALUE;
|
|
TCHAR buffer[MAX_PATH] = {0};
|
|
DWORD dwDataType = REG_NONE;
|
|
SP_DEVINFO_DATA devInfoData = {0};
|
|
DWORD cbRequiredSize = 0;
|
|
DWORD dwConfigFlags = 0;
|
|
|
|
hDevInfoSet = SetupDiGetClassDevs(NULL, pszEnumerator, NULL, DIGCF_ALLCLASSES);
|
|
dwStatus = (INVALID_HANDLE_VALUE != hDevInfoSet) ? ERROR_SUCCESS : GetLastError();
|
|
|
|
if (ERROR_SUCCESS == dwStatus)
|
|
{
|
|
devInfoData.cbSize = sizeof(devInfoData);
|
|
for (DWORD cDevIndex = 0; ERROR_SUCCESS == dwStatus; cDevIndex++)
|
|
{
|
|
dwStatus = SetupDiEnumDeviceInfo(hDevInfoSet, cDevIndex, &devInfoData) ? ERROR_SUCCESS : GetLastError();
|
|
|
|
//
|
|
// When SPDRP_DRIVER is not present, this devnode is associated
|
|
// with no driver or, in the other word, NULL driver
|
|
//
|
|
// For devnodes with NULL drivers, we will set CONFIGFLAG_FINISH_INSTALL
|
|
// so that the device will be re-detected
|
|
//
|
|
// Notes on the error code: internally SetupDiGetDeviceRegistryProperty
|
|
// first returns CR_NO_SUCH_VALUE, but this error code is remapped
|
|
// to ERROR_INVALID_DATA by setupapi
|
|
//
|
|
if (ERROR_SUCCESS == dwStatus)
|
|
{
|
|
dwStatus = SetupDiGetDeviceRegistryProperty(hDevInfoSet,
|
|
&devInfoData,
|
|
SPDRP_DRIVER,
|
|
&dwDataType,
|
|
reinterpret_cast<PBYTE>(buffer),
|
|
sizeof(buffer), &cbRequiredSize) ? ERROR_SUCCESS : GetLastError();
|
|
|
|
if (ERROR_INVALID_DATA == dwStatus)
|
|
{
|
|
dwConfigFlags = 0;
|
|
dwStatus = SetupDiGetDeviceRegistryProperty(hDevInfoSet,
|
|
&devInfoData,
|
|
SPDRP_CONFIGFLAGS,
|
|
&dwDataType,
|
|
reinterpret_cast<PBYTE>(&dwConfigFlags),
|
|
sizeof(dwConfigFlags),
|
|
&cbRequiredSize) ? (REG_DWORD == dwDataType ? ERROR_SUCCESS : ERROR_INVALID_PARAMETER) : GetLastError();
|
|
|
|
if ((ERROR_SUCCESS == dwStatus) && (!(dwConfigFlags & CONFIGFLAG_FINISH_INSTALL)))
|
|
{
|
|
dwConfigFlags |= CONFIGFLAG_FINISH_INSTALL;
|
|
dwStatus = SetupDiSetDeviceRegistryProperty(hDevInfoSet,
|
|
&devInfoData,
|
|
SPDRP_CONFIGFLAGS,
|
|
reinterpret_cast<PBYTE>(&dwConfigFlags),
|
|
sizeof(dwConfigFlags)) ? ERROR_SUCCESS : GetLastError();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dwStatus = ERROR_NO_MORE_ITEMS == dwStatus ? ERROR_SUCCESS : dwStatus;
|
|
}
|
|
else
|
|
{
|
|
dwStatus = ERROR_INVALID_DATA == dwStatus ? ERROR_SUCCESS : dwStatus;
|
|
}
|
|
|
|
if (INVALID_HANDLE_VALUE != hDevInfoSet)
|
|
{
|
|
SetupDiDestroyDeviceInfoList(hDevInfoSet);
|
|
}
|
|
|
|
return dwStatus;
|
|
}
|
|
|
|
DWORD WINAPI
|
|
TPnPDetect::
|
|
ProcessDevNodesWithNullDriversAll(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine enumerates a subset of devnodes and it sets
|
|
CONFIGFLAG_FINISH_INSTALL for devnodes that have no driver (or NULL driver)
|
|
so that these devices will be re-detected by PnP manager.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Standard (see MSDN)
|
|
|
|
Notes:
|
|
|
|
*/
|
|
{
|
|
DWORD dwStatus = ERROR_SUCCESS;
|
|
|
|
//
|
|
// The following enumerators can have printers attached
|
|
//
|
|
static PCTSTR acszEnumerators[] =
|
|
{
|
|
szParallelClassEnumerator,
|
|
szParallelDot4PrintClassEnumerator,
|
|
szUsbPrintClassEnumerator,
|
|
szInfraRedPrintClassEnumerator,
|
|
};
|
|
|
|
for (UINT i = 0; (ERROR_SUCCESS == dwStatus) && (i < COUNTOF(acszEnumerators)); i++)
|
|
{
|
|
dwStatus = ProcessDevNodesWithNullDriversForOneEnumerator(acszEnumerators[i]);
|
|
}
|
|
|
|
return dwStatus;
|
|
}
|
|
|
|
VOID
|
|
TPnPDetect::
|
|
Reset(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Resets the PnP detector class, so you can kick off the
|
|
PnP enumeration event again. Release the memory allocated
|
|
from calling EnumPrinters() before kicking off PnP enum.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
if( _hEventDone )
|
|
{
|
|
CloseHandle( _hEventDone );
|
|
_hEventDone = NULL;
|
|
}
|
|
|
|
if( _pInfo4Before )
|
|
{
|
|
FreeMem( _pInfo4Before );
|
|
_pInfo4Before = NULL;
|
|
}
|
|
|
|
_bDetectionInProgress = FALSE;
|
|
}
|
|
|