|
|
//***********************************************************************
// trapreg.cpp
//
// This file contains the implementation of the classes for the objects
// that are read from the registry, manipulated and written back to the
// registry.
//
// Author: Larry A. French
//
// History:
// 20-Febuary-1996 Larry A. French
// Totally rewrote it to fix the spagetti code and huge
// methods. The original author seemed to have little or
// no ability to form meaningful abstractions.
//
//
// Copyright (C) 1995, 1996 Microsoft Corporation. All rights reserved.
//
//************************************************************************
//
// Some of the interesting class implementations contained here are:
//
// CTrapReg
// This is the container class for the registry information. It is
// composed of the configuration "parameters" and an EventLog array.
//
// CXEventLogArray
// This class implements an array of CXEventLog objects, where the
// event logs are "application", "security", "system" and so on.
//
// CXEventLog
// This class implements a single event log. All information
// relevent to an event log can be accesssed through this class.
//
// CXEventSourceArray
// Each event log contains an event source array. The event source
// represents an application that can generate an Event.
//
// CXEventSource
// An eventsource represents an application that can generate some
// number of event-log events. The event source contains a CXEventArray
// and CXMessageArray. The CXEventArray contains all the events
// that will be converted to traps. The CXMessageArray contains all the
// possible messages that a particular event source can generate.
//
// CXMessageArray
// This class implements an array of CXMessage objects.
//
// CXMessage
// This class contains all the information relevent to a message
// that a message source can generate.
//
//
// CXEventArray
// This class implements an array of CXEvent objects.
//
// CXEvent
// This class represents an event that the user has selected to be
// converted to a trap. The event contains a message plus some
// additional information.
//
//**************************************************************************
// The Registry:
//
// These classes are loaded from the registry and written back to the
// registry when the user clicks OK. To understand the format of the
// registry, use the registry editor while looking though the "Serialize"
// and "Deserialize" member function for each of these classes.
//**************************************************************************
#include "stdafx.h"
#include "trapreg.h"
#include "regkey.h"
#include "busy.h"
#include "utils.h"
#include "globals.h"
#include "utils.h"
#include "dlgsavep.h"
#include "remote.h"
///////////////////////////////////////////////////////////////////
// Class: CBaseArray
//
// This class extends the CObArray class by adding the DeleteAll
// method.
//
//////////////////////////////////////////////////////////////////
//****************************************************************
// CBaseArray::DeleteAll
//
// Delete all the objects contained in this array.
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//
//****************************************************************
void CBaseArray::DeleteAll() { LONG nObjects = (LONG)GetSize(); for (LONG iObject = nObjects-1; iObject >= 0; --iObject) { CObject* pObject = GetAt(iObject); delete pObject; }
RemoveAll(); }
/////////////////////////////////////////////////////////////////////////////////////
// Class: CTrapReg
//
// This is the container class for all the registry information for eventrap.exe.
//
////////////////////////////////////////////////////////////////////////////////////
CTrapReg::CTrapReg() : m_pdlgLoadProgress(FALSE), m_pbtnApply(FALSE) { m_bNeedToCloseKeys = FALSE; m_pdlgSaveProgress = NULL; m_pdlgLoadProgress = NULL; m_bDidLockRegistry = FALSE; m_bRegIsReadOnly = FALSE; SetDirty(FALSE); m_nLoadSteps = LOAD_STEPS_IN_TRAPDLG;
m_bShowConfigTypeBox = TRUE; m_dwConfigType = CONFIG_TYPE_CUSTOM; }
CTrapReg::~CTrapReg() { delete m_pdlgSaveProgress; delete m_pdlgLoadProgress;
if (!g_bLostConnection) { if (m_bDidLockRegistry) { UnlockRegistry(); }
if (m_bNeedToCloseKeys) { m_regkeySource.Close(); m_regkeySnmp.Close(); m_regkeyEventLog.Close(); } } }
//*********************************************************************************
// CTrapReg::SetConfigType
//
// Set the configuration type to CONFIG_TYPE_CUSTOM or CONFIG_TYPE_DEFAULT
// When the configuration type is changed, the change is reflected in the
// registry immediately so that the config tool can know whether or not it
// should update the event to trap configuration.
//
// Parameters:
// DWORD dwConfigType
// This parameter must be CONFIG_TYPE_CUSTOM or CONFIG_TYPE_DEFAULT.
//
// Returns:
// SCODE
// S_OK if the configuration type was set, otherwise E_FAIL.
//
//*********************************************************************************
SCODE CTrapReg::SetConfigType(DWORD dwConfigType) { ASSERT(dwConfigType==CONFIG_TYPE_CUSTOM || dwConfigType==CONFIG_TYPE_DEFAULT_PENDING); if (dwConfigType != m_dwConfigType) { SetDirty(TRUE); } m_dwConfigType = dwConfigType; return S_OK; }
//*********************************************************************************
// CTrapReg::LockRegistry
//
// Lock the registry to prevent two concurrent edits of the event-to-trap configuration
// information.
//
// Parameters:
// None.
//
// Returns:
// SCODE
// S_OK if successful.
// E_FAIL if the configuration information was already locked.
// E_REGKEY_NO_CREATE if the "CurrentlyOpen" registry key can't
// be created.
//
//**********************************************************************************
SCODE CTrapReg::LockRegistry() { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
CRegistryKey regkey; if (m_regkeyEventLog.GetSubKey(SZ_REGKEY_CURRENTLY_OPEN, regkey)) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
if (AfxMessageBox(IDS_ERR_REGISTRY_BUSY, MB_YESNO | MB_ICONSTOP | MB_DEFBUTTON2) == IDNO) { regkey.Close(); return E_FAIL; } }
// Create the "CurrentlyOpen" key as a volatile key so that it will disappear the next
// time the machine is restarted in the event that the application that locked the
// event-to-trap configuration crashed before it could clear this lock.
if (!m_regkeyEventLog.CreateSubKey(SZ_REGKEY_CURRENTLY_OPEN, regkey, NULL, NULL, TRUE)) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
AfxMessageBox(IDS_WARNING_CANT_WRITE_CONFIG, MB_OK | MB_ICONSTOP); return E_REGKEY_NO_CREATE; } regkey.Close(); m_bDidLockRegistry = TRUE; return S_OK; }
//***********************************************************************
// CTrapReg::UnlockRegistry
//
// Unlock the event-to-trap configuration so that others can edit it.
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//
//***********************************************************************
void CTrapReg::UnlockRegistry() { m_regkeyEventLog.DeleteSubKey(SZ_REGKEY_CURRENTLY_OPEN); }
//***********************************************************************
// CTrapReg::Connect
//
// Connect to a registry. The registry may exist on a remote computer.
//
// Parameters:
// LPCTSTR pszComputerName
// The computer who's registry you want to edit. An empty string
// specifies a request to connect to the local machine.
//
// Returns:
// SCODE
// S_OK if the connection was made.
// E_FAIL if an error occurred. In this event, the appropriate
// error message boxes will have already been displayed.
//
//***********************************************************************
SCODE CTrapReg::Connect(LPCTSTR pszComputerName, BOOL bIsReconnecting) { SCODE sc;
g_bLostConnection = FALSE;
if (pszComputerName) { m_sComputerName = pszComputerName; }
// There are eight steps here, plus there are three initial steps in
// CTrapReg::Deserialize. After that the step count will be reset
// and then stepped again for each log where each log will have
// ten sub-steps.
if (!bIsReconnecting) { m_pdlgLoadProgress->SetStepCount(LOAD_STEP_COUNT); }
CRegistryValue regval; CRegistryKey regkeyEventLog;
if (m_regkeySource.Connect(pszComputerName) != ERROR_SUCCESS) { if (m_regkeySource.m_lResult == ERROR_ACCESS_DENIED) { AfxMessageBox(IDS_ERR_REG_NO_ACCESS, MB_OK | MB_ICONSTOP); return E_ACCESS_DENIED; } goto CONNECT_FAILURE; }
if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
if (m_regkeySnmp.Connect(pszComputerName) != ERROR_SUCCESS) { if (m_regkeySnmp.m_lResult == ERROR_ACCESS_DENIED) { AfxMessageBox(IDS_ERR_REG_NO_ACCESS, MB_OK | MB_ICONSTOP); return E_ACCESS_DENIED; } goto CONNECT_FAILURE; } if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
// SOFTWARE\\Microsoft\\SNMP_EVENTS
if (m_regkeySnmp.Open(SZ_REGKEY_SNMP_EVENTS, KEY_READ | KEY_WRITE | KEY_CREATE_SUB_KEY) != ERROR_SUCCESS) { if (m_regkeySnmp.Open(SZ_REGKEY_SNMP_EVENTS, KEY_READ) == ERROR_SUCCESS) { m_bRegIsReadOnly = TRUE; } else { // At this point we know the SNMP_EVENTS key could not be opened. This
// could either be because we don't have access to the registry or we
// weren't installed yet. We now check to see if we can access the
// registry at all.
CRegistryKey regkeyMicrosoft; if (regkeyMicrosoft.Open(SZ_REGKEY_MICROSOFT, KEY_READ) == ERROR_SUCCESS) { regkeyMicrosoft.Close(); AfxMessageBox(IDS_ERR_NOT_INSTALLED, MB_OK | MB_ICONSTOP); } else { // We couldn't even access SOFTWARE\Microsoft, so we know that
// we don't have access to the registry.
AfxMessageBox(IDS_ERR_REG_NO_ACCESS, MB_OK | MB_ICONSTOP); return E_ACCESS_DENIED; } } return E_FAIL;
} if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
// SYSTEM\\CurrentControlSet\\Services\\EventLog
if (m_regkeySource.Open(SZ_REGKEY_SOURCE_EVENTLOG, KEY_ENUMERATE_SUB_KEYS | KEY_READ | KEY_QUERY_VALUE ) != ERROR_SUCCESS) { m_regkeySnmp.Close(); AfxMessageBox(IDS_ERR_REG_NO_ACCESS, MB_OK | MB_ICONSTOP); return E_ACCESS_DENIED; }
if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
if (!m_regkeySnmp.GetSubKey(SZ_REGKEY_EVENTLOG, m_regkeyEventLog)) { if (m_regkeySnmp.m_lResult == ERROR_ACCESS_DENIED) { AfxMessageBox(IDS_ERR_REG_NO_ACCESS, MB_OK | MB_ICONSTOP); sc = E_ACCESS_DENIED; } else { AfxMessageBox(IDS_WARNING_CANT_READ_CONFIG, MB_OK | MB_ICONSTOP); sc = E_REGKEY_NOT_FOUND; } m_regkeySnmp.Close(); m_regkeySource.Close(); return sc; }
if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
m_bNeedToCloseKeys = TRUE;
sc = LockRegistry();
if (FAILED(sc)) { if (sc == E_REGKEY_LOST_CONNECTION) { return sc; } else { return E_REGKEY_NO_CREATE; } } if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
m_bShowConfigTypeBox = TRUE;
if (FAILED(sc)) { if (sc == E_ACCESS_DENIED) { AfxMessageBox(IDS_ERR_REG_NO_ACCESS, MB_OK | MB_ICONSTOP); return E_ACCESS_DENIED; } else { goto CONNECT_FAILURE; } } if (!bIsReconnecting) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps; }
return S_OK;
CONNECT_FAILURE: CString sMessage; sMessage.LoadString(IDS_CANTCONNECT); if (pszComputerName != NULL) { sMessage += pszComputerName; } AfxMessageBox((LPCTSTR) sMessage, MB_OK | MB_ICONSTOP); return E_FAIL; }
//****************************************************************************
// CTrapReg::BuildSourceHasTrapsMap
//
// This method fills the m_mapEventSources CMapStringToPtr object with the
// names of all the event sources that actually have events configured for them.
// When this map is used later, we only need to know whether or not a particular
// entry exists in the map, so the value associated with each entry is irrelevant.
//
// Why do we need m_mapEventSources? The reason is that we need a quick way to
// determine whether or not a particular source has events configured for it.
// This is used when all the event sources are being enumerated and we need to know
// whether or not to load the messages for the event source (an expensive operation).
// If a particular event source has events configured for it, then we need to load
// the messages so that the message text can be displayed. This is because the
// event configuration stored in the registry only contains the event id and not the
// message text.
//
// Parameters:
// None.
//
// Returns:
// SCODE
// S_OK if successful, otherwise E_FAIL.
//
//******************************************************************************
SCODE CTrapReg::BuildSourceHasTrapsMap() {
CRegistryKey regkey; if (!g_reg.m_regkeySnmp.GetSubKey(SZ_REGKEY_SOURCES, regkey)) { // For a fresh installation, there is no source subkey.
if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } return S_OK; }
CStringArray* pasEventSources = regkey.EnumSubKeys(); regkey.Close();
if (pasEventSources == NULL) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } return S_OK; }
CString sEventSource; LONG nEventSources = (LONG)pasEventSources->GetSize(); for (LONG iEventSource = 0; iEventSource < nEventSources; ++iEventSource) { sEventSource = pasEventSources->GetAt(iEventSource); sEventSource.MakeUpper(); m_mapSourceHasTraps.SetAt(sEventSource, NULL); } delete pasEventSources; return S_OK; }
//**************************************************************************
// CTrapReg::Deserialize
//
// Read all the registry information (not including the event source messages) that
// is required by eventrap.exe into this object. Reading the messages for most
// event sources is delayed until the user actually requests it by selecting
// an event source in the event source tree control. If an event source has
// events that are being mapped into traps, then the messages for that event
// source are loaded because an event description in the registry does not contain
// the message text.
//
// Parameters:
// None.
//
// Returns:
// SCODE
// S_OK if successful.
// E_FAIL if a failure was detected. In the event of a failure, all
// of the appropriate message boxes will have been displayed.
//
//***************************************************************************
SCODE CTrapReg::Deserialize() { m_bSomeMessageWasNotFound = FALSE; SetDirty(FALSE);
// Get the value for the configuration type.
CRegistryValue regval; if (m_regkeyEventLog.GetValue(SZ_NAME_REGVAL_CONFIGTYPE, regval)) { m_dwConfigType = *(DWORD*)regval.m_pData; } else { if (g_bLostConnection) { AfxMessageBox(IDS_ERROR_NOT_RESPONDING); return E_REGKEY_LOST_CONNECTION; }
// If the config type value doesn't exist, assume a custom configuration.
// This can happen because the setup program doesn't necessarily create
// this value.
m_dwConfigType = CONFIG_TYPE_CUSTOM; } if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps;
SCODE sc = BuildSourceHasTrapsMap(); if (SUCCEEDED(sc)) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps;
// Load the event log list, the current event list and so on.
sc = m_params.Deserialize(); if (sc == S_LOAD_CANCELED) { return sc; }
if (SUCCEEDED(sc)) { if (m_pdlgLoadProgress->StepProgress()) { return S_LOAD_CANCELED; } ++m_nLoadSteps;
sc = m_aEventLogs.Deserialize(); if (sc == S_LOAD_CANCELED) { return sc; }
if (SUCCEEDED(sc)) { if (m_nLoadSteps < LOAD_STEP_COUNT) { if (m_pdlgLoadProgress->StepProgress(LOAD_STEP_COUNT - m_nLoadSteps)) { return S_LOAD_CANCELED; } } } } }
if (FAILED(sc)) { if (sc == E_REGKEY_LOST_CONNECTION) { AfxMessageBox(IDS_ERROR_NOT_RESPONDING); } else { AfxMessageBox(IDS_WARNING_CANT_READ_CONFIG); }
} return sc; }
//**************************************************************************
// CTrapReg::GetSaveProgressStepCount
//
// Get the number of steps for the save progress dialog. The number of steps
// is the number of events that will be written to SNMP_EVENTS\EventLog in
// the registry.
//
// Parameters:
// None.
//
// Returns:
// The number of steps to use for the save progress dialog.
//
//*************************************************************************
LONG CTrapReg::GetSaveProgressStepCount() { LONG nSteps = 0; LONG nEventLogs = m_aEventLogs.GetSize(); for (LONG iEventLog = 0; iEventLog < nEventLogs; ++iEventLog) { CXEventLog* pEventLog = m_aEventLogs[iEventLog];
LONG nEventSources = pEventLog->m_aEventSources.GetSize(); for (LONG iEventSource = 0; iEventSource < nEventSources; ++iEventSource) { CXEventSource* pEventSource = pEventLog->m_aEventSources.GetAt(iEventSource); nSteps += pEventSource->m_aEvents.GetSize(); } } return nSteps; }
//**************************************************************************
// CTrapReg::Serialize
//
// Write eventrap's current configuration out to the registry.
//
// Parameters:
// None.
//
// Returns:
// SCODE
// S_OK if successful.
// E_FAIL if a failure was detected. In the event of a failure, all
// of the appropriate message boxes will have been displayed.
//
//***************************************************************************
SCODE CTrapReg::Serialize() { SCODE sc; if (g_bLostConnection) { sc = Connect(m_sComputerName, TRUE); if (FAILED(sc)) { if (g_bLostConnection) { AfxMessageBox(IDS_ERROR_NOT_RESPONDING); return E_REGKEY_LOST_CONNECTION; } return S_SAVE_CANCELED; } }
if (!m_bIsDirty) { // The configuration state was not changed, so there is nothing to do.
return S_OK; }
LONG nProgressSteps = GetSaveProgressStepCount(); if (nProgressSteps > 0) { m_pdlgSaveProgress = new CDlgSaveProgress; m_pdlgSaveProgress->Create(IDD_SAVE_PROGRESS);
m_pdlgSaveProgress->SetStepCount( nProgressSteps ); }
CRegistryValue regval; regval.Set(SZ_NAME_REGVAL_CONFIGTYPE, REG_DWORD, sizeof(DWORD), (LPBYTE)&m_dwConfigType); if (!m_regkeyEventLog.SetValue(regval)) { if (g_bLostConnection) { AfxMessageBox(IDS_ERROR_NOT_RESPONDING); sc = E_REGKEY_LOST_CONNECTION; } else { AfxMessageBox(IDS_WARNING_CANT_WRITE_CONFIG); sc = S_SAVE_CANCELED; } } else { sc = m_aEventLogs.Serialize(); if (sc != S_SAVE_CANCELED) {
if (SUCCEEDED(sc)) { sc = m_params.Serialize(); }
if (sc != S_SAVE_CANCELED) SetDirty(FALSE);
if (FAILED(sc)) { if (g_bLostConnection) { AfxMessageBox(IDS_ERROR_NOT_RESPONDING); } else { AfxMessageBox(IDS_WARNING_CANT_WRITE_CONFIG); } } } }
delete m_pdlgSaveProgress; m_pdlgSaveProgress = NULL; return sc; }
void CTrapReg::SetDirty(BOOL bDirty) { m_bIsDirty = bDirty; if (m_pbtnApply) { m_pbtnApply->EnableWindow(m_bIsDirty); } }
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
// Class: CTrapParams
//
// This class represents the information stored in the
// SNMP_EVENTS\EventLog\Parameters registry key.
//
// Question: Why is it that the horizontal space in the gap between
// the lines at the top of this header appears to be very irregular?
//////////////////////////////////////////////////////////////////
//****************************************************************
// CTrapParams::CTrapParams
//
// Constructor for CTrapParams.
//
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//
//****************************************************************
CTrapParams::CTrapParams() { m_trapsize.m_bTrimFlag = TRUE; m_trapsize.m_dwMaxTrapSize = 4096; m_trapsize.m_bTrimMessages = FALSE; }
//********************************************************************
// CTrapParams::Deserialize
//
// Read the contents of this CTrapParams object from the registry.
//
// Parameters:
// None.
//
// Returns:
// SCODE
// S_OK if successful.
// E_FAIL if there was a problem reading the required information
// from the registry.
//********************************************************************
SCODE CTrapParams::Deserialize() { CRegistryKey regkeyParams; if (!g_reg.m_regkeySnmp.GetSubKey(SZ_REGKEY_PARAMETERS, regkeyParams)) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_REGKEY_NOT_FOUND; } }
CRegistryValue regval;
// !!!CR: There is no longer any reason to load the BASE OID
if (!regkeyParams.GetValue(SZ_REGKEY_PARAMS_BASE_ENTERPRISE_OID, regval)) goto REGISTRY_FAILURE; m_sBaseEnterpriseOID = (LPCTSTR)regval.m_pData;
if (!regkeyParams.GetValue(SZ_REGKEY_PARAMS_TRIMFLAG, regval)) m_trapsize.m_bTrimFlag = FALSE; else m_trapsize.m_bTrimFlag = (*(DWORD*)regval.m_pData == 1);
if (!regkeyParams.GetValue(SZ_REGKEY_PARAMS_MAXTRAP_SIZE, regval)) m_trapsize.m_dwMaxTrapSize = MAX_TRAP_SIZE; else m_trapsize.m_dwMaxTrapSize = *(DWORD*)regval.m_pData;
if (!regkeyParams.GetValue(SZ_REGKEY_PARAMS_TRIM_MESSAGE, regval)) m_trapsize.m_bTrimMessages = TRUE; else m_trapsize.m_bTrimMessages = (*(DWORD*)regval.m_pData) != 0;
if (!regkeyParams.GetValue(SZ_REGKEY_PARAMS_THRESHOLDENABLED, regval)) m_throttle.m_bIsEnabled = TRUE; else m_throttle.m_bIsEnabled = (*(DWORD*)regval.m_pData) != THROTTLE_DISABLED;
// Threshold trap count.
if (!regkeyParams.GetValue(SZ_REGKEY_PARAMS_THRESHOLDCOUNT, regval) || *(DWORD*)regval.m_pData < 2) m_throttle.m_nTraps = THRESHOLD_COUNT; else m_throttle.m_nTraps = *(DWORD*)regval.m_pData;
// Threshold time in seconds
if (!regkeyParams.GetValue(SZ_REGKEY_PARAMS_THRESHOLDTIME, regval)) m_throttle.m_nSeconds = THRESHOLD_TIME; else m_throttle.m_nSeconds = *(DWORD*)regval.m_pData;
if (regkeyParams.Close() != ERROR_SUCCESS) { goto REGISTRY_FAILURE; } return S_OK;
REGISTRY_FAILURE: if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_FAIL; } }
//****************************************************************
// CTrapParams::Serialize
//
// Write SNMP_EVENTS\EventLog\Parameters information to the
// registry.
//
// Parameters:
// None.
//
// Returns:
// S_OK if everything went OK.
// E_REGKEY_NOT_FOUND if an expected registry key was missing.
//*****************************************************************
SCODE CTrapParams::Serialize() { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
// Open the Parameters key.
// Create simply opens the key if already present.
CRegistryKey regkey; if (!g_reg.m_regkeySnmp.CreateSubKey(SZ_REGKEY_PARAMETERS, regkey)) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_REGKEY_NOT_FOUND; } }
CRegistryValue regval;
// Save the Message Length and the TrimMessage.
DWORD dwTrim; if (m_trapsize.m_bTrimFlag) dwTrim = 1; else dwTrim = 0; regval.Set(SZ_REGKEY_PARAMS_TRIMFLAG, REG_DWORD, sizeof(DWORD), (LPBYTE)&dwTrim); regkey.SetValue(regval); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
if (m_trapsize.m_bTrimFlag) { // Save the maximum trap size
regval.Set(SZ_REGKEY_PARAMS_MAXTRAP_SIZE, REG_DWORD, sizeof(DWORD), (LPBYTE)&m_trapsize.m_dwMaxTrapSize); regkey.SetValue(regval); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
// Save the trim message length
DWORD dwTrimMessages = m_trapsize.m_bTrimMessages; regval.Set(SZ_REGKEY_PARAMS_TRIM_MESSAGE, REG_DWORD, sizeof(DWORD), (LPBYTE)&dwTrimMessages); regkey.SetValue(regval); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } }
// Threshold enabled flag
DWORD dwValue = (m_throttle.m_bIsEnabled ? THROTTLE_ENABLED : THROTTLE_DISABLED); regval.Set(SZ_REGKEY_PARAMS_THRESHOLDENABLED, REG_DWORD, sizeof(DWORD), (LPBYTE)&dwValue); regkey.SetValue(regval); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
// If throttle is not enabled, do not write the ThresholdCount and ThresholdTime parameters
if (m_throttle.m_bIsEnabled) { // Threshold trap count.
regval.Set(SZ_REGKEY_PARAMS_THRESHOLDCOUNT, REG_DWORD, sizeof(DWORD), (LPBYTE)&m_throttle.m_nTraps); regkey.SetValue(regval); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
// Threshold time in seconds
regval.Set(SZ_REGKEY_PARAMS_THRESHOLDTIME, REG_DWORD, sizeof(DWORD), (LPBYTE)&m_throttle.m_nSeconds); regkey.SetValue(regval); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } }
regkey.Close(); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } return S_OK; }
//*******************************************************************
// CTrapParams::ResetExtensionAgent
//
// Reset the extension agent. This is done by setting the "Threshold"
// parameter to zero in the registry. The extension agent monitors this
// value and will reset itself when a zero is written there.
//
// The user may want to reset the extension agent if its throttle limit
// has been tripped.
//
// Parameters:
// None.
//
// Returns:
// SCODE
// S_OK if successful. E_FAIL if the extension agent could not
// be reset. If a failure occurs, the appropriate message box
// is displayed.
//
//*********************************************************************
SCODE CTrapParams::ResetExtensionAgent() { CRegistryKey regkey; if (!g_reg.m_regkeySnmp.GetSubKey(SZ_REGKEY_PARAMETERS, regkey)) { return E_REGKEY_NOT_FOUND; } CRegistryValue regval;
// Set the "Threshold" value under the Parameters key to zero to reset
// the extension agent.
DWORD dwValue = THROTTLE_RESET; SCODE sc = S_OK; regval.Set(SZ_REGKEY_PARAMS_THRESHOLD, REG_DWORD, sizeof(DWORD), (LPBYTE)&dwValue); if (!regkey.SetValue(regval)) { AfxMessageBox(IDS_WARNING_CANT_WRITE_CONFIG); sc = E_FAIL; }
regkey.Close(); return sc; }
//***********************************************************************
// CTrapParams::ThrottleIsTripped
//
// Check the registry to determine whether or not the extension agent
// throttle was tripped.
//
// Parameters:
// None.
//
// Returns:
// TRUE if the extension agent's throttle was tripped, FALSE otherwise.
//
//************************************************************************
BOOL CTrapParams::ThrottleIsTripped() { CRegistryKey regkey; if (!g_reg.m_regkeySnmp.GetSubKey(SZ_REGKEY_PARAMETERS, regkey)) { return FALSE; } CRegistryValue regval;
// SNMP_EVENTS\Parameters\Threshold value
BOOL bThrottleIsTripped = FALSE; if (regkey.GetValue(SZ_REGKEY_PARAMS_THRESHOLD, regval)) { if (*(DWORD*)regval.m_pData == THROTTLE_TRIPPED) { bThrottleIsTripped = TRUE; } }
regkey.Close(); return bThrottleIsTripped; }
///////////////////////////////////////////////////////////////////
// Class: CXEventLogArray
//
// This class implements an array of CXEventLog objects.
//
//////////////////////////////////////////////////////////////////
//****************************************************************
// CXEventLogArray::Deserialize
//
// Examine the registry find all the event logs and load all the
// relevent information for all the event logs into this array.
//
// Parameters:
// None.
//
// Returns:
// S_OK if successful.
// E_FAIL if a failure was detected.
//
//****************************************************************
SCODE CXEventLogArray::Deserialize() { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
CStringArray* pasEventLogs = g_reg.m_regkeySource.EnumSubKeys(); // Prefix bug 445192
if (pasEventLogs == NULL) return E_FAIL; SCODE sc = S_OK;
// Iterate through all the event log names and create each log.
LONG nEventLogs = (LONG)pasEventLogs->GetSize(); if (nEventLogs > 0) { g_reg.m_nLoadStepsPerLog = LOAD_LOG_ARRAY_STEP_COUNT / nEventLogs; } LONG nUnusedSteps = LOAD_LOG_ARRAY_STEP_COUNT - (nEventLogs * g_reg.m_nLoadStepsPerLog);
for (LONG iEventLog=0; iEventLog < nEventLogs; ++iEventLog) { CString sEventLog = pasEventLogs->GetAt(iEventLog); CXEventLog* pEventLog = new CXEventLog(sEventLog); sc = pEventLog->Deserialize(); if ((sc==S_LOAD_CANCELED) || FAILED(sc)) { delete pEventLog; break; } else if (sc == S_NO_SOURCES) { delete pEventLog; sc = S_OK; } else { Add(pEventLog); } } delete pasEventLogs; if (g_reg.m_pdlgLoadProgress->StepProgress(nUnusedSteps)) { sc = S_LOAD_CANCELED; }
return sc; }
//****************************************************************
// CXEventLogArray::Serialize
//
// Write the current configuration of all the EventLogs out to the
// registry. Only those logs and sources that actually have events
// are written.
//
// Parameters:
// None.
//
// Returns:
// S_OK if successful.
// E_FAIL if a failure was detected.
//
//****************************************************************
SCODE CXEventLogArray::Serialize() { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
// This is where the eventlog stuff should be cleaned up.
CRegistryKey regkey; if (!g_reg.m_regkeySnmp.CreateSubKey(SZ_REGKEY_EVENTLOG, regkey)) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_REGKEY_NOT_FOUND; } } regkey.Close();
if (!g_reg.m_regkeySnmp.CreateSubKey(SZ_REGKEY_SOURCES, regkey)) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_REGKEY_NOT_FOUND; } }
// Delete the keys for the sources and events for which we no longer
// trap. I'm going to be lazy and just delete them all.
// !!!CR: It could potentially save a lot of time if this was made smarter
// !!!CR: so that it only replaced items that had been deleted.
LONG nEventSources, iEventSource; CStringArray* pasEventSources = regkey.EnumSubKeys(); nEventSources = (LONG)pasEventSources->GetSize(); for (iEventSource=0; iEventSource<nEventSources; iEventSource++) { CString sSource; sSource = pasEventSources->GetAt(iEventSource); regkey.DeleteSubKey(sSource); } delete pasEventSources;
SCODE sc = S_OK; LONG nEventLogs = GetSize(); for (LONG iEventLog = 0; iEventLog < nEventLogs; ++iEventLog) { sc = GetAt(iEventLog)->Serialize(regkey); if (sc == S_SAVE_CANCELED) { break; } else if (g_bLostConnection) { sc = E_REGKEY_LOST_CONNECTION; break; } } regkey.Close();
return sc; }
//****************************************************************
// CXEventLogArray::FindEventSource
//
// Given the name of an event log and the name of the event source
// within the event log, return a pointer to the requested CXEventSource.
//
// Parameters:
// CString& sLog
// The name of the event log.
//
// CString& sEventSource
// The name of the event source.
//
// Returns:
// CXEventSource*
// A pointer to the requested event source if it was found. NULL
// if no such event source exists.
//
//****************************************************************
CXEventSource* CXEventLogArray::FindEventSource(CString& sLog, CString& sEventSource) { LONG nLogs = GetSize(); for (LONG iLog = 0; iLog < nLogs; ++iLog) { CXEventLog* pEventLog = GetAt(iLog); if (pEventLog->m_sName.CompareNoCase(sLog) == 0) { return pEventLog->FindEventSource(sEventSource); } } return NULL; }
///////////////////////////////////////////////////////////////////
// Class: CXEventLog
//
// This class contains all the information for a particular event log.
//
//////////////////////////////////////////////////////////////////
//************************************************************************
// CXEventLog::Deserialize
//
// Load the contents of this EventLog object from the registry.
//
// Parameters:
// g_reg is a global parameter.
//
// Returns:
// SCODE
// S_OK or S_NO_SOURCES if successful. E_FAIL if there was
// a failure of any kind.
//
//************************************************************************
SCODE CXEventLog::Deserialize() { return m_aEventSources.Deserialize(this); }
//************************************************************************
// CXEventLog::Serialize
//
// Write the current configuration for this log to the registry.
//
// Parameters:
// CRegistryKey& regkey
// This registry key points to SOFTWARE\Microsoft\SNMP_EVENTS\EventLog
//
// Returns:
// SCODE
// S_OK or S_SAVE_CANCELED if successful. E_FAIL for an error condition.
// a failure of any kind.
//
//************************************************************************
SCODE CXEventLog::Serialize(CRegistryKey& regkey) { return m_aEventSources.Serialize(regkey); }
///////////////////////////////////////////////////////////////////
// Class: CXEventSourceArray
//
// This class implements an array of CXEventSource pointers and
// related methods.
//
//////////////////////////////////////////////////////////////////
//*************************************************************************
// CXEventSourceArray::Deserialize
//
// Load all the information pertaining to the event sources associated with
// the given event log. This information is loaded from the registry.
//
// Parameters:
// CXEventLog* pEventLog
// Pointer to the event log. The sources associated with this
// event log are loaded into this object.
//
// Returns:
// SCODE
// S_OK or S_NO_SOURCES if successful. E_FAIL if there was
// a failure of any kind.
//*************************************************************************
SCODE CXEventSourceArray::Deserialize(CXEventLog* pEventLog) {
// Get the registry entry for this log. This registry key will be
// used to enumerate the event sources for this log.
CRegistryKey regkey; if (!g_reg.m_regkeySource.GetSubKey(pEventLog->m_sName, regkey)) { if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerLog)) { return S_LOAD_CANCELED; } if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_FAIL; } }
SCODE sc = S_OK;
// Enumerate the event sources for this log.
CStringArray* pasSources = regkey.EnumSubKeys(); if (pasSources == NULL) { regkey.Close(); if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerLog)) { return S_LOAD_CANCELED; } if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_FAIL; } }
// Iterate though all the event sources and add them as a sub-item
// under the log.
LONG nEventSources = (LONG)pasSources->GetSize(); LONG nScaledStepSize = 0; g_reg.m_nLoadStepsPerSource = 0; if (nEventSources > 0) { nScaledStepSize = (g_reg.m_nLoadStepsPerLog * 1000) / nEventSources; g_reg.m_nLoadStepsPerSource = g_reg.m_nLoadStepsPerLog / nEventSources; } LONG nLoadSteps = 0; LONG nProgress = 0;
// Set the load progress step count. Since we don't know how many events are saved
// for each event source, we will assume some small number for LOAD_STEPS_FOR_SOURCE
// and divide the actual number of steps up as evenly as possible once we know the actual
// event count.
for (LONG iEventSource=0; iEventSource< nEventSources; ++iEventSource) { nProgress += nScaledStepSize; g_reg.m_nLoadStepsPerSource = nProgress / 1000; if (g_reg.m_nLoadStepsPerSource > 0) { nProgress -= g_reg.m_nLoadStepsPerSource * 1000; nLoadSteps += g_reg.m_nLoadStepsPerSource; }
CString sEventSource = pasSources->GetAt(iEventSource); CXEventSource* pEventSource = new CXEventSource(pEventLog, sEventSource); sc = pEventSource->Deserialize(regkey); if ((sc==S_LOAD_CANCELED) || FAILED(sc)) { delete pEventSource; break; } else if (sc == S_NO_EVENTS) { // If there are no events, then this is not a valid event source.
delete pEventSource; sc = S_OK; } else { Add(pEventSource); } } delete pasSources; if (SUCCEEDED(sc)) { // We only close the registry key if we succeeded to avoid hanging if we loose
// a remote connection.
regkey.Close(); if (GetSize() == 0) { sc = S_NO_SOURCES; } } if (nLoadSteps < g_reg.m_nLoadStepsPerLog) { if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerLog - nLoadSteps)) { return S_LOAD_CANCELED; } g_reg.m_nLoadSteps += g_reg.m_nLoadStepsPerLog - nLoadSteps; } return sc; }
//************************************************************************
// CXEventSourceArray::Serialize
//
// Write the current configuration for this event source array to the registry.
//
// Parameters:
// CRegistryKey& regkey
// This registry key points to SOFTWARE\Microsoft\SNMP_EVENTS\EventLog\Sources
//
// Returns:
// SCODE
// S_OK or S_SAVE_CANCELED if successful. E_FAIL for an error condition.
// a failure of any kind.
//
//************************************************************************
SCODE CXEventSourceArray::Serialize(CRegistryKey& regkey) { // Write the subkeys under SNMP_EVENTS\EventLog
SCODE sc = S_OK; LONG nEventSources = GetSize(); for (LONG iEventSource = 0; iEventSource < nEventSources; ++iEventSource) { SCODE scTemp = GetAt(iEventSource)->Serialize(regkey); if (g_bLostConnection) { sc = E_REGKEY_LOST_CONNECTION; break; } if (FAILED(scTemp)) { sc = E_FAIL; break; } if (scTemp == S_SAVE_CANCELED) { sc = S_SAVE_CANCELED; break; } } return sc; }
//************************************************************************
// CXEventSourceArray::FindEventSource
//
// Given an event source name, find the specified event source in this
// event source array.
//
// Parameters:
// CString& sEventSource
// The name of the event source to search for.
//
// Returns:
// CXEventSource*
// Pointer to the event source if it is found, otherwise NULL.
//
//***********************************************************************
CXEventSource* CXEventSourceArray::FindEventSource(CString& sEventSource) { LONG nSources = GetSize(); for (LONG iSource = 0; iSource < nSources; ++iSource) { CXEventSource* pEventSource = GetAt(iSource); if (pEventSource->m_sName.CompareNoCase(sEventSource)==0) { return pEventSource; } } return NULL; }
///////////////////////////////////////////////////////////////////
// Class: CXEventSource
//
// This class implements an an event source object. An event source
// corresponds to an application that can generate events. The
// event sources are enumerated from the registry in
// "SYSTEM\CurrentControlSet\Services\EventLogs" under each particular
// eventlog found there.
//
// An event source has an array of messages and an array of events
// associated with it.
//
// The message array comes from the message .DLL file(s) pointed to by
// the "EventMessageFile" value attached to the source's key in the registry.
// The message array is read-only in the sense that it is loaded from the
// registry and never written back to it.
//
// The event array comes from SNMP_EVENTS\EventLog\<source-subkey>. These
// events are loaded when the configuration program starts up and written
// back out when the user clicks "OK". Note that the events stored in the
// registry contain the event ID, but not the message text. The message text
// for an event is found by searching the message array in the CXEventSource
// object for the event's ID.
//
//////////////////////////////////////////////////////////////////
//*************************************************************************
// CXEventSource::CXEventSource
//
// Construct the CXEventSource object.
//
// Parameters:
// CXEventLog* pEventLog
// Pointer to the event log that contains this event source.
//
// CString& sName
// The name of this event source.
//
// Returns:
// Nothing.
//
//*************************************************************************
CXEventSource::CXEventSource(CXEventLog* pEventLog, CString& sName) { m_pEventLog = pEventLog; m_sName = sName; m_aMessages.Initialize(this); }
//************************************************************************
// CXEventSource::~CXEventSource
//
// Destroy thus event source object.
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//
//************************************************************************
CXEventSource::~CXEventSource() { // We must explicitly delete the contents of the event array and message
// array. Note that this is different behavior from the CXEventLogArray
// and CXEventSourceArray. This is because it was useful to create
// message and event arrays as temporary containers for a set of pointers.
// Thus, there were situations where you did not want to delete the
// objects contained in these arrays when the arrays were destroyed.
m_aEvents.DeleteAll(); m_aMessages.DeleteAll(); }
//**********************************************************************
// CXEventSource::Deserialize
//
// Load this event source from the registry given the registry key
// for the event log that contains this source.
//
// Parameters:
// CRegistryKey& regkeyLog
// An open registry key for the event log containing this
// event source. This key points to somewhere in
// SYSTEM\CurrentControlSet\Services\EventLog
//
// Returns:
// SCODE
// S_OK = the source has events and no errors were encountered.
// S_NO_EVENTS = the source has no events and no errors were encountered.
// E_FAIL = an condition was encountered.
//
//***********************************************************************
SCODE CXEventSource::Deserialize(CRegistryKey& regkeyLog) { CRegistryKey regkeySource; if (!regkeyLog.GetSubKey(m_sName, regkeySource)) { if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerSource)) { return S_LOAD_CANCELED; } g_reg.m_nLoadSteps += g_reg.m_nLoadStepsPerSource; if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_FAIL; } }
SCODE sc = E_FAIL; if (SUCCEEDED(GetLibPath(regkeySource))) { sc = m_aEvents.Deserialize(this); } else { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerSource)) { return S_LOAD_CANCELED; } g_reg.m_nLoadSteps += g_reg.m_nLoadStepsPerSource; sc = S_NO_EVENTS; }
regkeySource.Close(); if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
// Delay deserializing the messages for this source until they are
// needed.
return sc; }
#if 0
//*************************************************************************
// CXEventSource::GetLibPath
//
// Get the path the the EventMessageFile for this event source.
//
// Parameters:
// CRegistryKey& regkeySource
// An open registry key corresponding to this source in
// SYSTEM\CurrentControlSet\Services\EventLog\<event log>
//
// Returns:
// SCODE
// S_OK if successful, otherwise E_FAIL.
//
//*************************************************************************
SCODE CXEventSource::GetLibPath(CRegistryKey& regkeySource) { CRegistryValue regval; if (!regkeySource.GetValue(SZ_REGKEY_SOURCE_EVENT_MESSAGE_FILE, regval)) return E_FAIL;
TCHAR szLibPath[MAX_STRING]; if (ExpandEnvironmentStrings((LPCTSTR)regval.m_pData, szLibPath, sizeof(szLibPath)) == 0) return E_FAIL;
m_sLibPath = szLibPath; return S_OK; } #else
//*************************************************************************
// CXEventSource::GetLibPath
//
// Get the path the the EventMessageFile for this event source.
//
// Parameters:
// CRegistryKey& regkeySource
// An open registry key corresponding to this source in
// SYSTEM\CurrentControlSet\Services\EventLog\<event log>
//
// Returns:
// SCODE
// S_OK if successful, otherwise E_FAIL.
//
//*************************************************************************
SCODE CXEventSource::GetLibPath(CRegistryKey& regkeySource) { static CEnvCache cache;
CRegistryValue regval; if (!regkeySource.GetValue(SZ_REGKEY_SOURCE_EVENT_MESSAGE_FILE, regval)) return E_FAIL;
SCODE sc = S_OK; if (g_reg.m_sComputerName.IsEmpty()) { // Editing the local computer computer's registry, so the local environment
// variables are in effect.
TCHAR szLibPath[MAX_STRING]; if (ExpandEnvironmentStrings((LPCTSTR)regval.m_pData, szLibPath, sizeof(szLibPath)/sizeof(szLibPath[0]))) { m_sLibPath = szLibPath; } else { sc = E_FAIL; } } else { // Editing a remote computer's registry, so the remote environment strings are in
// effect. Also, file paths must be mapped to the UNC path for the machine. For
// example, C:Foo will be mapped to \\Machine\C$\Foo
m_sLibPath = regval.m_pData; sc = RemoteExpandEnvStrings(g_reg.m_sComputerName, cache, m_sLibPath); if (SUCCEEDED(sc)) { sc = MapPathToUNC(g_reg.m_sComputerName, m_sLibPath); } }
return S_OK; }
#endif
//************************************************************************
// CXEventSource::Serialize
//
// Write the configuration information for this event source to the registry.
//
// Parameters:
// CRegistryKey& regkeyParent
// An open registry key pointing to SNMP_EVENTS\EventLog\Sources
//
// Returns:
// SCODE
// S_OK if successful.
// S_SAVE_CANCELED if no errors, but the user canceled the save.
//
//************************************************************************
SCODE CXEventSource::Serialize(CRegistryKey& regkeyParent) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
SCODE sc = S_OK; if (m_aEvents.GetSize() > 0) { CRegistryKey regkey; if (!regkeyParent.CreateSubKey(m_sName, regkey)) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } else { return E_REGKEY_NOT_FOUND; } }
CString sEnterpriseOID; GetEnterpriseOID(sEnterpriseOID); CRegistryValue regval;
regval.Set(SZ_REGKEY_SOURCE_ENTERPRISE_OID, REG_SZ, (sEnterpriseOID.GetLength() + 1) * sizeof(TCHAR), (LPBYTE)(LPCTSTR)sEnterpriseOID); regkey.SetValue(regval);
DWORD dwAppend = 1; regval.Set(SZ_REGKEY_SOURCE_APPEND, REG_DWORD, sizeof(DWORD), (LPBYTE) &dwAppend); regkey.SetValue(regval);
sc = m_aEvents.Serialize(regkey); regkey.Close(); }
if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; } return sc; }
//*******************************************************************
// CXEventSource::GetEnterpriseOID
//
// Get the enterprise OID for this event source. The enterprise OID
// is composed of a prefix and suffix string concatenated together. The
// prefix string is an ASCII decimal value for the length of the suffix
// string. The suffix string is composed by separating each character of
// the name of this source by a "." character.
//
// Parameters:
// CString& sEnterpriseOID
// A reference to the string where the enterprise OID for this
// source will be returned.
//
// Returns:
// The enterprise OID in via the sEnterpriseOID reference.
//
//********************************************************************
void CXEventSource::GetEnterpriseOID(CString& sEnterpriseOID, BOOL bGetFullID) { CString sValue;
// Form the prefix string in sEnterpriseOID and compute the length
// of the prefix and suffix strings.
DecString(sValue, m_sName.GetLength()); if (bGetFullID) { sEnterpriseOID = g_reg.m_params.m_sBaseEnterpriseOID + _T('.') + sValue; } else { sEnterpriseOID = sValue; }
// Append the suffix string to the prefix string by getting a pointer to
// the sEnterpriseOID buffer and allocating enough space to hold the
// combined strings.
LPCTSTR pszSrc = m_sName;
// Append the suffix by copying it to the destination buffer and inserting the
// "." separator characters as we go.
LONG iCh; while (iCh = *pszSrc++) { switch(sizeof(TCHAR)) { case 1: iCh &= 0x0ff; break; case 2: iCh &= 0x0ffffL; break; default: ASSERT(FALSE); break; }
DecString(sValue, iCh); sEnterpriseOID += _T('.'); sEnterpriseOID += sValue; } }
///////////////////////////////////////////////////////////////////
// Class: CXEventArray
//
// This class implements an array of pointers to CXEvent objects.
// The events contained in this array correspond to the events that
// the user has configured in the main dialog. Don't confuse events
// with messages. Events are the subset of the messages that the
// user has selected to be translated into traps.
//
// For further information on how this CXEventArray fits into the
// scheme of things, please see the CXEventSource class header.
//////////////////////////////////////////////////////////////////
//************************************************************************
// CXEventArray::Deserialize
//
// Read an array of events from the registry for the given
// source.
//
// Parameters:
// CXEventSource* pEventSource
// Pointer to the event source who's events should be read.
//
// Returns:
// SCODE
// S_OK if successful.
// E_FAIL if an error occurs.
//
//************************************************************************
SCODE CXEventArray::Deserialize(CXEventSource* pEventSource) { if (!g_reg.SourceHasTraps(pEventSource->m_sName)) { if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerSource)) { return S_LOAD_CANCELED; } g_reg.m_nLoadSteps += g_reg.m_nLoadStepsPerSource; return S_OK; }
// Control comes here if we know that there are events configured
// for the event source that this event array is part of. We now
// need to load the events for this source by enumerating them
// from SNMP_EVENTS\EventLog\<event source>
CString sKey; sKey = sKey + SZ_REGKEY_SOURCES + _T("\\") + pEventSource->m_sName; CRegistryKey regkey; if (!g_reg.m_regkeySnmp.GetSubKey(sKey, regkey)) { if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerSource)) { return S_LOAD_CANCELED; } g_reg.m_nLoadSteps += g_reg.m_nLoadStepsPerSource; return S_OK; }
// Enumerate the events for this source
CStringArray* pasEvents = regkey.EnumSubKeys(); if (pasEvents == NULL) { if (g_bLostConnection) { return E_REGKEY_LOST_CONNECTION; }
regkey.Close(); if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerSource)) { return S_LOAD_CANCELED; } g_reg.m_nLoadSteps += g_reg.m_nLoadStepsPerSource;
return E_FAIL; }
// Iterate though all the events and add them as a sub-item
// under the event source.
LONG nEvents = (LONG)pasEvents->GetSize(); LONG nStepsDone = 0; LONG nEventsPerStep = 0; if (g_reg.m_nLoadStepsPerSource > 0) { nEventsPerStep = nEvents / g_reg.m_nLoadStepsPerSource; }
for (LONG iEvent=0; iEvent< nEvents; ++iEvent) { CString sEvent = pasEvents->GetAt(iEvent); CXEvent* pEvent = new CXEvent(pEventSource); SCODE sc = pEvent->Deserialize(regkey, sEvent); if (sc == E_MESSAGE_NOT_FOUND) { delete pEvent; if (!g_reg.m_bSomeMessageWasNotFound) { AfxMessageBox(IDS_ERR_MESSAGE_NOT_FOUND, MB_OK | MB_ICONEXCLAMATION); g_reg.m_bSomeMessageWasNotFound = TRUE; g_reg.SetDirty(TRUE); }
continue; }
if ((sc == S_LOAD_CANCELED) || FAILED(sc) ) { delete pEvent; delete pasEvents; return sc; }
if (nEventsPerStep > 0) { if ((iEvent % nEventsPerStep) == (nEventsPerStep - 1)) { if (g_reg.m_pdlgLoadProgress->StepProgress()) { delete pasEvents; return S_LOAD_CANCELED; } ++g_reg.m_nLoadSteps; ++nStepsDone; } } } delete pasEvents; regkey.Close(); if (nStepsDone < g_reg.m_nLoadStepsPerSource) { if (g_reg.m_pdlgLoadProgress->StepProgress(g_reg.m_nLoadStepsPerSource - nStepsDone)) { return S_LOAD_CANCELED; } g_reg.m_nLoadSteps += g_reg.m_nLoadStepsPerSource - nStepsDone; } return S_OK; }
//************************************************************************
// CXEventArray::Serialize
//
// Write the current configuration for the events contained in this array
// out to the registry.
//
// Parameters:
// CRegistryKey& regkeyParent
// An open registry key for the source that owns these events.
// The source key is located in SNMP_EVENTS\EventLogs\<source-key>
//
// Returns:
// SCODE
// S_OK = All events saved without errors.
// S_SAVE_CANCELED = No errors, but the user canceled the save.
// E_FAIL = An error occurs.
//
//************************************************************************
SCODE CXEventArray::Serialize(CRegistryKey& regkeyParent) { SCODE sc = S_OK; LONG nEvents = GetSize(); for (LONG iEvent = 0; iEvent < nEvents; ++iEvent) { SCODE scTemp = GetAt(iEvent)->Serialize(regkeyParent); if (scTemp == S_SAVE_CANCELED) { sc = S_SAVE_CANCELED; break; }
if (FAILED(scTemp)) { if (g_bLostConnection) { sc = E_REGKEY_LOST_CONNECTION; } else { sc = E_FAIL; } break; } } return sc; }
//***********************************************************************
// CXEventArray::Add
//
// Add an event pointer to this array. Note that there is no assumption
// that the array owns the pointer. Someone must explicitly call the DeleteAll
// member to delete the pointers stored in this array.
//
// Parameters:
// CXEvent* pEvent
// Pointer to the event to add to this array.
//
// Returns:
// Nothing.
//
//***********************************************************************
void CXEventArray::Add(CXEvent* pEvent) { CBaseArray::Add(pEvent); }
//***********************************************************************
// CXEventArray::FindEvent
//
// Given an event id, find the corresponding event in this array.
//
// Note that this array should never contain duplicate events.
//
// Parameters:
// DWORD dwId
// The event ID.
//
// Returns:
// CXEvent*
// A pointer to the desired event. NULL if the event was
// not found.
//
//***********************************************************************
CXEvent* CXEventArray::FindEvent(DWORD dwId) { LONG nEvents = GetSize(); for (LONG iEvent=0; iEvent < nEvents; ++iEvent) { CXEvent* pEvent = GetAt(iEvent); if (pEvent->m_message.m_dwId == dwId) { return pEvent; } } return NULL; }
//***********************************************************************
// CXEventArray::FindEvent
//
// Given an event pointer, remove the event from this array.
//
// Parameters:
// CXEvent* pEventRemove
// A pointer to the event to remove.
//
// Returns:
// SCODE
// S_OK if the event was removed.
// E_FAIL if the event was not found in this array.
//
//***********************************************************************
SCODE CXEventArray::RemoveEvent(CXEvent* pEventRemove) { // Iterate through the event array to search for the specified event.
LONG nEvents = GetSize(); for (LONG iEvent=0; iEvent < nEvents; ++iEvent) { CXEvent* pEvent = GetAt(iEvent); if (pEvent == pEventRemove) { RemoveAt(iEvent); return S_OK; } } return E_FAIL; }
///////////////////////////////////////////////////////////////////
// Class: CXEvent
//
// This class implements an event. Events are the subset of the
// messages that the user selects to be translated into traps.
// Events, and not messages, are what the user configures.
//
// For further information on how this class fits into the
// scheme of things, please see the CXEventSource class header.
//////////////////////////////////////////////////////////////////
//*********************************************************************
// CXEvent::CXEvent
//
// Construct the event.
//
// Parameters:
// CXEventSource* pEventSource
// Pointer to the event source that has the potential to generate
// this event.
//
// Returns:
// Nothing.
//
//*********************************************************************
CXEvent::CXEvent(CXEventSource* pEventSource) : m_message(pEventSource) { m_dwCount = 0; m_dwTimeInterval = 0; m_pEventSource = pEventSource; m_pEventSource->m_aEvents.Add(this); }
//**********************************************************************
// CXEvent::CXEvent
//
// Construct an event. This form of the constructor creates an event
// directly from a CXMessage object. This is possible because the
// CXMessage object contains a back-pointer to its source.
//
// Parameters:
// CXMessage* pMessage
// Pointer to the message that is used as the event template.
//
// Returns:
// Nothing.
//**********************************************************************
CXEvent::CXEvent(CXMessage* pMessage) : m_message(pMessage->m_pEventSource) { m_pEventSource = pMessage->m_pEventSource; m_message = *pMessage; m_dwCount = 0; m_dwTimeInterval = 0; m_pEventSource->m_aEvents.Add(this); }
//**********************************************************************
// CXEvent::~CXEvent
//
// Destroy this event.
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//**********************************************************************
CXEvent::~CXEvent() { // Remove this event from the source
m_pEventSource->m_aEvents.RemoveEvent(this); }
//**********************************************************************
// CXEvent::Deserialize
//
// Read this event from the registry.
//
// Parameters:
// CRegistryKey& regkeyParent
// An open registry key pointing to the event source in
// SNMP_EVENTS\EventLog
//
// CString& sName
// The name of the event to load.
//
// Returns:
// SCODE
// S_OK if successful.
// E_FAIL if an error occurred.
//
//*********************************************************************
SCODE CXEvent::Deserialize(CRegistryKey& regkeyParent, CString& sName) { CRegistryKey regkey; if (!regkeyParent.GetSubKey(sName, regkey)) { return E_FAIL; }
SCODE sc = E_FAIL; CRegistryValue regval;
// Get the count and time interval.
m_dwCount = 0; m_dwTimeInterval = 0; if (regkey.GetValue(SZ_REGKEY_EVENT_COUNT, regval)) { m_dwCount = *(DWORD*)regval.m_pData; if (regkey.GetValue(SZ_REGKEY_EVENT_TIME, regval)) { m_dwTimeInterval = *(DWORD*)regval.m_pData; } }
if (regkey.GetValue(SZ_REGKEY_EVENT_FULLID, regval)) { DWORD dwFullId = *(DWORD*)regval.m_pData;
CXMessage* pMessage = m_pEventSource->FindMessage(dwFullId); if (pMessage == NULL) { sc = E_MESSAGE_NOT_FOUND; } else { m_message = *pMessage; sc = S_OK; } }
regkey.Close(); return sc; }
//**********************************************************************
// CXEvent::Deserialize
//
// Write the configuration for this event to the registry.
//
// Parameters:
// CRegistryKey& regkeyParent
// An open registry key pointing to the event source in
// SNMP_EVENTS\EventLog
//
// Returns:
// SCODE
// S_OK = the event was successful written out.
// S_SAVE_CANCELED = no errors, but the user canceled the save.
// E_FAIL = if an error occurred.
//
//*********************************************************************
SCODE CXEvent::Serialize(CRegistryKey& regkeyParent) { if (g_reg.m_pdlgSaveProgress) { if (g_reg.m_pdlgSaveProgress->StepProgress()) { return S_SAVE_CANCELED; } }
CRegistryKey regkey;
CString sName; GetName(sName); if (!regkeyParent.CreateSubKey(sName, regkey)) { return E_REGKEY_NO_CREATE; }
CRegistryValue regval; if (m_dwCount > 0) { regval.Set(SZ_REGKEY_EVENT_COUNT, REG_DWORD, sizeof(DWORD), (LPBYTE) &m_dwCount); regkey.SetValue(regval);
if (m_dwTimeInterval > 0) { regval.Set(SZ_REGKEY_EVENT_TIME, REG_DWORD, sizeof(DWORD), (LPBYTE) &m_dwTimeInterval); regkey.SetValue(regval); } }
regval.Set(SZ_REGKEY_EVENT_FULLID, REG_DWORD, sizeof(DWORD), (LPBYTE) &m_message.m_dwId); regkey.SetValue(regval); regkey.Close(); return S_OK; }
//*************************************************************************
// CXEvent::GetCount
//
// Get the ASCII decimal value for the m_dwCount member.
//
// Using this method to do the conversion ensures that the count value is
// presented to the user in a consistent form throughout the program.
//
// Parameters:
// CString& sText
// This is where the count value is returned.
//
// Returns:
// The ASCII value for the count is returned via sText.
//
// Note: m_dwCount and m_dwTimeInterval work together. A trap is sent only if
// m_dwCount events are registered withing m_dwTimeInterval seconds.
//*************************************************************************
void CXEvent::GetCount(CString& sText) { DecString(sText, (long) m_dwCount); }
//*************************************************************************
// CXEvent::GetTimeInterval
//
// Get the ASCII decimal value for the m_dwTimeInterval member.
//
// Using this method to do the conversion ensures that the time-interval value is
// presented to the user in a consistent form throughout the program.
//
// Parameters:
// CString& sText
// This is where the count value is returned.
//
// Returns:
// The ASCII value for the count is returned via sText.
//
// Note: m_dwCount and m_dwTimeInterval work together. A trap is sent only if
// m_dwCount events are registered withing m_dwTimeInterval seconds.
//*************************************************************************
void CXEvent::GetTimeInterval(CString& sText) { DecString(sText, (long) m_dwTimeInterval); }
///////////////////////////////////////////////////////////////////
// Class: CXMessage
//
// This class implements a message. Each event source has some
// number of messages associated with it. A user may select some
// subset of the messages to be converted into "events". The user
// configures events, not messages.
//
// For further information on how this class fits into the
// scheme of things, please see the CXEventSource class header.
//////////////////////////////////////////////////////////////////
CXMessage::CXMessage(CXEventSource* pEventSource) { m_pEventSource = pEventSource; }
CXMessage& CXMessage::operator=(CXMessage& message) { m_pEventSource = message.m_pEventSource; m_dwId = message.m_dwId; m_sText = message.m_sText; return *this; }
//***************************************************************************
//
// CMessage::GetSeverity
//
// Get the severity level of the event. This is the human-readable string
// corresponding to the top two bits of the event ID.
//
// Parameters:
// CString& sSeverity
// A reference to the place to return the severity string.
//
// Returns:
// Nothing.
//
// Status:
//
//***************************************************************************
void CXMessage::GetSeverity(CString& sSeverity) { MapEventToSeverity(m_dwId, sSeverity); }
//***************************************************************************
//
// CMessage::GetTrappingString
//
// This method returns the trapping string "yes" if the event is being
// trapped and "no" if its not being trapped.
//
// Parameters:
// CString& sTrapping
// A reference to the place to return the trapping string.
//
// Returns:
// Nothing.
//
// Status:
//
//***************************************************************************
void CXMessage::IsTrapping(CString& sIsTrapping) { CXEvent* pEvent = m_pEventSource->FindEvent(m_dwId); sIsTrapping.LoadString( pEvent != NULL ? IDS_IS_TRAPPING : IDS_NOT_TRAPPING); }
//****************************************************************************
//
// CMessage::SetAndCleanText
//
// Set the m_sText data member to a cleaned up version of a source string.
// The text is cleaned by converting all funny whitespace characters such
// as carriage return, tabs and so on to ordinary space characters. All
// leading space is stripped from the beginning of the string.
//
//****************************************************************************
void CXMessage::SetAndCleanText(PMESSAGE_RESOURCE_ENTRY pEntry) { BOOL bIsLeadingSpace = TRUE; USHORT i;
if (pEntry->Flags == 0x00000) // ANSI char set
{ CHAR *pszSrc = (CHAR *)pEntry->Text; CHAR chSrc; LPTSTR pszDst = m_sText.GetBuffer(strlen(pszSrc) + 1);
for (i=0; i<pEntry->Length && *pszSrc; i++, pszSrc++) { chSrc = *pszSrc; if (chSrc >= 0x09 && chSrc <= 0x0d) chSrc = ' '; if (chSrc == ' ' && bIsLeadingSpace) continue;
*pszDst++ = (TCHAR)chSrc; if (bIsLeadingSpace) // testing only is less costly
bIsLeadingSpace = FALSE; } *pszDst = _T('\0'); } else // UNICODE char set
{ wchar_t *pwszSrc = (wchar_t *)pEntry->Text; wchar_t wchSrc; LPTSTR pszDst = m_sText.GetBuffer(wcslen(pwszSrc) + 1);
for (i=0; i<pEntry->Length/sizeof(wchar_t) && *pwszSrc; i++, pwszSrc++) { wchSrc = *pwszSrc; if (wchSrc >= (wchar_t)0x09 && wchSrc <= (wchar_t)0x0d) wchSrc = (wchar_t)' '; if (wchSrc == (wchar_t)' ' && bIsLeadingSpace) continue;
*pszDst++ = (TCHAR)wchSrc; if (bIsLeadingSpace) // testing only is less costly
bIsLeadingSpace = FALSE; } *pszDst = _T('\0'); }
m_sText.ReleaseBuffer(); }
//****************************************************************************
// CXMessage::GetShortId
//
// This method returns the message's "short ID" that users see for events and
// messages. The short ID is the ASCII decimal value for the low-order 16 bits
// of the message ID.
//
// Using this method to do the conversion ensures that the short-ID value is
// presented to the user in a consistent form throughout the program.
//
// Parameters:
// CString& sShortId
// This is where the ID string is returned.
//
// Returns:
// The message ID string is returned via sShortId
//
//****************************************************************************
void CXMessage::GetShortId(CString& sShortId) { TCHAR szBuffer[MAX_STRING]; _ltot((LONG) LOWORD(m_dwId), szBuffer, 10); sShortId = szBuffer; }
///////////////////////////////////////////////////////////////////
// Class: CXMessageArray
//
// This class implements an array of pointers to CXMessage objects.
//
// For further information on how this CXMessageArray fits into the
// scheme of things, please see the CXEventSource class header.
//////////////////////////////////////////////////////////////////
//****************************************************************
// CXMessageArray::CXMessageArray
//
// Constructor.
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//
//****************************************************************
CXMessageArray::CXMessageArray() { m_bDidLoadMessages = FALSE; m_pEventSource = NULL; }
//*******************************************************************
// CXMessageArray::FindMessage
//
// Search this array for a message given its ID.
//
// Parameters:
// DWORD dwId
// The full message ID
//
// Returns:
// CXMessage*
// Pointer to the message if it was found. NULL if it was
// not found.
//
// Note:
// Duplicate messages are not allowed in the array, but no code
// enforces this for the sake of efficiency.
//
//*******************************************************************
CXMessage* CXMessageArray::FindMessage(DWORD dwId) { if (!m_bDidLoadMessages) { if (FAILED(LoadMessages())) { return NULL; } }
LONG nMessages = GetSize(); for (LONG iMessage = 0; iMessage < nMessages; ++iMessage) { CXMessage* pMessage = GetAt(iMessage); if (pMessage->m_dwId == dwId) { return pMessage; } }
return NULL; }
//****************************************************************************
//
// XProcessMsgTable
//
// This function processes a the message table contained in a message .DLL file
// and adds all the messages it contains to the given CXMessageArray object.
//
// Parameters:
// HANDLE hModule
// The module handle for the .DLL file.
//
// LPCTSTR lpszType
// Ignored.
//
// LPTSTR lpszName
// The name of the module.
//
// LONG lParam
// A pointer to a CXMessageArray object where the messages will be
// stored.
//
// Returns:
// BOOL
// Always returns TRUE.
//
//
//****************************************************************************
static BOOL CALLBACK XProcessMsgTable(HANDLE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG_PTR lParam) { CXMessageArray* paMessages = (CXMessageArray*)(LPVOID) (LONG_PTR)lParam;
// Found a message table. Process it!
HRSRC hResource = FindResource((HINSTANCE)hModule, lpszName, RT_MESSAGETABLE); if (hResource == NULL) return TRUE;
HGLOBAL hMem = LoadResource((HINSTANCE)hModule, hResource); if (hMem == NULL) return TRUE;
PMESSAGE_RESOURCE_DATA pMsgTable = (PMESSAGE_RESOURCE_DATA)::LockResource(hMem); if (pMsgTable == NULL) return TRUE;
ULONG ulBlock, ulId, ulOffset;
for (ulBlock=0; ulBlock<pMsgTable->NumberOfBlocks; ulBlock++) { ulOffset = pMsgTable->Blocks[ulBlock].OffsetToEntries; for (ulId = pMsgTable->Blocks[ulBlock].LowId; ulId <= pMsgTable->Blocks[ulBlock].HighId; ulId++)
{ PMESSAGE_RESOURCE_ENTRY pEntry = (PMESSAGE_RESOURCE_ENTRY)((ULONG_PTR)pMsgTable + ulOffset); CXMessage *pMessage = new CXMessage(paMessages->m_pEventSource); pMessage->m_dwId = (DWORD) ulId; pMessage->SetAndCleanText(pEntry); paMessages->Add(pMessage); ulOffset += pEntry->Length; } }
return TRUE; }
//****************************************************************************
// CXMessageArray::LoadMessages
//
// Load the messages from the message .DLL file(s) for the source into this
// message array.
//
// Parameters:
// None.
//
// Returns:
// SCODE
// S_OK if successful.
// E_FAIL if an error occurs.
//
//*****************************************************************************
SCODE CXMessageArray::LoadMessages() { ASSERT(m_pEventSource != NULL); if (m_bDidLoadMessages) { return S_OK; }
CBusy busy; CString sLibPathList = m_pEventSource->m_sLibPath; CString sLibPath;
while (GetNextPath(sLibPathList, sLibPath) != E_FAIL) {
// Load the library and get a list of all the messages.
HINSTANCE hInstMsgFile = LoadLibraryEx((LPCTSTR) sLibPath, NULL, LOAD_LIBRARY_AS_DATAFILE); if (hInstMsgFile == NULL) { TCHAR szMessage[MAX_STRING]; CString sFormat; sFormat.LoadString(IDS_ERR_LOAD_MESSAGE_FILE_FAILED); _stprintf(szMessage, (LPCTSTR) sFormat, (LPCTSTR) sLibPath); AfxMessageBox(szMessage, MB_OK | MB_ICONSTOP); continue; }
EnumResourceNames(hInstMsgFile, RT_MESSAGETABLE, (ENUMRESNAMEPROC)XProcessMsgTable, (LONG_PTR) this);
GetLastError();
FreeLibrary(hInstMsgFile); }
m_bDidLoadMessages = TRUE; return S_OK; }
//**************************************************************
// CXMessageArray::GetNextPath
//
// This function extracts the next path element from a list
// of semi-colon separated paths. It also removes the extracted
// element and the semi-colon from the path list.
//
// Paramters:
// CString& sPathlist
// A reference to a string consisting of one or more paths separated
// by semi-colons.
//
// CString& sPath
// A reference to the place where the extracted path string
// will be returned.
//
// Returns:
// SCODE
// S_OK if a path was extracted, E_FAIL otherwise.
//
// The path is returned via sPath. sPathlist is updated
// so that sPath and the trailing semi-colon is removed
//
//**************************************************************
SCODE CXMessageArray::GetNextPath(CString& sPathlist, CString& sPath) { CString sPathTemp;
sPath.Empty(); while (sPath.IsEmpty() && !sPathlist.IsEmpty()) { // Copy the next path from the sPathlist to sPath and
// remove it from sPathlist
INT ich = sPathlist.Find(_T(';')); if (ich == -1) { sPathTemp = sPathlist; sPathlist = _T(""); } else { sPathTemp = sPathlist.Left(ich); sPathlist = sPathlist.Right( sPathlist.GetLength() - (ich + 1)); }
// Trim any leading or trailing space characters from
// the path.
// Find the first non-space character
LPTSTR pszStart = sPathTemp.GetBuffer(sPathTemp.GetLength() + 1); while (*pszStart) { if (!_istspace(*pszStart)) { break; } ++pszStart; } // here, pszStart either points to the 1st non-space character or a string
// of zero length
// Find the first non-space character in reverse direction
LPTSTR pszEnd = pszStart + _tcslen(pszStart); // point to the null character
if (pszStart != pszEnd) { pszEnd--; // point to the last character
while (_istspace(*pszEnd)) { pszEnd--; } // here, pszEnd points to the first non-space character in reverse direction
pszEnd++; *pszEnd = _T('\0'); }
sPath = pszStart; sPathTemp.ReleaseBuffer(); } if (sPath.IsEmpty()) { return E_FAIL; } else { return S_OK; } }
|