/*++ Copyright (c) 1994-1998 Microsoft Corporation Module Name : inetmgr.cpp Abstract: Main program object Author: Ronald Meijer (ronaldm) Project: Internet Services Manager Functions Exported: Revision History: --*/ // // Include files // #include "stdafx.h" #include "inetmgr.h" #include "constr.h" #include #include #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif // // Default HTML help topics file name // #define DEFAULT_HTML _T("htmldocs\\inetdocs.htm") LPOLESTR CoTaskDupString( IN LPCOLESTR szString ) /*++ Routine Description: Helper function to duplicate a OLESTR Arguments: LPOLESTR szString : Source string Return Value: Pointer to the new string or NULL --*/ { OLECHAR * lpString = (OLECHAR *)CoTaskMemAlloc( sizeof(OLECHAR)*(lstrlen(szString) + 1) ); if (lpString != NULL) { lstrcpy(lpString, szString); } return lpString; } HRESULT BuildResURL( OUT CString & str, IN HINSTANCE hSourceInstance ) /*++ Routine Description: Helper function to generate "res://" type string Arguments: CString & str : Returns res:// string HINSTANCE hSourceInstance : Source instance, : or -1 for current module : or NULL for calling app (MMC) Return Value: HRESULT --*/ { AFX_MANAGE_STATE(::AfxGetStaticModuleState()); CError err; TCHAR atchModuleFileName[MAX_PATH + 1]; int cch = ::GetModuleFileName( (hSourceInstance == (HINSTANCE) - 1) ? ::AfxGetInstanceHandle() : hSourceInstance, atchModuleFileName, sizeof(atchModuleFileName) / sizeof(OLECHAR) ); if (!cch) { err.GetLastWinError(); ASSERT(FALSE); return err; } str = _T("res://"); str += atchModuleFileName; return err; } // // Static Initialization // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LPCTSTR CServiceInfo::s_cszSupcfg = _T("::SUPCFG:"); // // ISM Method VTable Definition // CServiceInfo::ISM_METHOD_DEF CServiceInfo::s_imdMethods[ISM_NUM_METHODS] = { //-----------ID----------------Must Have?--------------Method Name------------ ISM_QUERY_SERVICE_INFO, TRUE, SZ_SERVICEINFO_PROC, ISM_DISCOVER_SERVERS, FALSE, SZ_DISCOVERY_PROC, ISM_QUERY_SERVER_INFO, TRUE, SZ_SERVERINFO_PROC, ISM_CHANGE_SERVICE_STATE, FALSE, SZ_CHANGESTATE_PROC, ISM_CONFIGURE, TRUE, SZ_CONFIGURE_PROC, ISM_BIND, FALSE, SZ_BIND_PROC, ISM_UNBIND, FALSE, SZ_UNBIND_PROC, ISM_CONFIGURE_CHILD, FALSE, SZ_CONFIGURE_CHILD_PROC, ISM_ENUMERATE_INSTANCES, FALSE, SZ_ENUMERATE_INSTANCES_PROC, ISM_ENUMERATE_CHILDREN, FALSE, SZ_ENUMERATE_CHILDREN_PROC, ISM_ADD_INSTANCE, FALSE, SZ_ADD_INSTANCE_PROC, ISM_DELETE_INSTANCE, FALSE, SZ_DELETE_INSTANCE_PROC, ISM_ADD_CHILD, FALSE, SZ_ADD_CHILD_PROC, ISM_DELETE_CHILD, FALSE, SZ_DELETE_CHILD_PROC, ISM_RENAME_CHILD, FALSE, SZ_RENAME_CHILD_PROC, ISM_QUERY_INSTANCE_INFO, FALSE, SZ_QUERY_INSTANCE_INFO_PROC, ISM_QUERY_CHILD_INFO, FALSE, SZ_QUERY_CHILD_INFO_PROC, ISM_MMC_CONFIGURE, FALSE, SZ_MMC_CONFIGURE_PROC, ISM_MMC_CONFIGURE_CHILD, FALSE, SZ_MMC_CONFIGURE_CHILD_PROC, ISM_SECURITY_WIZARD, FALSE, SZ_SECURITY_WIZARD_PROC }; CServiceInfo::CServiceInfo( IN int nID, IN LPCTSTR lpDLLName ) /*++ Routine Description: Constructor for service info object. Load the specified config DLL, and initialise the entry points. Arguments: int nID : The guaranteed unique ID assigned to this service LPCTSTR lpDLLName : The config DLL name to be loaded Return Value: N/A --*/ : CObjectPlus(), m_hModule(NULL), m_psiMaster(NULL), m_strDLLName(lpDLLName), m_strSupDLLName(), #ifndef USE_VTABLE m_pfnQueryServiceInfo(NULL), m_pfnDiscoverServers(NULL), m_pfnQueryServerInfo(NULL), m_pfnChangeServiceState(NULL), m_pfnConfigure(NULL), m_pfnConfigureChild(NULL), m_pfnEnumerateInstances(NULL), m_pfnEnumerateChildren(NULL), m_pfnAddInstance(NULL), m_pfnDeleteInstance(NULL), m_pfnAddChild(NULL), m_pfnDeleteChild(NULL), m_pfnRenameChild(NULL), m_pfnQueryInstanceInfo(NULL), m_pfnQueryChildInfo(NULL), m_pfnISMMMCConfigureServers(NULL), m_pfnISMMMCConfigureChild(NULL), m_pfnISMSecurityWizard(NULL), #endif // USE_VTABLE m_nID(nID), m_iBmpID(-1), m_iBmpChildID(-1) { #ifdef USE_VTABLE // // Initialize VTable // ZeroMemory(&m_rgpfnISMMethods, sizeof(m_rgpfnISMMethods)); #endif // USE_VTABLE // // Check for parameter options. // TRACEEOLID("Raw DLL name: " << m_strDLLName); // // Load super DLL // int nOpt = m_strDLLName.Find(s_cszSupcfg); if (nOpt >= 0) { m_strSupDLLName = m_strDLLName.Mid(nOpt + lstrlen(s_cszSupcfg)); m_strDLLName.ReleaseBuffer(nOpt); TRACEEOLID("Superceed DLL: " << m_strSupDLLName); } TRACEEOLID("Attempting to load " << m_strDLLName); CError err; BOOL fMissingMethod = FALSE; m_hModule = ::AfxLoadLibrary(m_strDLLName); if (m_hModule == NULL) { err.GetLastWinError(); if (err.Succeeded()) { // // This shouldn't happen, but it sometimes does. // AfxLoadLibrary resets the last error somewhere??? // TRACEEOLID("Error!!! Library not loaded, but last error returned 0"); err = ERROR_DLL_NOT_FOUND; } TRACEEOLID("Failed to load " << m_strDLLName << " GetLastError() returns " << err ); } else { TRACEEOLID(m_strDLLName << " LoadLibrary succeeded"); #ifdef USE_VTABLE // // Initialize VTable // for (int i = 0; i < ISM_NUM_METHODS; ++i) { m_rgpfnISMMethods[CServiceInfo::s_imdMethods[i].iID] = (pfnISMMethod)GetProcAddress( m_hModule, CServiceInfo::s_imdMethods[i].lpszMethodName ); if (CServiceInfo::s_imdMethods[i].fMustHave && !m_rgpfnISMMethods[CServiceInfo::s_imdMethods[i].iID]) { fMissingMethod = TRUE; } } #else // // Initialise function pointers (not all need be // present) // m_pfnQueryServiceInfo = (pfnQueryServiceInfo) ::GetProcAddress(m_hModule, SZ_SERVICEINFO_PROC); m_pfnDiscoverServers = (pfnDiscoverServers) ::GetProcAddress(m_hModule, SZ_DISCOVERY_PROC); m_pfnQueryServerInfo = (pfnQueryServerInfo) ::GetProcAddress(m_hModule, SZ_SERVERINFO_PROC); m_pfnChangeServiceState = (pfnChangeServiceState) ::GetProcAddress(m_hModule, SZ_CHANGESTATE_PROC); m_pfnConfigure = (pfnConfigure) ::GetProcAddress(m_hModule, SZ_CONFIGURE_PROC); m_pfnBind = (pfnBind) ::GetProcAddress(m_hModule, SZ_BIND_PROC); m_pfnUnbind = (pfnUnbind) ::GetProcAddress(m_hModule, SZ_UNBIND_PROC); m_pfnConfigureChild = (pfnConfigureChild) ::GetProcAddress(m_hModule, SZ_CONFIGURE_CHILD_PROC); m_pfnEnumerateInstances = (pfnEnumerateInstances) ::GetProcAddress(m_hModule, SZ_ENUMERATE_INSTANCES_PROC); m_pfnEnumerateChildren = (pfnEnumerateChildren) ::GetProcAddress(m_hModule, SZ_ENUMERATE_CHILDREN_PROC); m_pfnAddInstance = (pfnAddInstance) ::GetProcAddress(m_hModule, SZ_ADD_INSTANCE_PROC); m_pfnDeleteInstance = (pfnDeleteInstance) ::GetProcAddress(m_hModule, SZ_DELETE_INSTANCE_PROC); m_pfnAddChild = (pfnAddChild) ::GetProcAddress(m_hModule, SZ_ADD_CHILD_PROC); m_pfnDeleteChild = (pfnDeleteChild) ::GetProcAddress(m_hModule, SZ_DELETE_CHILD_PROC); m_pfnRenameChild = (pfnRenameChild) ::GetProcAddress(m_hModule, SZ_RENAME_CHILD_PROC); m_pfnQueryInstanceInfo = (pfnQueryInstanceInfo) ::GetProcAddress(m_hModule, SZ_QUERY_INSTANCE_INFO_PROC); m_pfnQueryChildInfo = (pfnQueryChildInfo) ::GetProcAddress(m_hModule, SZ_QUERY_CHILD_INFO_PROC); m_pfnISMMMCConfigureServers = (pfnISMMMCConfigureServers) ::GetProcAddress(m_hModule, SZ_MMC_CONFIGURE_PROC); m_pfnISMMMCConfigureChild = (pfnISMMMCConfigureChild) ::GetProcAddress(m_hModule, SZ_MMC_CONFIGURE_CHILD_PROC); m_pfnISMSecurityWizard = (pfnISMSecurityWizard) ::GetProcAddress(m_hModule, SZ_SECURITY_WIZARD_PROC); fMissingMethod = m_pfnQueryServiceInfo == NULL || m_pfnQueryServerInfo == NULL || m_pfnChangeServiceState == NULL || m_pfnConfigure == NULL; #endif // USE_VTABLE ::ZeroMemory(&m_si, sizeof(m_si)); m_si.dwSize = sizeof(m_si); err = ISMQueryServiceInfo(&m_si); } if (err.Failed()) { // // Fill in the short and long names // with default values. // CString strMenu, strToolTips, str; VERIFY(strMenu.LoadString(IDS_DEFAULT_SHORTNAME)); VERIFY(strToolTips.LoadString(IDS_DEFAULT_LONGNAME)); // // Since the structure was zero-filled to // begin with, lstrcpyn is ok, because // we will always have the terminating NULL // str.Format(strMenu, (LPCTSTR)lpDLLName); ::lstrcpyn( m_si.atchShortName, (LPCTSTR)str, sizeof((m_si.atchShortName) - 1) / sizeof(m_si.atchShortName[0]) ); str.Format(strToolTips, (LPCTSTR)lpDLLName); ::lstrcpyn( m_si.atchLongName, (LPCTSTR)str, sizeof((m_si.atchLongName) - 1) / sizeof(m_si.atchLongName[0]) ); } BOOL fInitOK = m_hModule != NULL && !fMissingMethod && err.Succeeded(); // // The service is selected at startup // time if it loaded succesfully // m_fIsSelected = fInitOK; TRACEEOLID("Success = " << fInitOK); m_hrReturnCode = err; } CServiceInfo::~CServiceInfo() /*++ Routine Description: Destruct the object by unloading the config DLL Arguments: N/A Return Value: N/A --*/ { TRACEEOLID("Cleaning up service info"); if (m_hModule != NULL) { TRACEEOLID("Unloading library"); VERIFY(::AfxFreeLibrary(m_hModule)); } } // // ISM Api Functions // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef USE_VTABLE DWORD CServiceInfo::ISMQueryServiceInfo( OUT ISMSERVICEINFO * psi ) /*++ Routine Description: Return service-specific information back to to the application. This function is called by the service manager immediately after LoadLibary(); Arguments: ISMSERVICEINFO * psi : Service information returned. Return Value: Error return code --*/ { if (ISM_NO_VTABLE_ENTRY(ISM_QUERY_SERVICE_INFO)) { return ERROR_CAN_NOT_COMPLETE; } DWORD err = ISM_VTABLE(ISM_QUERY_SERVICE_INFO)(psi); if (RequiresSuperDll() && err == ERROR_SUCCESS) { // // This service is superceded. Add "downlevel" to // service name, if there's room. // CString strDL; VERIFY(strDL.LoadString(IDS_DOWNLEVEL)); if (lstrlen(psi->atchShortName) + strDL.GetLength() < MAX_SNLEN) { lstrcat(psi->atchShortName, (LPCTSTR)strDL); } } return err; } DWORD CServiceInfo::ISMQueryServerInfo( IN LPCTSTR lpszServerName, OUT ISMSERVERINFO * psi ) /*++ Routine Description: Get information on a single server with regards to this service. Arguments: LPCTSTR lpszServerName : Name of server. ISMSERVERINFO * psi : Server information returned. Return Value: Error return code --*/ { DWORD err; if (HasSuperDll()) { // // This config DLL is superceded by another one. If _that_ service // is installed, then assume this one is not. // err = GetSuperDll()->ISMQueryServerInfo(lpszServerName, psi); if (err == ERROR_SUCCESS) { // // The superceding service is installed. That means this // service is not. // return ERROR_SERVICE_DOES_NOT_EXIST; } } if (ISM_NO_VTABLE_ENTRY(ISM_QUERY_SERVER_INFO)) { return ERROR_CAN_NOT_COMPLETE; } return ISM_VTABLE(ISM_QUERY_SERVER_INFO)( lpszServerName, psi ); } #else !USE_VTABLE // // CODEWORK: Most of these method below could be inlined // DWORD CServiceInfo::ISMQueryServiceInfo( OUT ISMSERVICEINFO * psi ) /*++ Routine Description: Return service-specific information back to to the application. This function is called by the service manager immediately after LoadLibary(); Arguments: ISMSERVICEINFO * psi : Service information returned. Return Value: Error return code --*/ { if (m_pfnQueryServiceInfo == NULL) { return ERROR_CAN_NOT_COMPLETE; } DWORD err = (*m_pfnQueryServiceInfo)(psi); if (RequiresSuperDll() && err == ERROR_SUCCESS) { // // This service is superceded. Add "downlevel" to // service name, if there's room. // CString strDL; VERIFY(strDL.LoadString(IDS_DOWNLEVEL)); if (lstrlen(psi->atchShortName) + strDL.GetLength() < MAX_SNLEN) { lstrcat(psi->atchShortName, (LPCTSTR)strDL); } } return err; } DWORD CServiceInfo::ISMQueryServerInfo( IN LPCTSTR lpszServerName, // Name of server. OUT ISMSERVERINFO * psi // Server information returned. ) /*++ Routine Description: Get information on a single server with regards to this service. Arguments: LPCTSTR lpszServerName : Name of server. ISMSERVERINFO * psi : Server information returned. Return Value: Error return code --*/ { DWORD err; if (HasSuperDll()) { // // This config DLL is superceded by another one. If _that_ service // is installed, then assume this one is not. // err = GetSuperDll()->ISMQueryServerInfo(lpszServerName, psi); if (err == ERROR_SUCCESS) { // // The superceding service is installed. That means this // service is not. // return ERROR_SERVICE_DOES_NOT_EXIST; } } if (m_pfnQueryServerInfo != NULL) { return (*m_pfnQueryServerInfo)(lpszServerName, psi); } return ERROR_CAN_NOT_COMPLETE; } #endif // USE_VTABLE // // New Instance Command Class // // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< CNewInstanceCmd::CNewInstanceCmd( IN CServiceInfo * pServiceInfo ) /*++ Routine Description: New instance command constructor. Build a menu command for the create new submenu. Arguments: CServiceInfo * pServiceInfo : Service info object Return Value: N/A --*/ : m_pServiceInfo(pServiceInfo) { ASSERT(m_pServiceInfo != NULL); try { CString str; VERIFY(str.LoadString(IDS_MENU_EX_NEWINSTANCE)); m_strMenuCommand.Format(str, m_pServiceInfo->GetShortName()); VERIFY(str.LoadString(IDS_MENU_TT_EX_NEWINSTANCE)); m_strTTText.Format(str, m_pServiceInfo->GetShortName()); } catch(CMemoryException * e) { e->ReportError(); e->Delete(); } } /* static */ CServiceInfo * CServerInfo::FindServiceByMask( IN ULONGLONG ullTarget, IN CObListPlus & oblServices ) /*++ Routine Description: Given the inetsloc mask, return the service this fits. Return NULL if the service was not found. Arguments: ULONGLONG ullTarget : The mask to look for CObListPlus & oblServices : List of service objects in which to look Return Value: Pointer to service object that uses this mask, or NULL if the service isn't found --*/ { CObListIter obli(oblServices); CServiceInfo * psi; // // Straight sequential search // TRACEEOLID("Looking for service with mask " << (DWORD)ullTarget); while (psi = (CServiceInfo *)obli.Next()) { if (psi->InitializedOK() && psi->UseInetSlocDiscover() && psi->QueryDiscoveryMask() == ullTarget ) { TRACEEOLID("Found it: " << psi->GetShortName()); return psi; } } // // Didn't find it.. // TRACEEOLID("Error: mask not matched up with any known service"); return NULL; } /* static */ LPCTSTR CServerInfo::CleanServerName( IN OUT CString & str ) /*++ Routine Description: Utility function to clean up a computer/hostname Arguments: CString & str : Server name to be cleaned up Return Value: Pointer to the string --*/ { #ifdef ENFORCE_NETBIOS // // Clean up name, and enforce leading slashes // str.MakeUpper(); try { if (!IS_NETBIOS_NAME(str)) { str = _T("\\\\") + str; } } catch(CMemoryException * e) { TRACEEOLID("!!!exception cleaning up server name"); e->ReportError(); e->Delete(); } #else // // If the name is NETBIOS, convert to upper case. Otherwise // the name is assumed to be a hostname, and should be // converted to lower case. // if (IS_NETBIOS_NAME(str)) { str.MakeUpper(); } else { str.MakeLower(); } #endif // ENFORCE_NETBIOS return str; } CServerInfo::CServerInfo( IN LPCTSTR lpszServerName, IN ISMSERVERINFO * psi, IN CServiceInfo * pServiceInfo ) /*++ Routine Description: Construct with a server name. This is typically in response to a single connection attempt Arguments: LPCTSTR lpszServerName : Name of this server ISMSERVERINFO * psi : Server info CServiceInfo * pServiceInfo : service that found it. Return Value: N/A --*/ : m_strServerName(lpszServerName), m_strComment( psi->atchComment), m_nServiceState(psi->nState), m_hServer(NULL), m_pService(pServiceInfo) { CServerInfo::CleanServerName(m_strServerName); if (m_pService != NULL) { // // Bind here // if (m_pService->IsK2Service()) { ASSERT(m_hServer == NULL); HRESULT hr = m_pService->ISMBind( m_strServerName, &m_hServer ); } } else { TRACEEOLID("Did not match up server with installed service"); } } CServerInfo::CServerInfo( IN LPCSTR lpszServerName, IN LPINET_SERVICE_INFO lpisi, IN CObListPlus & oblServices ) /*++ Routine Description: Construct with information from the inetsloc discover process. Construction of the CString will automatically perform the ANSI/Unicode conversion, Arguments: LPCSTR lpszServerName : Name of this server (no "\\") LPINET_SERVICE_INFO lpisi : Discovery information CObListPlus & oblServices : List of installed services Return Value: N/A --*/ : m_strServerName(lpszServerName), m_strComment(lpisi->ServiceComment), m_nServiceState(lpisi->ServiceState), m_hServer(NULL), m_pService(NULL) { CServerInfo::CleanServerName(m_strServerName); m_pService = FindServiceByMask(lpisi->ServiceMask, oblServices); if (m_pService != NULL) { if (m_pService->IsK2Service()) { ASSERT(m_hServer == NULL); HRESULT hr = m_pService->ISMBind( m_strServerName, &m_hServer ); } } else { TRACEEOLID("Did not match up server with installed service"); } } HRESULT CServerInfo::ISMRebind() /*++ Routine Description: Handle lost connection. Attempt to rebind. Arguments: None Return Value: HRESULT --*/ { HRESULT hr = S_OK; if (m_pService != NULL) { if (m_pService->IsK2Service()) { // // Must be previously bound // ASSERT(m_hServer != NULL); // // This may cause a first chance exception, because // the handle could be invalid at this stage // hr = m_pService->ISMUnbind(m_hServer); hr = m_pService->ISMBind( m_strServerName, &m_hServer ); } } return hr; } CServerInfo::CServerInfo( IN const CServerInfo & si ) /*++ Routine Description: Copy Constructor Arguments: const CServerInfo & si : Source server info object Return Value: N/A --*/ : m_strServerName(si.m_strServerName), m_strComment(si.m_strComment), m_nServiceState(si.m_nServiceState), m_hServer(NULL), m_pService(si.m_pService) { // // Bind again here // if (m_pService->IsK2Service()) { ASSERT(m_hServer == NULL); HRESULT hr = m_pService->ISMBind( m_strServerName, &m_hServer ); } } CServerInfo::~CServerInfo() /*++ Routine Description: Destruct the object. Do not free in the pointer to the service, because we don't own it. Arguments: N/A Return Value: N/A --*/ { // // Unbind here // if (m_pService->IsK2Service()) { m_pService->ISMUnbind(m_hServer); } } const CServerInfo & CServerInfo::operator=( IN const CServerInfo & si ) /*++ Routine Description: Assignment operator Arguments: const CServerInfo & si : Source server info object Return Value: Reference to this object --*/ { m_strServerName = si.m_strServerName; m_nServiceState = si.m_nServiceState; m_strComment = si.m_strComment; m_pService = si.m_pService; // // Need to rebind // if (m_pService->IsK2Service()) { ASSERT(m_hServer == NULL); HRESULT hr = m_pService->ISMBind( m_strServerName, &m_hServer ); } return *this; } BOOL CServerInfo::operator==( IN CServerInfo & si ) /*++ Routine Description: Comparision Operator Arguments: const CServerInfo & si : Server info object to be compared against Return Value: TRUE if the objects are the same, FALSE otherwise --*/ { // // Must be the same service type // if (m_pService != si.m_pService) { return FALSE; } // // And computer name // return ::lstrcmpi( QueryServerDisplayName(), si.QueryServerDisplayName() ) == 0; } DWORD CServerInfo::ChangeServiceState( IN int nNewState, OUT int * pnCurrentState, IN DWORD dwInstance ) /*++ Routine Description: Change Service State on this computer (running, stopped, paused) Arguments: int nNewState : New service state INetServiceRunning, etc int * pnCurrentState : Pointer to current state DWORD dwInstance : Instance ID whose state is to be changed Return Value: Error return code --*/ { ASSERT(m_pService != NULL); // // Allocate string with 2 terminating NULLS, // as required by the apis. // int nLen = m_strServerName.GetLength(); LPTSTR lpServers = AllocTString(nLen + 2); if (lpServers == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } ::lstrcpy(lpServers, m_strServerName); lpServers[nLen+1] = _T('\0'); // // Call the actual api (0 -- means service) // DWORD err = m_pService->ISMChangeServiceState( nNewState, pnCurrentState, dwInstance, lpServers ); FreeMem(lpServers); return err; } DWORD CServerInfo::ConfigureServer( IN HWND hWnd, IN DWORD dwInstance ) /*++ Routine Description: Perform configuration on this server Arguments: HWND hWnd : Parent window handle DWORD dwInstance : Instance ID to be configured Return Value: Error return code --*/ { ASSERT(m_pService != NULL); // // Allocate string with 2 terminating NULLS // // CODEWORK: Make this a helper function // int nLen = m_strServerName.GetLength(); LPTSTR lpServers = AllocTString(nLen + 2); if (lpServers == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } ::lstrcpy(lpServers, (LPCTSTR)m_strServerName); lpServers[nLen + 1] = _T('\0'); DWORD err = m_pService->ISMConfigureServers(hWnd, dwInstance, lpServers); FreeMem(lpServers); return err; } HRESULT CServerInfo::MMMCConfigureServer( IN PVOID lpfnProvider, IN LPARAM param, IN LONG_PTR handle, IN DWORD dwInstance ) /*++ Routine Description: Bring up the service configuration property sheets, using the MMC property mechanism. Arguments: PVOID lpfnProvider : Provider callback LPARAM param : lparam to be passed to the sheet LONG_PTR handle : console handle DWORD dwInstance : Instance number Return Value: Error return code --*/ { ASSERT(m_pService != NULL); /* // // Allocate string with 2 terminating NULLS // int nLen = m_strServerName.GetLength(); LPTSTR lpServers = AllocTString(nLen + 2); if (lpServers == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } ::lstrcpy(lpServers, (LPCTSTR)m_strServerName); lpServers[nLen + 1] = _T('\0'); */ CError err(m_pService->ISMMMCConfigureServers( m_hServer, lpfnProvider, param, handle, dwInstance )); // FreeMem(lpServers); return err; } DWORD CServerInfo::Refresh() /*++ Routine Description: Attempt to refresh the comment and server state of the server object Arguments: None Return Value: Error return code --*/ { ISMSERVERINFO si; si.dwSize = sizeof(si); CError err(m_pService->ISMQueryServerInfo( (LPCTSTR)m_strServerName, &si )); if (err.Succeeded()) { ASSERT(si.nState == INetServiceStopped || si.nState == INetServicePaused || si.nState == INetServiceRunning || si.nState == INetServiceUnknown); m_nServiceState = si.nState; m_strComment = si.atchComment; } return err; }