windows-nt/Source/XPSP1/NT/inetsrv/iis/svcs/loadbal/cmdline/iislbc.cxx

929 lines
25 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
iislbc.cxx
Abstract:
Command line utility to set/get IIS load balancing configuration
Author:
Philippe Choquier (phillich)
--*/
//#define INITGUID
extern "C" {
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntsecapi.h>
#include <windows.h>
#include <winsock2.h>
#include <stdlib.h>
#include <time.h>
#include <ipnat.h>
#if 0
typedef NTSTATUS
(*PNAT_REGISTER_SESSION_CONTROL)(
IN ULONG Version
);
typedef NTSTATUS
(*PNAT_IOCTL_SESSION_CONTROL)(
IN PIRP Irp
);
#endif
}
#include <stdio.h>
#include <ole2.h>
#include <pdh.h>
#include <IisLb.h>
#include <lbxbf.hxx>
#include <lbcnfg.hxx>
#include <IisLbs.hxx>
#include <bootexp.hxx>
#include "iisrc.h"
//
// HRESULTTOWIN32() maps an HRESULT to a Win32 error. If the facility code
// of the HRESULT is FACILITY_WIN32, then the code portion (i.e. the
// original Win32 error) is returned. Otherwise, the original HRESULT is
// returned unchagned.
//
#define HRESULTTOWIN32(hres) \
((HRESULT_FACILITY(hres) == FACILITY_WIN32) \
? HRESULT_CODE(hres) \
: (hres))
#define RETURNCODETOHRESULT(rc) \
(((rc) < 0x10000) \
? HRESULT_FROM_WIN32(rc) \
: (rc))
enum LB_CMD {
CMD_NONE,
CMD_ADD_SERVER,
CMD_ADD_IP,
CMD_ADD_PERFMON,
CMD_DEL_SERVER,
CMD_DEL_IP,
CMD_DEL_PERFMON,
#if 0
CMD_SET_STICKY,
#endif
CMD_SET_IP,
CMD_LIST,
CMD_LIST_IP_ENDPOINTS,
CMD_START_DRIVER,
CMD_STOP_DRIVER,
} ;
#define LEN_SERVER_NAME 16
#define LEN_IP (4*3+3+1+3)
#define LEN_PERF 40
extern CKernelIpMapHelper g_KernelIpMap;
extern WSADATA g_WSAData;
BOOL
LocatePerfCounter(
CComputerPerfCounters* pPerfCounters,
LPWSTR pszSrv,
LPWSTR pszParam,
UINT* piPerfCounter
)
{
UINT i;
LPWSTR pszS;
LPWSTR psz;
DWORD dw;
if ( pszSrv && (*pszSrv == L'\0' || !wcscmp(pszSrv,L"*")) )
{
pszSrv = NULL;
}
for ( i = 0 ;
pPerfCounters->EnumPerfCounter( i, &pszS, &psz, &dw ) ;
++i )
{
if ( ((pszS == NULL) == (pszSrv==NULL)) &&
!_wcsicmp( psz, pszParam ) )
{
*piPerfCounter = i;
return TRUE;
}
}
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
BOOL
LocateServer(
CIPMap* IpMap,
LPWSTR pszParam,
UINT* piServer
)
{
UINT i;
LPWSTR psz;
for ( i = 0 ;
IpMap->EnumComputer( i, &psz ) ;
++ i )
{
if ( !_wcsicmp( psz, pszParam ) )
{
*piServer = i;
return TRUE;
}
}
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
BOOL
LocatePublicIp(
CIPMap* IpMap,
LPWSTR pszParam,
UINT* piRes
)
{
UINT i;
LPWSTR psz;
LPWSTR pszName;
DWORD dwSticky;
DWORD dwAttr;
for ( i = 0 ;
IpMap->EnumIpPublic( i, &psz, &pszName, &dwSticky, &dwAttr ) ;
++i )
{
if ( !_wcsicmp( psz, pszParam ) )
{
*piRes = i;
return TRUE;
}
}
SetLastError( ERROR_INVALID_PARAMETER );
return FALSE;
}
VOID
DisplayErrorMessage(
DWORD dwErr
)
{
LPWSTR pErr;
if ( FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwErr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&pErr,
0,
NULL ) )
{
LPWSTR p;
if ( p = wcschr( pErr, L'\r' ) )
{
*p = L'\0';
}
fputws( pErr, stdout );
LocalFree( pErr );
}
}
BOOL
ChooseCounter(
LPWSTR pszCounterBuffer
)
{
PDH_BROWSE_DLG_CONFIG_W BrowseInfo;
PDH_STATUS pdhStatus;
LPWSTR pszPostServerName;
WCHAR achSelect[128];
if ( !LoadStringW( NULL, IDS_LBC_SP, achSelect, sizeof(achSelect) ) )
{
achSelect[0] = L'\0';
}
#if defined(_X86_) && 0
__asm int 3
#endif
BrowseInfo.bIncludeInstanceIndex = FALSE;
BrowseInfo.bSingleCounterPerAdd = TRUE;
BrowseInfo.bSingleCounterPerDialog = TRUE;
BrowseInfo.bLocalCountersOnly = FALSE;
BrowseInfo.bWildCardInstances = FALSE;
BrowseInfo.bHideDetailBox = FALSE;
BrowseInfo.bInitializePath = FALSE;
BrowseInfo.bDisableMachineSelection = FALSE;
BrowseInfo.bIncludeCostlyObjects = TRUE;
BrowseInfo.hWndOwner = GetActiveWindow();
BrowseInfo.szDataSource = NULL;
BrowseInfo.szReturnPathBuffer = pszCounterBuffer;
BrowseInfo.cchReturnPathLength = MAX_PATH;
BrowseInfo.pCallBack = NULL;
BrowseInfo.dwCallBackArg = 0;
BrowseInfo.CallBackStatus = ERROR_SUCCESS;
BrowseInfo.dwDefaultDetailLevel = PERF_DETAIL_WIZARD;
BrowseInfo.szDialogBoxCaption = achSelect;
if ( PdhBrowseCountersW (&BrowseInfo) == ERROR_SUCCESS )
{
if ( !memcmp( pszCounterBuffer, L"\\\\", 2*sizeof(WCHAR) ) &&
(pszPostServerName = wcschr( pszCounterBuffer+2, L'\\' )) )
{
memmove( pszCounterBuffer, pszPostServerName, (wcslen(pszPostServerName)+1)*sizeof(WCHAR) );
}
return TRUE;
}
return FALSE;
}
#if 0
VOID
TestDriver(
)
{
HMODULE hMod;
PNAT_REGISTER_SESSION_CONTROL pfnNatRegisterSessionControl;
PNAT_IOCTL_SESSION_CONTROL pfnNatIoctlSessionControl;
PNAT_DIRECTOR_QUERY_SESSION pfnNatQuerySessionControl;
IRP Irp;
ULONG PrivateAddr;
USHORT PrivatePort;
ULONG PublicAddr;
USHORT PublicPort;
ULONG RemoteAddr;
USHORT RemotePort;
CKernelServerDescription* pS;
UINT iS;
UINT iP;
IO_STACK_LOCATION IrpSp;
LPVOID pv;
#if 0
UINT x;
UINT c;
UINT f;
UINT y;
for ( c = 0, x = 2 ; x < 1000 ; ++x )
{
f = TRUE;
for ( y = 2 ; y * y < x ; ++y )
{
if ( x % y == 0 )
{
f = FALSE;
break;
}
}
if ( f )
{
wprintf( L"%d\t", x );
if ( (++c & 7) == 7 )
{
wprintf(L"\n");
}
}
}
#endif
if ( (hMod = LoadLibraryW( L"iislbdr.dll" )) )
{
pfnNatRegisterSessionControl = (PNAT_REGISTER_SESSION_CONTROL)GetProcAddress( hMod,
"NatRegisterSessionControl" );
pfnNatIoctlSessionControl = (PNAT_IOCTL_SESSION_CONTROL)GetProcAddress( hMod,
"NatIoctlSessionControl" );
pfnNatQuerySessionControl = (PNAT_DIRECTOR_QUERY_SESSION)GetProcAddress( hMod,
"NatQuerySessionControl" );
if ( pfnNatRegisterSessionControl &&
pfnNatIoctlSessionControl &&
pfnNatQuerySessionControl )
{
if ( WSAStartup( MAKEWORD( 2, 0), &g_WSAData ) == 0 &&
InitGlobals() )
{
PublicAddr = 0x04030201;
PublicPort = 0x5000;
RemoteAddr = 0x0f0f0f0f;
RemotePort = 0xf000;
pfnNatRegisterSessionControl( 1 );
for ( iS = 0;
iS < g_KernelIpMap.ServerCount() ;
++iS )
{
pS = g_KernelIpMap.GetServerPtr( iS );
pS->m_dwLoadAvail = 100;
pS->m_LoadbalancedLoadAvail = pS->m_dwLoadAvail;
}
// refcount set to != 0
for ( iP = 0;
iP < g_KernelIpMap.PrivateIpCount() ;
++iP )
{
g_KernelIpMap.GetPrivateIpEndpoint( iP )->m_dwIndex = 1;
}
#if defined(_X86_)
__asm int 3
#endif
g_KernelIpMap.GetPublicIpPtr( 0 )->m_dwNotifyPort = 0x5000;
g_KernelIpMap.GetPublicIpPtr( 0 )->m_usUniquePort = 0x5000;
IoGetCurrentIrpStackLocation( &Irp ) = &IrpSp;
IrpSp.Parameters.DeviceIoControl.InputBufferLength = g_KernelIpMap.GetSize();
Irp.AssociatedIrp.SystemBuffer = g_KernelIpMap.GetBuffer();
pfnNatIoctlSessionControl( &Irp );
pfnNatIoctlSessionControl( &Irp );
pfnNatQuerySessionControl( NULL, NAT_PROTOCOL_TCP,
PublicAddr, PublicPort,
RemoteAddr, RemotePort,
&PrivateAddr, &PrivatePort,
&pv );
// test cache
pfnNatQuerySessionControl( NULL, NAT_PROTOCOL_TCP,
PublicAddr, PublicPort,
RemoteAddr, RemotePort,
&PrivateAddr, &PrivatePort,
&pv );
// test new public IP addr
PublicAddr = 0x05030201;
PublicPort = 0x5000;
pfnNatQuerySessionControl( NULL, NAT_PROTOCOL_TCP,
PublicAddr, PublicPort,
RemoteAddr, RemotePort,
&PrivateAddr, &PrivatePort,
&pv );
// test invalid public IP addr
PublicAddr = 0xff030201;
PublicPort = 0x5000;
pfnNatQuerySessionControl( NULL, NAT_PROTOCOL_TCP,
PublicAddr, PublicPort,
RemoteAddr, RemotePort,
&PrivateAddr, &PrivatePort,
&pv );
pfnNatIoctlSessionControl( &Irp );
}
TerminateGlobals();
}
else
{
DisplayErrorMessage( GetLastError() );
}
FreeLibrary( hMod );
}
else
{
DisplayErrorMessage( GetLastError() );
}
}
#endif
BYTE abSerialized[32768];
#define RC_BUFF achMsg
#define RC_PRINTF(a,b) if ( LoadStringW( NULL, a, achMsg, sizeof(achMsg) ) ) wprintf b;
LPWSTR
ToUnicode(
LPSTR psz
)
{
LPWSTR ps = (LPWSTR)LocalAlloc( LMEM_FIXED, (strlen(psz)+1)*sizeof(WCHAR) );
if ( ps )
{
if ( !MultiByteToWideChar( CP_ACP,
MB_PRECOMPOSED,
psz,
-1,
ps,
(strlen(psz)+1) ) )
{
*ps = L'\0';
}
return ps;
}
return L"";
}
int __cdecl main(
int argc,
CHAR* argv[]
)
{
int Status = 0;
HRESULT hRes;
LPSTR pszMachineName = NULL;
COSERVERINFO csiMachineName;
MULTI_QI rgmq;
IClassFactory* pcsfFactory = NULL;
IMSIisLb* pIisLb = NULL;
WCHAR awchComputer[64];
CIPMap IpMap;
CComputerPerfCounters PerfCounters;
CIpEndpointList IpEndpoints;
DWORD dwSticky;
DWORD dwReq;
LPBYTE pbSerialized;
int arg;
int cParam;
LPWSTR apszParam[16];
LB_CMD iCmd = CMD_NONE;
BOOL fUpdateIpMap = FALSE;
BOOL fUpdatePerfCounters = FALSE;
BOOL fUpdateSticky = FALSE;
UINT iServer;
UINT iPublicIp;
UINT iPerfCounter;
XBF xbf;
LPWSTR pszPublicIp;
LPWSTR pszPrivateIp;
LPWSTR pszServer;
LPWSTR pszName;
LPWSTR pszPerfCounter;
DWORD dwWeight;
WCHAR achCounterBuffer[MAX_PATH];
WCHAR achMsg[1024];
DWORD dwAttr;
if ( argc < 2 )
{
RC_PRINTF( IDS_LBC_HELP, (RC_BUFF) );
return 3;
}
else
{
RC_PRINTF( IDS_LBC_COPYRIGHT, (RC_BUFF) );
}
cParam = 0;
for ( arg = 1 ; arg < argc ; ++arg )
{
if ( argv[arg][0] == '-' )
{
switch ( argv[arg][1] )
{
case 'm':
pszMachineName = argv[arg]+2;
break;
case 'a':
switch ( argv[arg][2] )
{
case 's':
iCmd = CMD_ADD_SERVER; break;
case 'i':
iCmd = CMD_ADD_IP; break;
case 'p':
iCmd = CMD_ADD_PERFMON; break;
}
break;
case 'd':
switch ( argv[arg][2] )
{
case 's':
iCmd = CMD_DEL_SERVER; break;
case 'i':
iCmd = CMD_DEL_IP; break;
case 'p':
iCmd = CMD_DEL_PERFMON; break;
}
break;
case 's':
switch ( argv[arg][2] )
{
#if 0
case 's':
iCmd = CMD_SET_STICKY; break;
#endif
case 'i':
iCmd = CMD_SET_IP; break;
}
break;
case 'l':
switch ( argv[arg][2] )
{
case 'e':
iCmd = CMD_LIST_IP_ENDPOINTS; break;
case 'c':
iCmd = CMD_LIST;
}
break;
#if 0
case 't':
TestDriver();
return 0;
#endif
case '0':
iCmd = CMD_STOP_DRIVER;
break;
case '1':
iCmd = CMD_START_DRIVER;
break;
}
}
else if ( cParam < sizeof(apszParam)/sizeof(LPWSTR) )
{
apszParam[cParam++] = ToUnicode( argv[arg] );
}
}
//fill the structure for CoCreateInstanceEx
ZeroMemory( &csiMachineName, sizeof(csiMachineName) );
if ( pszMachineName )
{
if ( !MultiByteToWideChar( CP_ACP,
MB_PRECOMPOSED,
pszMachineName,
-1,
awchComputer,
sizeof(awchComputer) ) )
{
return FALSE;
}
csiMachineName.pwszName = awchComputer;
}
else
{
csiMachineName.pwszName = NULL;
}
CoInitializeEx( NULL, COINIT_MULTITHREADED );
#if 0
hRes = CoGetClassObject(CLSID_MSIisLb, CLSCTX_SERVER, &csiMachineName,
IID_IClassFactory, (void**) &pcsfFactory);
if ( SUCCEEDED( hRes ) )
{
hRes = pcsfFactory->CreateInstance(NULL, IID_IMSIisLb, (void **) &rgmq.pItf);
if ( SUCCEEDED( hRes ) )
#else
if ( 1 )
{
rgmq.pIID = &IID_IMSIisLb;
rgmq.hr = 0;
rgmq.pItf = NULL;
hRes = CoCreateInstanceEx( CLSID_MSIisLb, NULL, CLSCTX_SERVER, &csiMachineName, 1, &rgmq );
if ( SUCCEEDED( hRes ) && SUCCEEDED( rgmq.hr ) )
#endif
{
pIisLb = (IMSIisLb*)rgmq.pItf;
// get current configuration
if ( SUCCEEDED( hRes = pIisLb->GetIpList( sizeof(abSerialized), abSerialized, &dwReq ) ) )
{
pbSerialized = abSerialized;
hRes = IpMap.Unserialize( &pbSerialized, &dwReq ) ? S_OK : RETURNCODETOHRESULT(ERROR_BAD_CONFIGURATION);
}
if ( SUCCEEDED( hRes ) &&
SUCCEEDED( hRes = pIisLb->GetPerfmonCounters( sizeof(abSerialized), abSerialized, &dwReq ) ) )
{
pbSerialized = abSerialized;
hRes = PerfCounters.Unserialize( &pbSerialized, &dwReq ) ? S_OK : RETURNCODETOHRESULT(ERROR_BAD_CONFIGURATION);
}
#if 0
if ( SUCCEEDED( hRes ) )
{
hRes = pIisLb->GetStickyDuration( &dwSticky );
}
#endif
if ( SUCCEEDED( hRes ) &&
SUCCEEDED( hRes = pIisLb->GetIpEndpointList( sizeof(abSerialized), abSerialized, &dwReq ) ) )
{
pbSerialized = abSerialized;
hRes = IpEndpoints.Unserialize( &pbSerialized, &dwReq ) ? S_OK : RETURNCODETOHRESULT(ERROR_BAD_CONFIGURATION);
}
if ( FAILED(hRes) )
{
pIisLb->Release();
}
}
#if 0
pcsfFactory->Release();
#endif
}
if ( SUCCEEDED( hRes ) )
{
switch ( iCmd )
{
case CMD_START_DRIVER:
hRes = pIisLb->SetDriverState( SERVICE_RUNNING );
break;
case CMD_STOP_DRIVER:
hRes = pIisLb->SetDriverState( SERVICE_STOPPED );
break;
case CMD_ADD_SERVER:
if ( cParam == 1 &&
IpMap.AddComputer( apszParam[0] ) )
{
fUpdateIpMap = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT(GetLastError());
}
break;
case CMD_ADD_IP:
if ( cParam == 3 &&
IpMap.AddIpPublic( apszParam[0], apszParam[1], _wtoi(apszParam[2]), 0 ) )
{
fUpdateIpMap = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT(GetLastError());
}
break;
case CMD_ADD_PERFMON:
if ( cParam >= 2 &&
!wcscmp( apszParam[1], L"*" ) )
{
if ( ChooseCounter( achCounterBuffer ) )
{
apszParam[1] = achCounterBuffer;
}
else
{
cParam = 0;
}
}
// server name : * for all
if ( cParam >= 1 &&
!wcscmp( apszParam[0], L"*" ) )
{
apszParam[0] = NULL;
}
if ( cParam == 3 &&
PerfCounters.AddPerfCounter( apszParam[0], apszParam[1], _wtoi(apszParam[2]) ) )
{
fUpdatePerfCounters = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT(GetLastError());
}
break;
case CMD_DEL_SERVER:
if ( cParam == 1 &&
LocateServer( &IpMap, apszParam[0], &iServer ) )
{
if ( IpMap.DeleteComputer( iServer ) )
{
fUpdateIpMap = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT( GetLastError() );
}
}
else
{
hRes = RETURNCODETOHRESULT(ERROR_INVALID_PARAMETER);
}
break;
case CMD_DEL_IP:
if ( cParam == 1 &&
LocatePublicIp( &IpMap, apszParam[0], &iPublicIp ) )
{
if ( IpMap.DeleteIpPublic( iPublicIp ) )
{
fUpdateIpMap = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT( GetLastError() );
}
}
else
{
hRes = RETURNCODETOHRESULT(ERROR_INVALID_PARAMETER);
}
break;
case CMD_DEL_PERFMON:
if ( cParam == 2 &&
LocatePerfCounter( &PerfCounters, apszParam[0], apszParam[1], &iPerfCounter ) )
{
if ( PerfCounters.DeletePerfCounter( iPerfCounter ) )
{
fUpdatePerfCounters = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT( GetLastError() );
}
}
else
{
hRes = RETURNCODETOHRESULT(ERROR_INVALID_PARAMETER);
}
break;
#if 0
case CMD_SET_STICKY:
if ( cParam == 1 )
{
dwSticky = _wtoi( apszParam[0] );
fUpdateSticky = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT(ERROR_INVALID_PARAMETER);
}
break;
#endif
case CMD_SET_IP:
if ( cParam == 4 &&
LocateServer( &IpMap, apszParam[0], &iServer ) &&
LocatePublicIp( &IpMap, apszParam[1], &iPublicIp ) )
{
if ( IpMap.SetIpPrivate( iServer, iPublicIp, apszParam[2], apszParam[3] ) )
{
fUpdateIpMap = TRUE;
}
else
{
hRes = RETURNCODETOHRESULT( GetLastError() );
}
}
else
{
hRes = RETURNCODETOHRESULT(ERROR_INVALID_PARAMETER);
}
break;
case CMD_LIST_IP_ENDPOINTS:
RC_PRINTF( IDS_LBC_IPE, (RC_BUFF) );
for ( iPublicIp = 0 ;
IpEndpoints.EnumIpEndpoint( iPublicIp, &pszPublicIp ) ;
++iPublicIp )
{
wprintf( L"%-*.*s\t", LEN_IP, LEN_IP, pszPublicIp );
}
wprintf( L"\n");
break;
case CMD_LIST:
RC_PRINTF( IDS_LBC_IPM, (RC_BUFF, LEN_SERVER_NAME, LEN_SERVER_NAME, "") );
for ( iPublicIp = 0 ;
IpMap.EnumIpPublic( iPublicIp, &pszPublicIp, &pszName, &dwSticky, &dwAttr ) ;
++iPublicIp )
{
wprintf( L"%-*.*s\t", LEN_IP, LEN_IP, pszPublicIp );
}
wprintf( L"\n");
for ( iServer = 0 ;
IpMap.EnumComputer( iServer, &pszServer ) ;
++iServer )
{
wprintf( L"%-*.*s\t", LEN_SERVER_NAME, LEN_SERVER_NAME, pszServer );
for ( iPublicIp = 0 ;
IpMap.EnumIpPublic( iPublicIp, &pszPublicIp, &pszName, &dwSticky, &dwAttr ) ;
++iPublicIp )
{
IpMap.GetIpPrivate( iServer, iPublicIp, &pszPrivateIp, &pszName );
wprintf( L"%-*.*s\t", LEN_IP, LEN_IP, pszPrivateIp );
}
wprintf( L"\n");
}
wprintf( L"\n");
RC_PRINTF( IDS_LBC_PM, (RC_BUFF) );
for ( iPerfCounter = 0 ;
PerfCounters.EnumPerfCounter( iPerfCounter, &pszServer, &pszPerfCounter, &dwWeight ) ;
++iPerfCounter )
{
wprintf( L"%-*.*s%-*.*s\t%u\n",
LEN_SERVER_NAME, LEN_SERVER_NAME, pszServer ? pszServer : L"*",
LEN_PERF, LEN_PERF, pszPerfCounter,
dwWeight );
}
wprintf( L"\n");
#if 0
RC_PRINTF( IDS_LBC_ST, (RC_BUFF, dwSticky) );
#endif
break;
}
// update configuration
if ( SUCCEEDED( hRes ) &&
fUpdateIpMap &&
(xbf.Reset, IpMap.Serialize( &xbf )) )
{
hRes = pIisLb->SetIpList( xbf.GetUsed(), xbf.GetBuff() );
}
if ( SUCCEEDED( hRes ) &&
fUpdatePerfCounters &&
(xbf.Reset, PerfCounters.Serialize( &xbf )) )
{
hRes = pIisLb->SetPerfmonCounters( xbf.GetUsed(), xbf.GetBuff() );
}
#if 0
if ( SUCCEEDED( hRes ) &&
fUpdateSticky )
{
hRes = pIisLb->SetStickyDuration( dwSticky );
}
#endif
pIisLb->Release();
}
if ( FAILED( hRes ) )
{
DisplayErrorMessage( HRESULTTOWIN32( hRes ) );
}
CoUninitialize();
return Status;
}