// logman.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "strings.h" #include "unihelpr.h" #include "utils.h" #include "proputil.h" #include "propbag.h" #include "logman.h" #include "resource.h" #include "varg.c" // // Forward routines: // ARG_RECORD Commands[] = { IDS_HELP_DEBUG, IDS_ARG1_DEBUG, NULL, IDS_ARG2_DEBUG, NULL, IDS_PARAM_DEFAULT, NULL, ARG_TYPE_DEBUG, ARG_FLAG_OPTIONAL|ARG_FLAG_HIDDEN, (CMD_TYPE)0, 0, NULL, IDS_HELP_HELP, IDS_ARG1_HELP, NULL, IDS_ARG2_HELP, NULL, 0, NULL, ARG_TYPE_HELP, ARG_FLAG_OPTIONAL, (CMD_TYPE)FALSE, 0, NULL, IDS_HELP_COMPUTER, IDS_ARG1_COMPUTER, NULL, IDS_ARG2_COMPUTER, NULL, IDS_ARG1_COMPUTER, NULL, ARG_TYPE_STR, ARG_FLAG_OPTIONAL, (CMD_TYPE)NULL, 0, NULL, IDS_HELP_NAME, IDS_ARG1_NAME, NULL, IDS_ARG2_NAME, NULL, IDS_ARG1_NAME, NULL, ARG_TYPE_STR, ARG_FLAG_CONDITIONAL|ARG_FLAG_NOFLAG, (CMD_TYPE)NULL, 0, NULL, IDS_HELP_START, IDS_ARG1_START, NULL, IDS_ARG2_START, NULL, 0, NULL, ARG_TYPE_BOOL, ARG_FLAG_OPTIONAL, (CMD_TYPE)NULL, 0, NULL, IDS_HELP_STOP, IDS_ARG1_STOP, NULL, IDS_ARG2_STOP, NULL, 0,NULL, ARG_TYPE_BOOL, ARG_FLAG_OPTIONAL, (CMD_TYPE)NULL, 0, NULL, IDS_HELP_SETTINGS, IDS_ARG1_SETTINGS, NULL, IDS_ARG2_SETTINGS, NULL, IDS_PARAM_FILENAME, NULL, ARG_TYPE_STR, ARG_FLAG_CONDITIONAL, (CMD_TYPE)NULL, 0, NULL, IDS_HELP_OVER, IDS_ARG1_OVER, NULL, IDS_ARG2_OVER, NULL, 0, NULL, ARG_TYPE_BOOL, ARG_FLAG_OPTIONAL, (CMD_TYPE)NULL, 0, NULL, ARG_TERMINATOR }; enum _Commands { eDebug, eHelp, eComputer, eName, eStart, eStop, eSettings, eOverwrite }; DWORD _stdcall ValidateComputerName ( LPCTSTR szComputerName ); DWORD _stdcall LoadSettingsFile ( LPCTSTR szFileName, LPTSTR& szFileBuffer ); DWORD _stdcall DeleteQuery ( HKEY hkeyLogQueries, LPCWSTR szQueryName ); DWORD _stdcall InitializeNewQuery ( HKEY hkeyLogQueries, HKEY& rhKeyQuery, LPCWSTR szQueryName ); DWORD _stdcall CreateDefaultLogQueries ( HKEY hkeyLogQueries ); DWORD _stdcall Install ( HKEY hkeyMachine, HKEY& rhkeyLogQueries ); DWORD _stdcall ConnectToRegistry ( LPCTSTR szComputerName, HKEY& rhkeyLogQueries ); DWORD _stdcall WriteRegistryLastModified ( HKEY hkeyQuery ); DWORD _stdcall WriteToRegistry ( HKEY hkeyLogQueries, CPropertyBag* pPropBag, LPWSTR szQueryName, DWORD& rdwLogType ); DWORD _stdcall ValidateProperties ( HKEY hkeyLogQueries, CPropertyBag* pPropBag, DWORD* pdwLogType, LPWSTR szQueryName, DWORD* pdwNameBufLen ); DWORD _stdcall ProcessSettingsObject ( HKEY hkeyLogQueries, CPropertyBag* pPropBag ); DWORD _stdcall ProcessSettingsFile ( LPCTSTR szComputerName, LPCTSTR szFileName ); DWORD _stdcall StartQuery ( LPCTSTR szComputerName, LPCTSTR szQueryName ); DWORD _stdcall StopQuery ( LPCTSTR szComputerName, LPCTSTR szQueryName ); DWORD _stdcall ConnectToQuery ( LPCTSTR szComputerName, LPCTSTR szQueryName, HKEY* phkeyQuery ); DWORD _stdcall GetState ( LPCTSTR szComputerName, DWORD& rdwState ); DWORD _stdcall Synchronize ( LPCTSTR szComputerName ); // // Routines // DWORD _stdcall ValidateComputerName ( LPCTSTR szComputerName ) { DWORD dwStatus = ERROR_SUCCESS; if ( NULL != szComputerName ) { if ( ! ( MAX_COMPUTERNAME_LENGTH < lstrlen ( szComputerName ) ) ) { } else { dwStatus = ERROR_INVALID_COMPUTERNAME; } } return dwStatus; } DWORD _stdcall LoadSettingsFile ( LPCTSTR szFileName, LPTSTR& szFileBuffer ) { DWORD dwStatus = ERROR_SUCCESS; DWORD dwLogManStatus = ERROR_SUCCESS; HANDLE hOpenFile = NULL; TCHAR szLocalName [MAX_PATH]; // Todo: Allocate LPTSTR pFileNameStart = NULL; HANDLE hFindFile = NULL; LPTSTR szData = NULL; WIN32_FIND_DATA FindFileInfo; INT iNameOffset; USES_CONVERSION; lstrcpy ( szLocalName, szFileName ); // Todo: Handle error szFileBuffer = NULL; pFileNameStart = ExtractFileName (szLocalName) ; iNameOffset = (INT)(pFileNameStart - szLocalName); // convert short filename to long NTFS filename if necessary hFindFile = FindFirstFile ( szLocalName, &FindFileInfo) ; if (hFindFile && hFindFile != INVALID_HANDLE_VALUE) { // append the file name back to the path name lstrcpy (&szLocalName[iNameOffset], FindFileInfo.cFileName) ; FindClose (hFindFile) ; // Open the file hOpenFile = CreateFile ( szLocalName, GENERIC_READ, 0, // Not shared NULL, // Security attributes OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ( hOpenFile && hOpenFile != INVALID_HANDLE_VALUE ) { DWORD dwFileSize; DWORD dwFileSizeHigh; // Read the file contents into a memory buffer. dwFileSize = GetFileSize ( hOpenFile, &dwFileSizeHigh ); assert ( 0 == dwFileSizeHigh ); // Todo: Handle non-zero file size high szData = new TCHAR[(dwFileSize + sizeof(TCHAR))/sizeof(TCHAR)]; if ( NULL != szData ) { if ( FileRead ( hOpenFile, szData, dwFileSize ) ) { szFileBuffer = szData; } else { dwLogManStatus = LOGMAN_SETTINGS_FILE_NOT_LOADED; dwStatus = GetLastError(); } } else { dwLogManStatus = LOGMAN_SETTINGS_FILE_NOT_LOADED; dwStatus = ERROR_OUTOFMEMORY; } CloseHandle ( hOpenFile ); } else { dwLogManStatus = LOGMAN_SETTINGS_FILE_NOT_OPEN; dwStatus = GetLastError(); } } else { dwLogManStatus = LOGMAN_SETTINGS_FILE_NOT_OPEN; dwStatus = GetLastError(); } if ( ERROR_SUCCESS != dwLogManStatus ) { DisplayErrorMessage ( dwLogManStatus, T2W(szFileName) ); } if ( ERROR_SUCCESS != dwStatus ) { DisplayErrorMessage ( dwStatus ); } dwStatus = dwLogManStatus; return dwStatus; } DWORD _stdcall DeleteQuery ( HKEY hkeyLogQueries, LPCWSTR szQueryName ) { DWORD dwStatus = ERROR_SUCCESS; // Delete in the registry dwStatus = RegDeleteKeyW ( hkeyLogQueries, szQueryName ); return dwStatus; } DWORD _stdcall InitializeNewQuery ( HKEY hkeyLogQueries, HKEY& rhKeyQuery, LPCWSTR szQueryName ) { DWORD dwStatus = ERROR_SUCCESS; DWORD dwDisposition = 0; // Create the query specified, checking for duplicate query. dwStatus = RegCreateKeyExW ( hkeyLogQueries, szQueryName, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &rhKeyQuery, &dwDisposition); if ( ERROR_SUCCESS == dwStatus ) { if ( REG_OPENED_EXISTING_KEY == dwDisposition && !Commands[eOverwrite].bValue ) { dwStatus = LOGMAN_DUP_QUERY_NAME; } else { DWORD dwRegValue; // Initialize the current state value. After it is // initialized, it is only modified when: // 1) Set to Stopped or Started by the service // 2) Set to Start Pending by the config snapin. dwRegValue = SLQ_QUERY_STOPPED; dwStatus = WriteRegistryDwordValue ( rhKeyQuery, cwszRegCurrentState, &dwRegValue ); if ( ERROR_SUCCESS == dwStatus ) { // Initialize the log type to "new" to indicate partially created logs dwRegValue = SLQ_NEW_LOG; dwStatus = WriteRegistryDwordValue ( rhKeyQuery, cwszRegLogType, &dwRegValue ); } } } return dwStatus; } DWORD _stdcall CreateDefaultLogQueries ( HKEY hkeyLogQueries ) { DWORD dwStatus = ERROR_SUCCESS; HRESULT hr = NOERROR; LPWSTR szPropValue; HKEY hkeyQuery = NULL; LPWSTR szMszBuf = NULL; DWORD dwMszBufLen = 0; DWORD dwMszStringLen = 0; // Create default "System Overview" counter log query // Todo: check and translate HRESULT failures szPropValue = ResourceString ( IDS_DEFAULT_CTRLOG_QUERY_NAME ); if ( ERROR_SUCCESS == dwStatus ) { //Todo: ResourceString return failure how? dwStatus = InitializeNewQuery ( hkeyLogQueries, hkeyQuery, szPropValue ); } if ( ERROR_SUCCESS == dwStatus ) { szPropValue = ResourceString ( IDS_DEFAULT_CTRLOG_CPU_PATH ); hr = AddStringToMszBufferAlloc ( szPropValue, &szMszBuf, &dwMszBufLen, &dwMszStringLen ); if ( SUCCEEDED ( hr ) ) { szPropValue = ResourceString ( IDS_DEFAULT_CTRLOG_MEMORY_PATH ); hr = AddStringToMszBufferAlloc ( szPropValue, &szMszBuf, &dwMszBufLen, &dwMszStringLen ); if ( SUCCEEDED ( hr ) ) { szPropValue = ResourceString ( IDS_DEFAULT_CTRLOG_DISK_PATH ); hr = AddStringToMszBufferAlloc ( szPropValue, &szMszBuf, &dwMszBufLen, &dwMszStringLen ); } } if ( SUCCEEDED ( hr ) ) { dwStatus = WriteRegistryStringValue ( hkeyQuery, cwszRegCounterList, REG_MULTI_SZ, szMszBuf, dwMszStringLen ); if ( ERROR_SUCCESS != dwStatus ) { hr = HRESULT_FROM_WIN32 ( dwStatus ); } } if ( NULL != szMszBuf ) { delete szMszBuf; } } // Counter strings are required fields. if ( SUCCEEDED ( hr ) && ERROR_SUCCESS == dwStatus ) { DWORD dwTemp; SLQ_TIME_INFO stiData; szPropValue = ResourceString ( IDS_DEFAULT_CTRLOG_COMMENT ); dwStatus = WriteRegistryStringValue ( hkeyQuery, cwszRegComment, REG_SZ, szPropValue, lstrlenW ( szPropValue ) ); dwTemp = SLF_NAME_NONE; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegLogFileAutoFormat, &dwTemp ); // Start mode and time set to manual memset (&stiData, 0, sizeof(stiData)); stiData.wTimeType = SLQ_TT_TTYPE_START; stiData.wDataType = SLQ_TT_DTYPE_DATETIME; stiData.dwAutoMode = SLQ_AUTO_MODE_NONE; stiData.llDateTime = MAX_TIME_VALUE; dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegStartTime, &stiData ); // Stop and Sample time fields are defaults, but Smlogsvc generates // warning events if fields are missing. InitDefaultSlqTimeInfo ( SLQ_COUNTER_LOG, SLQ_TT_TTYPE_STOP, &stiData ); dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegStopTime, &stiData ); InitDefaultSlqTimeInfo ( SLQ_COUNTER_LOG, SLQ_TT_TTYPE_SAMPLE, &stiData ); dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegSampleInterval, &stiData ); // The following file fields are defaults, but Smlogsvc generates // warning events if fields are missing. dwStatus = WriteRegistryStringValue ( hkeyQuery, cwszRegLogFileFolder, REG_SZ, cwszDefaultLogFileFolder, lstrlenW ( cwszDefaultLogFileFolder ) ); dwTemp = DEFAULT_LOG_FILE_SERIAL_NUMBER; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegLogFileSerialNumber, &dwTemp ); dwTemp = DEFAULT_LOG_FILE_MAX_SIZE; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegLogFileMaxSize, &dwTemp ); dwTemp = SLF_BIN_FILE; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegLogFileType, &dwTemp ); szPropValue = ResourceString ( IDS_DEFAULT_CTRLOG_QUERY_NAME ); dwStatus = WriteRegistryStringValue ( hkeyQuery, cwszRegLogFileBaseName, REG_SZ, szPropValue, lstrlenW ( szPropValue ) ); // The sample query is "ExecuteOnly" dwTemp = 1; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegExecuteOnly, &dwTemp ); // Reset the log type from "new" to real type dwTemp = SLQ_COUNTER_LOG; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegLogType, &dwTemp ); dwStatus = WriteRegistryLastModified ( hkeyQuery ); dwStatus = ERROR_SUCCESS; // Non-required fields. } else { // Todo: Translate status, display message re: default log not installed if ( ERROR_SUCCESS == dwStatus ) { dwStatus = (DWORD) hr; } szPropValue = ResourceString ( IDS_DEFAULT_CTRLOG_QUERY_NAME ); if ( NULL != hkeyQuery ) { RegCloseKey ( hkeyQuery ); hkeyQuery = NULL; } DeleteQuery ( hkeyLogQueries, szPropValue ); } if ( NULL != hkeyQuery ) { RegCloseKey ( hkeyQuery ); } return dwStatus; } DWORD _stdcall Install ( HKEY hkeyMachine, HKEY& rhkeyLogQueries ) { DWORD dwStatus = ERROR_SUCCESS; HKEY hkeySysmonLog; DWORD dwDisposition; assert ( NULL != hkeyMachine ); rhkeyLogQueries = NULL; dwStatus = RegOpenKeyExW ( hkeyMachine, cwszRegKeySysmonLog, 0, KEY_READ | KEY_WRITE, &hkeySysmonLog); if ( ERROR_SUCCESS == dwStatus ) { // Create registry subkey for Log Queries dwStatus = RegCreateKeyExW ( hkeySysmonLog, cwszRegKeyLogQueries, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &rhkeyLogQueries, &dwDisposition); } if ( ERROR_ACCESS_DENIED == dwStatus ) { dwStatus = LOGMAN_INSTALL_ACCESS_DENIED; DisplayErrorMessage ( dwStatus ); } else if ( ERROR_SUCCESS == dwStatus ) { dwStatus = CreateDefaultLogQueries( rhkeyLogQueries ); // Todo: Handle failure } return dwStatus; } DWORD _stdcall ConnectToRegistry ( LPCTSTR szComputerName, HKEY& rhkeyLogQueries ) { DWORD dwStatus = ERROR_SUCCESS; HKEY hkeyMachine = HKEY_LOCAL_MACHINE; WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH+1]; USES_CONVERSION; wszComputerName[0] = NULL_W; // Connect to registry on specified machine. if ( NULL != szComputerName ) { if ( NULL_T != szComputerName[0] ) { lstrcpyW ( wszComputerName, T2W ( szComputerName ) ); dwStatus = RegConnectRegistryW ( wszComputerName, HKEY_LOCAL_MACHINE, &hkeyMachine ); } else { hkeyMachine = HKEY_LOCAL_MACHINE; } } else { hkeyMachine = HKEY_LOCAL_MACHINE; } if ( ERROR_SUCCESS == dwStatus ) { assert ( NULL != hkeyMachine ); dwStatus = RegOpenKeyExW ( hkeyMachine, cwszRegKeyFullLogQueries, 0, KEY_ALL_ACCESS, &rhkeyLogQueries ); if ( ERROR_ACCESS_DENIED == dwStatus ) { dwStatus = IDS_LOGMAN_REG_ACCESS_DENIED; } else if ( ERROR_SUCCESS != dwStatus ) { // Install displays error messages dwStatus = Install ( hkeyMachine, rhkeyLogQueries ); } } else { if ( 0 == lstrlenW ( wszComputerName ) ) { lstrcpyW ( wszComputerName, cwszLocalComputer ); } DisplayErrorMessage ( LOGMAN_NO_COMPUTER_CONNECT, wszComputerName ); DisplayErrorMessage ( dwStatus ); dwStatus = LOGMAN_NO_COMPUTER_CONNECT; } if ( NULL != hkeyMachine ) { RegCloseKey ( hkeyMachine ); } return dwStatus; } // // WriteRegistryLastModified function. // Copies the current "last modified date" to the registry where // it is read by the log service. // DWORD _stdcall WriteRegistryLastModified ( HKEY hkeyQuery ) { LONG dwStatus = ERROR_SUCCESS; SLQ_TIME_INFO plqLastModified; SYSTEMTIME stLocalTime; FILETIME ftLocalTime; // Get local time for last modified value. GetLocalTime (&stLocalTime); SystemTimeToFileTime (&stLocalTime, &ftLocalTime); plqLastModified.wDataType = SLQ_TT_DTYPE_DATETIME; plqLastModified.wTimeType = SLQ_TT_TTYPE_LAST_MODIFIED; plqLastModified.dwAutoMode = SLQ_AUTO_MODE_NONE; // not used. plqLastModified.llDateTime = *(LONGLONG *)&ftLocalTime; dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegLastModified, &plqLastModified); return dwStatus; } DWORD _stdcall ValidateProperties ( HKEY hkeyLogQueries, CPropertyBag* pPropBag, DWORD* pdwLogType, LPWSTR szQueryName, DWORD* pdwNameBufLen ) { DWORD dwStatus = ERROR_SUCCESS; HRESULT hr = NOERROR; LPWSTR szPropBagBuf = NULL; DWORD dwPropBagBufLen = 0; DWORD dwPropBagStringLen = 0; DWORD dwLocalLogType; // Query key is not necessary for validation, so set it to NULL. CPropertyUtils cPropertyUtils ( Commands[eComputer].strValue, NULL, pPropBag, NULL, hkeyLogQueries ); USES_CONVERSION; *pdwLogType = 0; szQueryName[0] = NULL_W; // Todo: Version info // Query type and name are required properties. hr = DwordFromPropertyBag ( pPropBag, cwszHtmlLogType, dwLocalLogType ); if ( SUCCEEDED ( hr ) ) { if ( SLQ_ALERT != dwLocalLogType ) { hr = StringFromPropBagAlloc ( pPropBag, cwszHtmlLogName, &szPropBagBuf, &dwPropBagBufLen, &dwPropBagStringLen ); } else { hr = StringFromPropBagAlloc ( pPropBag, cwszHtmlAlertName, &szPropBagBuf, &dwPropBagBufLen, &dwPropBagStringLen ); } if ( FAILED ( hr ) ) { dwStatus = LOGMAN_NO_OBJECT_NAME; /* } else { // Validate query name. // Todo: Length check. Must be <= MAX_PATH; lstrcpynW ( szTempCheck, szPropBagBuf, MAX_PATH ); szTokenCheck = _tcstok ( szTempCheck, cwszInvalidLogNameChars ); if ( 0 != lstrcmpi (szTempCheck, szTokenCheck ) ) { dwStatus = LOGMAN_INVALID_QUERY_NAME; } */ } } else { dwStatus = LOGMAN_NO_OBJECT_LOGTYPE; } // At this point, any hr failure is reflected in dwStatus. if ( ERROR_SUCCESS == dwStatus ) { if ( NULL == szQueryName || ( *pdwNameBufLen < ( dwPropBagStringLen + 1 ) ) ) { dwStatus = ERROR_INSUFFICIENT_BUFFER; } else { lstrcpyW ( szQueryName, szPropBagBuf ) ; } *pdwNameBufLen = dwPropBagStringLen + 1; // Room for NULL. *pdwLogType = dwLocalLogType; } // If required query type and name exist, then validate all other properties if ( ERROR_SUCCESS != dwStatus ) { // Todo: Error message re: Validation failure -or print that and write success messages // in the "Process object" method. // Todo: Handle "invalid query name" elsewhere. if ( LOGMAN_INVALID_QUERY_NAME == dwStatus ) { DisplayErrorMessage ( dwStatus, szQueryName ); } else { DisplayErrorMessage ( dwStatus ); } } else { // Initialize the query name string in CPropertyUtils, for error message display. cPropertyUtils.SetQueryName ( szQueryName ); // Validate required parameters first: if ( SLQ_TRACE_LOG == dwLocalLogType ) { //DWORD dwKernelValid; dwStatus = cPropertyUtils.Validate ( IdGuidListProp, dwLocalLogType ); // Todo: Check for kernel flags. Must have Guids or Kernel, cannot have both. } else { assert ( SLQ_COUNTER_LOG == dwLocalLogType || SLQ_ALERT == dwLocalLogType ); dwStatus = cPropertyUtils.Validate ( IdCounterListProp, dwLocalLogType ); // Todo: Validate Alert action flags here. If all subfields are messed up, // do not continue processing (?) } // All other fields are non-required if ( ERROR_SUCCESS == dwStatus ) { // Handle failures individually? Use exception handling? if ( SLQ_TRACE_LOG == dwLocalLogType ) { dwStatus = cPropertyUtils.Validate ( IdTraceBufferSizeProp ); dwStatus = cPropertyUtils.Validate ( IdTraceBufferMinCountProp ); dwStatus = cPropertyUtils.Validate ( IdTraceBufferMaxCountProp ); dwStatus = cPropertyUtils.Validate ( IdTraceBufferFlushIntProp ); } else if ( SLQ_ALERT == dwLocalLogType ) { dwStatus = cPropertyUtils.Validate ( IdActionFlagsProp ); dwStatus = cPropertyUtils.Validate ( IdCommandFileProp ); // Validated as part of action flags validation dwStatus = cPropertyUtils.Validate ( IdNetworkNameProp ); dwStatus = cPropertyUtils.Validate ( IdUserTextProp ); dwStatus = cPropertyUtils.Validate ( IdPerfLogNameProp ); } // Properties common to counter and trace logs if ( SLQ_COUNTER_LOG == dwLocalLogType || SLQ_TRACE_LOG == dwLocalLogType ) { dwStatus = cPropertyUtils.Validate ( IdLogFileTypeProp ); dwStatus = cPropertyUtils.Validate ( IdLogFileAutoFormatProp ); dwStatus = cPropertyUtils.Validate ( IdLogFileSerialNumberProp ); dwStatus = cPropertyUtils.Validate ( IdLogFileBaseNameProp ); dwStatus = cPropertyUtils.Validate ( IdLogFileMaxSizeProp ); dwStatus = cPropertyUtils.Validate ( IdLogFileFolderProp ); dwStatus = cPropertyUtils.Validate ( IdEofCommandFileProp ); } // Properties common to alerts and counter logs if ( SLQ_COUNTER_LOG == dwLocalLogType || SLQ_ALERT == dwLocalLogType ) { dwStatus = cPropertyUtils.Validate ( IdSampleProp ); } // Properties common to all query types dwStatus = cPropertyUtils.Validate ( IdCommentProp ); dwStatus = cPropertyUtils.Validate ( IdRestartProp, dwLocalLogType ); dwStatus = cPropertyUtils.Validate ( IdStartProp, dwLocalLogType ); dwStatus = cPropertyUtils.Validate ( IdStopProp, dwLocalLogType ); // Todo: Validation is currently ignored until fully implemented dwStatus = ERROR_SUCCESS; } else { // Display error message re: missing required property. // Need log-type specific message. if ( SLQ_COUNTER_LOG == dwLocalLogType ) { // DisplayErrorMessage ( dwStatus ); } else if ( SLQ_TRACE_LOG == dwLocalLogType ) { // Attempt to load kernel trace flags // dwStatus = cPropertyUtils.Validate ( IdTraceFlagsProp ); if ( ERROR_SUCCESS == dwStatus ) { // Todo: Check for kernel flag. Need either Kernel flag OR provider GUIDs // and NOT both. } } else if ( SLQ_ALERT == dwLocalLogType ) { } // Todo: Need status processing method to determine if any required subfields are incorrect. // If so, then stop processing this HTML file. // Todo: Validation is currently ignored until fully implemented dwStatus = ERROR_SUCCESS; } } // Todo: Validation is currently ignored until fully implemented dwStatus = ERROR_SUCCESS; return dwStatus; } DWORD _stdcall WriteToRegistry ( HKEY hkeyLogQueries, CPropertyBag* pPropBag, LPWSTR szQueryName, DWORD& rdwLogType ) { DWORD dwStatus = ERROR_SUCCESS; HRESULT hr = NOERROR; HKEY hkeyQuery = NULL; CPropertyUtils cPropertyUtils ( Commands[eComputer].strValue ); USES_CONVERSION; // Write all properties to the registry. // All errors are displayed as messages within this // routine or its subroutines. // Create log key dwStatus = InitializeNewQuery ( hkeyLogQueries, hkeyQuery, szQueryName ); if ( ERROR_SUCCESS != dwStatus ) { if ( LOGMAN_DUP_QUERY_NAME == dwStatus ) { if ( Commands[eOverwrite].bValue ) { // If Overwrite option specified, overwrite after displaying warning. DisplayErrorMessage ( LOGMAN_OVERWRITE_DUP_QUERY, szQueryName ); dwStatus = DeleteQuery ( hkeyLogQueries, szQueryName ); if ( ERROR_SUCCESS == dwStatus ) { dwStatus = InitializeNewQuery ( hkeyLogQueries, hkeyQuery, szQueryName ); if ( ERROR_SUCCESS != dwStatus ) { DisplayErrorMessage ( dwStatus ); } } } else { DisplayErrorMessage ( dwStatus, szQueryName ); } } else { DisplayErrorMessage ( dwStatus ); } } // If failed before this point, do not continue loading. if ( ERROR_SUCCESS == dwStatus ) { // Use log key to write properties to the registry. // When loading properties, continue even if errors. // // On error, nothing is written to the registry. // In this case, the default value will // be read in by the log service. // Note: StringFromPropBagAlloc and AddStringToMszBuffer // allocate data buffers that must be deleted by the caller. // These methods also indicate length of string returned // vs. length of buffer returned. // hkeyQueryList is not required for writing to the the registry, // so leave it NULL. // Todo: Some fields will require validation in this method, // to ensure that default values are used instead of incorrect values. cPropertyUtils.SetQueryName ( szQueryName ); cPropertyUtils.SetPropertyBag ( pPropBag ); cPropertyUtils.SetQueryKey ( hkeyQuery ); // Required properties: Counter list for counter logs and alerts, // provider guid list or kernel flags for trace logs. if ( SLQ_TRACE_LOG == rdwLogType ) { hr = cPropertyUtils.BagToRegistry ( IdGuidListProp, rdwLogType ); } else { assert ( SLQ_COUNTER_LOG == rdwLogType || SLQ_ALERT == rdwLogType ); hr = cPropertyUtils.BagToRegistry ( IdCounterListProp, rdwLogType ); } if ( FAILED ( hr ) ) { // Todo: At this point, the problem would be an internal error, // because validated previous to this. // Todo: Type - specific error message if ( SLQ_COUNTER_LOG == rdwLogType ) { } else if ( SLQ_TRACE_LOG == rdwLogType ) { // Attemp to load kernel trace flags hr = cPropertyUtils.BagToRegistry ( IdTraceFlagsProp ); if ( FAILED ( hr ) ) { //Todo: Check for kernel flag. Need either Kernel flag OR provider GUIDs } } else if ( SLQ_ALERT == rdwLogType ) { // Todo: Special case to write action properties. Only write correct ones? // Or stop processing if any invalid? } } else { if ( SLQ_TRACE_LOG == rdwLogType ) { hr = cPropertyUtils.BagToRegistry ( IdTraceBufferSizeProp ); hr = cPropertyUtils.BagToRegistry ( IdTraceBufferMinCountProp ); hr = cPropertyUtils.BagToRegistry ( IdTraceBufferMaxCountProp ); hr = cPropertyUtils.BagToRegistry ( IdTraceBufferFlushIntProp ); } else if ( SLQ_ALERT == rdwLogType ) { hr = cPropertyUtils.BagToRegistry ( IdActionFlagsProp ); hr = cPropertyUtils.BagToRegistry ( IdCommandFileProp ); hr = cPropertyUtils.BagToRegistry ( IdNetworkNameProp ); hr = cPropertyUtils.BagToRegistry ( IdUserTextProp ); hr = cPropertyUtils.BagToRegistry ( IdPerfLogNameProp ); } hr = NOERROR; // Todo: ensure dwLogType is valid // Properties common to counter and trace logs if ( SLQ_COUNTER_LOG == rdwLogType || SLQ_TRACE_LOG == rdwLogType ) { hr = cPropertyUtils.BagToRegistry ( IdLogFileMaxSizeProp ); hr = cPropertyUtils.BagToRegistry ( IdLogFileTypeProp ); hr = cPropertyUtils.BagToRegistry ( IdLogFileAutoFormatProp ); hr = cPropertyUtils.BagToRegistry ( IdLogFileSerialNumberProp ); hr = cPropertyUtils.BagToRegistry ( IdLogFileBaseNameProp ); hr = cPropertyUtils.BagToRegistry ( IdLogFileMaxSizeProp ); hr = cPropertyUtils.BagToRegistry ( IdLogFileFolderProp ); hr = cPropertyUtils.BagToRegistry ( IdEofCommandFileProp ); hr = NOERROR; } // Properties common to alerts and counter logs if ( SLQ_COUNTER_LOG == rdwLogType || SLQ_ALERT == rdwLogType ) { // Time values default if error hr = cPropertyUtils.BagToRegistry ( IdSampleProp ); hr = NOERROR; } // Properties common to all query types hr = cPropertyUtils.BagToRegistry ( IdCommentProp ); hr = cPropertyUtils.BagToRegistry ( IdRestartProp, rdwLogType ); hr = cPropertyUtils.BagToRegistry ( IdStartProp, rdwLogType ); hr = cPropertyUtils.BagToRegistry ( IdStopProp, rdwLogType ); hr = NOERROR; dwStatus = ERROR_SUCCESS; // Non-required fields // Required fields, ending the creation process. // Reset the log type from "new" to real type dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegLogType, &rdwLogType ); if ( ERROR_SUCCESS == dwStatus ) { dwStatus = WriteRegistryLastModified ( hkeyQuery ); } if ( ERROR_SUCCESS == dwStatus ) { DisplayErrorMessage ( IDS_LOGMAN_QUERY_CONFIG_SUCCESS, szQueryName ); } /* else //Todo: Error message re: unable to complete query in the registry */ } if ( FAILED ( hr ) || ( ERROR_SUCCESS != dwStatus ) ) { RegCloseKey ( hkeyQuery ); hkeyQuery = NULL; DeleteQuery ( hkeyLogQueries, szQueryName ); } } if ( NULL != hkeyQuery ) { RegCloseKey ( hkeyQuery ); } return dwStatus; } DWORD _stdcall ProcessSettingsObject ( HKEY hkeyLogQueries, CPropertyBag* pPropBag ) { DWORD dwStatus = ERROR_SUCCESS; DWORD dwLogType; WCHAR szQueryName [MAX_PATH]; // Todo: Remove length restriction DWORD dwNameBufLen = MAX_PATH; // Validate all properties // Todo: Ensure that at least one provider, counter, or alert was added. dwStatus = ValidateProperties ( hkeyLogQueries, pPropBag, &dwLogType, szQueryName, &dwNameBufLen ); // ValidateProperties() displays messages for all errors if ( ERROR_SUCCESS == dwStatus ) { // Write all properties to the registry. // WriteToRegistry() displays messages for all errors dwStatus = WriteToRegistry ( hkeyLogQueries, pPropBag, szQueryName, dwLogType ); } return dwStatus; } DWORD _stdcall ProcessSettingsFile ( LPCTSTR szComputerName, LPCTSTR szFileName ) { DWORD dwStatus = ERROR_SUCCESS; LPTSTR szFirstObject = NULL; // Open file dwStatus = LoadSettingsFile ( szFileName, szFirstObject ); if ( ERROR_SUCCESS == dwStatus && NULL != szFirstObject ) { HKEY hkeyLogQueries = NULL; dwStatus = ConnectToRegistry ( szComputerName, hkeyLogQueries ); if ( ERROR_SUCCESS == dwStatus ) { LPTSTR szCurrentObject = NULL; LPTSTR szNextObject = NULL; BOOL bAtLeastOneSysmonObjectRead = FALSE; assert ( NULL != hkeyLogQueries ); szCurrentObject = szFirstObject; while ( ERROR_SUCCESS == dwStatus && NULL != szCurrentObject ) { CPropertyBag PropBag; dwStatus = PropBag.LoadData ( szCurrentObject, szNextObject ); if ( ERROR_SUCCESS == dwStatus ) { dwStatus = ProcessSettingsObject ( hkeyLogQueries, &PropBag ); if ( ERROR_SUCCESS == dwStatus ) { bAtLeastOneSysmonObjectRead = TRUE; } else { if ( LOGMAN_NO_SYSMON_OBJECT != dwStatus ) { bAtLeastOneSysmonObjectRead = TRUE; } // Error messages handled (displayed) within // ProcessSettingsObject(), so reset dwStatus dwStatus = ERROR_SUCCESS; } } else { // Handle (display) error message, then reset // dwStatus to continue. if ( LOGMAN_NO_SYSMON_OBJECT != dwStatus ) { DisplayErrorMessage ( dwStatus ); } dwStatus = ERROR_SUCCESS; } szCurrentObject = szNextObject; } if ( !bAtLeastOneSysmonObjectRead ) { dwStatus = LOGMAN_NO_SYSMON_OBJECT; DisplayErrorMessage ( dwStatus ); } RegCloseKey ( hkeyLogQueries ); } // else error message displayed by ConnectToRegistry() } // else error message displayed by LoadSettingsFile() // Delete data buffer allocated by LoadSettingsFile if ( NULL != szFirstObject ) { delete szFirstObject; } return dwStatus; } DWORD _stdcall ConnectToQuery ( LPCTSTR szComputerName, LPCTSTR szQueryName, HKEY& rhkeyQuery ) { DWORD dwStatus = ERROR_SUCCESS; HKEY hkeyQuery = NULL; HKEY hkeyLogQueries = NULL; USES_CONVERSION; dwStatus = ConnectToRegistry ( szComputerName, hkeyLogQueries ); if ( ERROR_SUCCESS == dwStatus ) { dwStatus = RegOpenKeyEx ( hkeyLogQueries, szQueryName, 0, KEY_ALL_ACCESS, &hkeyQuery ); if ( ERROR_SUCCESS != dwStatus ) { if ( ERROR_ACCESS_DENIED == dwStatus ) { dwStatus = LOGMAN_REG_ACCESS_DENIED; DisplayErrorMessage ( dwStatus ); } else if ( ERROR_FILE_NOT_FOUND == dwStatus ) { dwStatus = LOGMAN_NO_QUERY_CONNECT; DisplayErrorMessage ( dwStatus, T2W( szQueryName ) ); } } } RegCloseKey ( hkeyLogQueries ); rhkeyQuery = hkeyQuery; return dwStatus; } #pragma warning ( disable : 4706 ) DWORD _stdcall StartQuery ( LPCTSTR szComputerName, LPCTSTR szQueryName ) { DWORD dwStatus = ERROR_SUCCESS; HKEY hkeyQuery = NULL; USES_CONVERSION; // ConnectToQuery displays any errors dwStatus = ConnectToQuery ( szComputerName, szQueryName, hkeyQuery ); if ( ERROR_SUCCESS == dwStatus ) { SLQ_TIME_INFO stiData; DWORD dwRegValue; BOOL bSetStopToMax; // Todo: Warn the user if start mode is not manual. // Set start mode to manual, start time = MIN_TIME_VALUE memset (&stiData, 0, sizeof(stiData)); stiData.wTimeType = SLQ_TT_TTYPE_START; stiData.wDataType = SLQ_TT_DTYPE_DATETIME; stiData.dwAutoMode = SLQ_AUTO_MODE_NONE; stiData.llDateTime = MIN_TIME_VALUE; dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegStartTime, &stiData ); if ( ERROR_SUCCESS == dwStatus ) { // If stop time mode set to manual, or StopAt with time before Now, // set the mode to Manual, value to MAX_TIME_VALUE bSetStopToMax = FALSE; dwStatus = ReadRegistrySlqTime ( hkeyQuery, cwszRegStopTime, &stiData ); if ( ERROR_SUCCESS == dwStatus ) { if ( SLQ_AUTO_MODE_NONE == stiData.dwAutoMode ) { bSetStopToMax = TRUE; } else if ( SLQ_AUTO_MODE_AT == stiData.dwAutoMode ) { SYSTEMTIME stLocalTime; FILETIME ftLocalTime; LONGLONG llLocalTime; // get local time GetLocalTime (&stLocalTime); SystemTimeToFileTime (&stLocalTime, &ftLocalTime); llLocalTime = *(LONGLONG*)&ftLocalTime; if ( llLocalTime >= stiData.llDateTime ) { bSetStopToMax = TRUE; } } } if ( ERROR_SUCCESS == dwStatus && bSetStopToMax ) { assert( SLQ_TT_DTYPE_DATETIME == stiData.wDataType ); stiData.dwAutoMode = SLQ_AUTO_MODE_NONE; stiData.llDateTime = MAX_TIME_VALUE; dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegStopTime, &stiData ); } } // Service needs to distinguish between Running and Start Pending // at service startup, so always set state to start pending. // Todo: Check to see if running, before executing this. if ( ERROR_SUCCESS == dwStatus ) { dwRegValue = SLQ_QUERY_START_PENDING; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegCurrentState, &dwRegValue ); } // Set LastModified if ( ERROR_SUCCESS == dwStatus ) { dwStatus = WriteRegistryLastModified ( hkeyQuery ); } // Start the service on the target machine if ( ERROR_SUCCESS == dwStatus ) { DWORD dwTimeout = 3; DWORD dwState = 0; dwStatus = Synchronize ( szComputerName ); while (--dwTimeout && ERROR_SUCCESS == dwStatus ) { dwStatus = GetState ( szComputerName, dwState ); if ( SERVICE_RUNNING == dwState ) { break; } } if ( ERROR_SUCCESS == dwStatus && SERVICE_RUNNING != dwState ) { dwStatus = LOGMAN_START_TIMED_OUT; } } if ( ERROR_SUCCESS != dwStatus ) { if ( LOGMAN_START_TIMED_OUT == dwStatus ) { DisplayErrorMessage ( dwStatus, T2W(szQueryName) ); } else { DisplayErrorMessage ( LOGMAN_START_FAILED, T2W(szQueryName) ); DisplayErrorMessage ( dwStatus ); } } } if ( NULL != hkeyQuery ) { RegCloseKey ( hkeyQuery ); } if ( ERROR_SUCCESS == dwStatus ) { DisplayErrorMessage ( LOGMAN_QUERY_START_SUCCESS, T2W(szQueryName) ); } return dwStatus; } #pragma warning ( default: 4706 ) #pragma warning ( disable: 4706 ) DWORD _stdcall StopQuery ( LPCTSTR szComputerName, LPCTSTR szQueryName ) { DWORD dwStatus = ERROR_SUCCESS; HKEY hkeyQuery = NULL; USES_CONVERSION; // ConnectToQuery displays any errors dwStatus = ConnectToQuery ( szComputerName, szQueryName, hkeyQuery ); if ( ERROR_SUCCESS == dwStatus ) { SLQ_TIME_INFO stiData; DWORD dwRestartMode = 0; // If query is set to restart on end, clear the restart flag. dwStatus = ReadRegistryDwordValue ( hkeyQuery, cwszRegRestart, &dwRestartMode ); // Todo: Warn user if ( ERROR_SUCCESS == dwStatus && SLQ_AUTO_MODE_NONE != dwRestartMode ) { dwRestartMode = SLQ_AUTO_MODE_NONE; dwStatus = WriteRegistryDwordValue ( hkeyQuery, cwszRegRestart, &dwRestartMode, REG_BINARY ); } // Set stop mode to manual, stop time to MIN_TIME_VALUE if ( ERROR_SUCCESS == dwStatus ) { memset (&stiData, 0, sizeof(stiData)); stiData.wTimeType = SLQ_TT_TTYPE_STOP; stiData.wDataType = SLQ_TT_DTYPE_DATETIME; stiData.dwAutoMode = SLQ_AUTO_MODE_NONE; stiData.llDateTime = MIN_TIME_VALUE; dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegStopTime, &stiData ); } // If start time mode set to manual, set the value to MAX_TIME_VALUE if ( ERROR_SUCCESS == dwStatus ) { dwStatus = ReadRegistrySlqTime ( hkeyQuery, cwszRegStartTime, &stiData ); if ( ERROR_SUCCESS == dwStatus && SLQ_AUTO_MODE_NONE == stiData.dwAutoMode ) { assert( SLQ_TT_DTYPE_DATETIME == stiData.wDataType ); stiData.llDateTime = MAX_TIME_VALUE; dwStatus = WriteRegistrySlqTime ( hkeyQuery, cwszRegStartTime, &stiData ); } } // Set LastModified if ( ERROR_SUCCESS == dwStatus ) { dwStatus = WriteRegistryLastModified ( hkeyQuery ); } // Start the service on the target machine if ( ERROR_SUCCESS == dwStatus ) { DWORD dwTimeout = 3; DWORD dwState = 0; dwStatus = Synchronize ( szComputerName ); while (--dwTimeout && ERROR_SUCCESS == dwStatus ) { dwStatus = GetState ( szComputerName, dwState ); if ( SERVICE_STOPPED == dwState ) { break; } } if ( ERROR_SUCCESS == dwStatus && SERVICE_STOPPED != dwState ) { dwStatus = LOGMAN_STOP_TIMED_OUT; } } if ( ERROR_SUCCESS != dwStatus ) { if ( LOGMAN_STOP_TIMED_OUT == dwStatus ) { DisplayErrorMessage ( dwStatus, T2W(szQueryName) ); } else { DisplayErrorMessage ( LOGMAN_STOP_FAILED, T2W(szQueryName) ); DisplayErrorMessage ( dwStatus ); } } } if ( NULL != hkeyQuery ) { RegCloseKey ( hkeyQuery ); } if ( ERROR_SUCCESS == dwStatus ) { DisplayErrorMessage ( LOGMAN_QUERY_STOP_SUCCESS, T2W(szQueryName) ); } return dwStatus; } #pragma warning ( default: 4706 ) DWORD _stdcall GetState ( LPCTSTR szComputerName, DWORD& rdwState ) { DWORD dwStatus = ERROR_SUCCESS; SERVICE_STATUS ssData; SC_HANDLE hSC; SC_HANDLE hLogService; rdwState = 0; // Error by default. // open SC database hSC = OpenSCManager ( szComputerName, NULL, SC_MANAGER_CONNECT); if (hSC != NULL) { // open service hLogService = OpenServiceW ( hSC, cwszLogService, SERVICE_INTERROGATE ); if (hLogService != NULL) { if ( ControlService ( hLogService, SERVICE_CONTROL_INTERROGATE, &ssData)) { rdwState = ssData.dwCurrentState; } else { dwStatus = GetLastError(); rdwState = SERVICE_STOPPED; // *** error message assert (dwStatus != 0); } CloseServiceHandle (hLogService); } else { // *** error message dwStatus = GetLastError(); assert (dwStatus != 0); } CloseServiceHandle (hSC); } else { // *** error message dwStatus = GetLastError(); assert (dwStatus != 0); } // OpenSCManager if ( ERROR_SERVICE_NOT_ACTIVE == dwStatus || ERROR_SERVICE_REQUEST_TIMEOUT == dwStatus ) { rdwState = SERVICE_STOPPED; dwStatus = ERROR_SUCCESS; } return dwStatus; } #pragma warning ( disable: 4706 ) DWORD _stdcall Synchronize ( LPCTSTR szComputerName ) { // If the service is running, tell it to synchronize itself, // Check the state afterwards to see if it got the message. // If stop pending or stopped, wait until the service is // stopped and then attempt to start it. The service // synchronizes itself from the registry when it is started. // Return ERROR_SUCCESS for success, other for failure. SC_HANDLE hSC = NULL; SC_HANDLE hLogService = NULL; SERVICE_STATUS ssData; DWORD dwCurrentState; DWORD dwTimeout = 25; LONG dwStatus = ERROR_SUCCESS; dwStatus = GetState ( szComputerName, dwCurrentState ); if ( ERROR_SUCCESS == dwStatus && 0 != dwCurrentState ) { // open SC database hSC = OpenSCManager ( szComputerName, NULL, SC_MANAGER_CONNECT); if ( NULL != hSC ) { // open service hLogService = OpenServiceW ( hSC, cwszLogService, SERVICE_USER_DEFINED_CONTROL | SERVICE_START ); if ( NULL != hLogService ) { if ( ( SERVICE_STOPPED != dwCurrentState ) && ( SERVICE_STOP_PENDING != dwCurrentState ) ) { // Wait 100 milliseconds before synchronizing service, // to ensure that registry values are written. Sleep ( 100 ); ControlService ( hLogService, SERVICE_CONTROL_SYNCHRONIZE, &ssData); dwCurrentState = ssData.dwCurrentState; } // Make sure that the ControlService call reached the service // while it was in run state. if ( ( SERVICE_STOPPED == dwCurrentState ) || ( SERVICE_STOP_PENDING == dwCurrentState ) ) { if ( SERVICE_STOP_PENDING == dwCurrentState ) { // wait for the service to stop before starting it. while ( --dwTimeout && ERROR_SUCCESS == dwStatus ) { dwStatus = GetState ( szComputerName, dwCurrentState ); if ( SERVICE_STOP_PENDING == dwCurrentState ) { Sleep(200); } else { break; } } } dwTimeout = 25; if ( SERVICE_STOPPED == dwCurrentState ) { if ( StartService (hLogService, 0, NULL) ) { // wait for the service to start or stop // before returning while ( --dwTimeout && ERROR_SUCCESS == dwStatus ) { dwStatus = GetState ( szComputerName, dwCurrentState ); if ( SERVICE_START_PENDING == dwCurrentState ) { Sleep(200); } else { break; } } } else { dwStatus = GetLastError(); } } else { // *** error message } // *** ensure that dwCurrentState is not stopped? } } CloseServiceHandle ( hLogService ); } else { dwStatus = GetLastError(); } CloseServiceHandle (hSC); } else { dwStatus = GetLastError(); } // Todo: Update Auto Start service config return dwStatus; } #pragma warning ( default : 4706 ) int __cdecl _tmain ( INT argc, LPTSTR argv[] ) { DWORD dwStatus = 0; // Accept user input. // The command line arguments are the objects to sample. // If no user input, then display help. ParseCmd( argc, argv ); if (Commands[eSettings].bDefined) { dwStatus = ProcessSettingsFile ( Commands[eComputer].strValue, Commands[eSettings].strValue ); // Error messages displayed within ProcessSettingsFile method } if ( Commands[eStart].bDefined ) { dwStatus = StartQuery ( Commands[eComputer].strValue, Commands[eName].strValue ); // Error messages displayed within StartQuery method } if ( Commands[eStop].bDefined ) { dwStatus = StopQuery ( Commands[eComputer].strValue, Commands[eName].strValue ); // Error messages displayed within StopQuery method } // Error messages displayed within submethods, so reset dwStatus. dwStatus = 0; FreeCmd(); return dwStatus; }