You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
550 lines
16 KiB
550 lines
16 KiB
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <assert.h>
|
|
#include <pdh.h>
|
|
#include <pdhp.h>
|
|
#include <pdhmsg.h>
|
|
#include <winperf.h>
|
|
#include <tchar.h>
|
|
#include <varg.h>
|
|
|
|
#include "rpdh.h"
|
|
|
|
const DWORD dwFileHeaderLength = 13;
|
|
const DWORD dwTypeLoc = 2;
|
|
const DWORD dwFieldLength = 7;
|
|
const DWORD dwPerfmonTypeLength = 5;
|
|
|
|
LPCSTR szTsvType = "PDH-TSV";
|
|
LPCSTR szCsvType = "PDH-CSV";
|
|
LPCSTR szBinaryType = "PDH-BIN";
|
|
LPCWSTR cszPerfmonLogSig = (LPCWSTR)L"Loges";
|
|
|
|
#define MemoryAllocate(x) \
|
|
((LPVOID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, x))
|
|
#define MemoryFree(x) \
|
|
if( x ){ ((VOID) HeapFree(GetProcessHeap(), 0, x)); x = NULL; }
|
|
#define MemoryResize(x,y) \
|
|
((LPVOID) HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, x, y));
|
|
|
|
#define MAKEULONGLONG(low, high) \
|
|
((ULONGLONG) (((DWORD) (low)) | ((ULONGLONG) ((DWORD) (high))) << 32))
|
|
|
|
#define PDH_BLG_HEADER_SIZE 24
|
|
#define PDH_MAX_BUFFER 0x01000000
|
|
#define PDH_HEADER_BUFFER 65536
|
|
|
|
typedef struct _PDHI_BINARY_LOG_RECORD_HEADER {
|
|
DWORD dwType;
|
|
DWORD dwLength;
|
|
} PDHI_BINARY_LOG_RECORD_HEADER, *PPDHI_BINARY_LOG_RECORD_HEADER;
|
|
|
|
PDH_STATUS
|
|
R_PdhRelog( LPTSTR szSource, HQUERY hQuery, PPDH_RELOG_INFO_W pRelogInfo )
|
|
{
|
|
PDH_STATUS pdhStatus = ERROR_SUCCESS;
|
|
HLOG hLogOut;
|
|
ULONG nSampleCount = 0;
|
|
ULONG nSamplesWritten = 0;
|
|
|
|
ULONG nRecordSkip;
|
|
|
|
pdhStatus = PdhOpenLogW(
|
|
pRelogInfo->strLog,
|
|
pRelogInfo->dwFlags,
|
|
&pRelogInfo->dwFileFormat,
|
|
hQuery,
|
|
0,
|
|
NULL,
|
|
&hLogOut
|
|
);
|
|
|
|
if( pdhStatus == ERROR_SUCCESS ){
|
|
|
|
DWORD dwNumEntries = 1;
|
|
DWORD dwBufferSize = sizeof(PDH_TIME_INFO);
|
|
PDH_TIME_INFO TimeInfo;
|
|
|
|
ZeroMemory( &TimeInfo, sizeof( PDH_TIME_INFO ) );
|
|
|
|
pdhStatus = PdhGetDataSourceTimeRange(
|
|
szSource,
|
|
&dwNumEntries,
|
|
&TimeInfo,
|
|
&dwBufferSize
|
|
);
|
|
|
|
if( pRelogInfo->TimeInfo.StartTime > TimeInfo.StartTime &&
|
|
pRelogInfo->TimeInfo.StartTime < TimeInfo.EndTime ){
|
|
|
|
TimeInfo.StartTime = pRelogInfo->TimeInfo.StartTime;
|
|
}
|
|
|
|
if( pRelogInfo->TimeInfo.EndTime < TimeInfo.EndTime &&
|
|
pRelogInfo->TimeInfo.EndTime > TimeInfo.StartTime ){
|
|
|
|
TimeInfo.EndTime = pRelogInfo->TimeInfo.EndTime;
|
|
}
|
|
|
|
if( TimeInfo.EndTime > TimeInfo.StartTime ){
|
|
pdhStatus = PdhSetQueryTimeRange( hQuery, &TimeInfo );
|
|
}
|
|
|
|
nRecordSkip = pRelogInfo->TimeInfo.SampleCount >= 1 ? pRelogInfo->TimeInfo.SampleCount : 1;
|
|
|
|
while( ERROR_SUCCESS == pdhStatus ){
|
|
|
|
if( nSampleCount++ % nRecordSkip ){
|
|
pdhStatus = PdhCollectQueryData( hQuery );
|
|
continue;
|
|
}
|
|
|
|
if( ERROR_SUCCESS == pdhStatus ){
|
|
pdhStatus = PdhUpdateLog( hLogOut, NULL );
|
|
nSamplesWritten++;
|
|
}
|
|
}
|
|
|
|
if( PDH_NO_MORE_DATA == pdhStatus ||
|
|
(PDH_ENTRY_NOT_IN_LOG_FILE == pdhStatus && pRelogInfo->dwFileFormat == PDH_LOG_TYPE_BINARY) ){
|
|
|
|
pdhStatus = PdhCloseLog( hLogOut, 0 );
|
|
if( ERROR_SUCCESS == pdhStatus ){
|
|
PdhGetDataSourceTimeRange( pRelogInfo->strLog, &dwNumEntries, &pRelogInfo->TimeInfo, &dwBufferSize );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pRelogInfo->TimeInfo.SampleCount = nSamplesWritten;
|
|
|
|
return pdhStatus;
|
|
}
|
|
|
|
DWORD
|
|
GetLogFileType (
|
|
IN HANDLE hLogFile
|
|
)
|
|
{
|
|
CHAR cBuffer[MAX_PATH];
|
|
CHAR cType[MAX_PATH];
|
|
WCHAR wcType[MAX_PATH];
|
|
BOOL bStatus;
|
|
DWORD dwResult;
|
|
DWORD dwBytesRead;
|
|
|
|
memset (&cBuffer[0], 0, sizeof(cBuffer));
|
|
memset (&cType[0], 0, sizeof(cType));
|
|
memset (&wcType[0], 0, sizeof(wcType));
|
|
|
|
// read first log file record
|
|
SetFilePointer (hLogFile, 0, NULL, FILE_BEGIN);
|
|
|
|
bStatus = ReadFile (hLogFile,
|
|
(LPVOID)cBuffer,
|
|
dwFileHeaderLength,
|
|
&dwBytesRead,
|
|
NULL);
|
|
|
|
if (bStatus) {
|
|
// read header record to get type
|
|
lstrcpynA (cType, (LPSTR)(cBuffer+dwTypeLoc), dwFieldLength+1);
|
|
if (lstrcmpA(cType, szTsvType) == 0) {
|
|
dwResult = PDH_LOG_TYPE_TSV;
|
|
} else if (lstrcmpA(cType, szCsvType) == 0) {
|
|
dwResult = PDH_LOG_TYPE_CSV;
|
|
} else if (lstrcmpA(cType, szBinaryType) == 0) {
|
|
dwResult = PDH_LOG_TYPE_RETIRED_BIN_;
|
|
} else {
|
|
// perfmon log file type string is in a different
|
|
// location than sysmon logs and used wide chars.
|
|
lstrcpynW (wcType, (LPWSTR)cBuffer, dwPerfmonTypeLength+1);
|
|
if (lstrcmpW(wcType, cszPerfmonLogSig) == 0) {
|
|
dwResult = PDH_LOG_TYPE_PERFMON;
|
|
} else {
|
|
dwResult = PDH_LOG_TYPE_UNDEFINED;
|
|
}
|
|
}
|
|
} else {
|
|
// unable to read file
|
|
dwResult = PDH_LOG_TYPE_UNDEFINED;
|
|
}
|
|
return dwResult;
|
|
|
|
}
|
|
|
|
PDH_STATUS
|
|
R_PdhBlgLogFileHeader(LPCWSTR szFile1, LPCWSTR szFile2)
|
|
{
|
|
BOOL bReturn = FALSE;
|
|
PDH_STATUS Status = ERROR_SUCCESS;
|
|
LPWSTR szHeaderList1 = NULL;
|
|
LPWSTR szHeaderList2 = NULL;
|
|
DWORD dwHeader1 = 0;
|
|
DWORD dwHeader2 = 0;
|
|
LPWSTR szName1;
|
|
LPWSTR szName2;
|
|
|
|
Status = PdhListLogFileHeaderW(szFile1, szHeaderList1, &dwHeader1);
|
|
if (Status != ERROR_SUCCESS && Status != PDH_MORE_DATA && Status != PDH_INSUFFICIENT_BUFFER){
|
|
goto Cleanup;
|
|
}
|
|
szHeaderList1 = (LPWSTR)MemoryAllocate( (dwHeader1+1)*sizeof(WCHAR) );
|
|
Status = PdhListLogFileHeaderW(szFile1, szHeaderList1, &dwHeader1);
|
|
if (Status != ERROR_SUCCESS ){
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = PdhListLogFileHeaderW(szFile2, szHeaderList2, &dwHeader2);
|
|
if (Status != ERROR_SUCCESS && Status != PDH_MORE_DATA && Status != PDH_INSUFFICIENT_BUFFER){
|
|
goto Cleanup;
|
|
}
|
|
szHeaderList2 = (LPWSTR)MemoryAllocate( (dwHeader2+1)*sizeof(WCHAR) );
|
|
Status = PdhListLogFileHeaderW(szFile2, szHeaderList2, &dwHeader2);
|
|
if (Status != ERROR_SUCCESS ){
|
|
goto Cleanup;
|
|
}
|
|
|
|
for (szName1 = szHeaderList1, szName2 = szHeaderList2;
|
|
szName1[0] != L'\0' && szName2[0] != L'\0';
|
|
szName1 += (lstrlenW(szName1) + 1),
|
|
szName2 += (lstrlenW(szName2) + 1)) {
|
|
|
|
if (lstrcmpiW(szName1, szName2) != 0) {
|
|
varg_printf( g_debug, _T("(%s) != (%s)\n"), szName1, szName2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (szName1[0] != L'\0' || szName2[0] != L'\0'){
|
|
Status = PDH_HEADER_MISMATCH;
|
|
}
|
|
|
|
Cleanup:
|
|
MemoryFree( szHeaderList1 );
|
|
MemoryFree( szHeaderList2 );
|
|
|
|
return Status;
|
|
}
|
|
|
|
PDH_STATUS
|
|
R_PdhGetLogFileType(
|
|
IN LPCWSTR LogFileName,
|
|
IN LPDWORD LogFileType)
|
|
{
|
|
PDH_STATUS pdhStatus = ERROR_SUCCESS;
|
|
HANDLE hFile;
|
|
DWORD dwLogFormat;
|
|
|
|
if (LogFileName == NULL) {
|
|
pdhStatus = PDH_INVALID_ARGUMENT;
|
|
}
|
|
else {
|
|
__try {
|
|
dwLogFormat = * LogFileType;
|
|
* LogFileType = dwLogFormat;
|
|
if (* LogFileName == L'\0') {
|
|
pdhStatus = PDH_INVALID_ARGUMENT;
|
|
}
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
pdhStatus = PDH_INVALID_ARGUMENT;
|
|
}
|
|
}
|
|
|
|
if (pdhStatus == ERROR_SUCCESS) {
|
|
hFile = CreateFileW(LogFileName,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) {
|
|
pdhStatus = PDH_LOG_FILE_OPEN_ERROR;
|
|
}
|
|
}
|
|
|
|
if (pdhStatus == ERROR_SUCCESS) {
|
|
dwLogFormat = GetLogFileType(hFile);
|
|
CloseHandle(hFile);
|
|
}
|
|
|
|
if (pdhStatus == ERROR_SUCCESS) {
|
|
* LogFileType = dwLogFormat;
|
|
}
|
|
|
|
return pdhStatus;
|
|
}
|
|
|
|
PDH_STATUS
|
|
R_PdhRelogCopyFile(
|
|
HANDLE hOutFile,
|
|
HANDLE hInFile,
|
|
BOOL bCopyHeader
|
|
)
|
|
{
|
|
BOOL bResult = TRUE;
|
|
PDH_STATUS pdhStatus = ERROR_SUCCESS;
|
|
DWORD dwSizeLow = 0;
|
|
DWORD dwSizeHigh = 0;
|
|
DWORD dwBuffer = PDH_MAX_BUFFER;
|
|
ULONGLONG lFileSize = 0;
|
|
ULONGLONG lCurrent = 0;
|
|
LPBYTE pBuffer = NULL;
|
|
LPBYTE pCurrent = NULL;
|
|
|
|
PPDHI_BINARY_LOG_RECORD_HEADER pPdhBlgHeader = NULL;
|
|
|
|
dwSizeLow = GetFileSize(hInFile, & dwSizeHigh);
|
|
lFileSize = MAKEULONGLONG(dwSizeLow, dwSizeHigh);
|
|
lCurrent = PDH_BLG_HEADER_SIZE + sizeof(PDHI_BINARY_LOG_RECORD_HEADER);
|
|
if (lCurrent > lFileSize) {
|
|
pdhStatus = PDH_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pBuffer = (LPBYTE)MemoryAllocate(dwBuffer);
|
|
if (pBuffer == NULL) {
|
|
pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
dwSizeLow = (DWORD) lCurrent;
|
|
dwSizeHigh = 0;
|
|
bResult = ReadFile(hInFile, pBuffer, dwSizeLow, & dwSizeHigh, NULL);
|
|
|
|
if ((bResult == FALSE) || (dwSizeLow != dwSizeHigh)) {
|
|
pdhStatus = PDH_INVALID_DATA;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPdhBlgHeader = (PPDHI_BINARY_LOG_RECORD_HEADER)
|
|
(pBuffer + PDH_BLG_HEADER_SIZE);
|
|
if (pPdhBlgHeader->dwType != 0x01024C42) {
|
|
pdhStatus = PDH_INVALID_ARGUMENT;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (pPdhBlgHeader->dwLength + PDH_BLG_HEADER_SIZE > dwBuffer) {
|
|
LPBYTE pTemp = pBuffer;
|
|
pBuffer = (LPBYTE)MemoryResize(pBuffer, pPdhBlgHeader->dwLength);
|
|
if (pBuffer == NULL) {
|
|
MemoryFree(pTemp);
|
|
pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
if ((lCurrent + dwSizeLow) > lFileSize) {
|
|
pdhStatus = PDH_INVALID_HANDLE;
|
|
goto Cleanup;
|
|
}
|
|
pCurrent = (LPBYTE) (pBuffer + dwSizeLow);
|
|
dwSizeLow = pPdhBlgHeader->dwLength
|
|
- sizeof(PDHI_BINARY_LOG_RECORD_HEADER);
|
|
dwSizeHigh = 0;
|
|
bResult = ReadFile(hInFile, pCurrent, dwSizeLow, & dwSizeHigh, NULL);
|
|
if ((bResult == FALSE) || (dwSizeLow != dwSizeHigh)) {
|
|
pdhStatus = PDH_INVALID_DATA;
|
|
goto Cleanup;
|
|
}
|
|
|
|
lCurrent += dwSizeLow;
|
|
if (bCopyHeader) {
|
|
dwSizeLow = dwSizeHigh = (DWORD) lCurrent;
|
|
bResult = WriteFile(hOutFile, pBuffer, dwSizeLow, & dwSizeHigh, NULL);
|
|
if ((bResult == FALSE) || (dwSizeLow != dwSizeHigh)) {
|
|
pdhStatus = PDH_INVALID_DATA;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
while (lCurrent + sizeof(PDHI_BINARY_LOG_RECORD_HEADER) < lFileSize) {
|
|
ZeroMemory(pBuffer, dwBuffer);
|
|
dwSizeLow = sizeof(PDHI_BINARY_LOG_RECORD_HEADER);
|
|
dwSizeHigh = 0;
|
|
bResult = ReadFile(hInFile, pBuffer, dwSizeLow, & dwSizeHigh, NULL);
|
|
if ((bResult == FALSE) || (dwSizeLow != dwSizeHigh)) {
|
|
pdhStatus = PDH_INVALID_DATA;
|
|
goto Cleanup;
|
|
}
|
|
|
|
pPdhBlgHeader = (PPDHI_BINARY_LOG_RECORD_HEADER) pBuffer;
|
|
if ((pPdhBlgHeader->dwType & 0x0000FFFF) != 0x00004C42) {
|
|
pdhStatus = PDH_INVALID_ARGUMENT;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (pPdhBlgHeader->dwLength > dwBuffer) {
|
|
LPBYTE pTemp = pBuffer;
|
|
pBuffer = (LPBYTE)MemoryResize(pBuffer, pPdhBlgHeader->dwLength);
|
|
if (pBuffer == NULL) {
|
|
MemoryFree(pTemp);
|
|
pdhStatus = PDH_MEMORY_ALLOCATION_FAILURE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
if ((lCurrent + pPdhBlgHeader->dwLength) > lFileSize) {
|
|
pdhStatus = PDH_INVALID_HANDLE;
|
|
bResult = FALSE;
|
|
goto Cleanup;
|
|
}
|
|
pCurrent = (LPBYTE) (pBuffer + dwSizeLow);
|
|
dwSizeLow = pPdhBlgHeader->dwLength
|
|
- sizeof(PDHI_BINARY_LOG_RECORD_HEADER);
|
|
dwSizeHigh = 0;
|
|
bResult = ReadFile(hInFile, pCurrent, dwSizeLow, & dwSizeHigh, NULL);
|
|
if ((bResult == FALSE) || (dwSizeLow != dwSizeHigh)) {
|
|
pdhStatus = PDH_INVALID_DATA;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ((pPdhBlgHeader->dwType & 0x00FF0000) == 0x00030000) {
|
|
dwSizeLow = dwSizeHigh = pPdhBlgHeader->dwLength;
|
|
bResult = WriteFile(
|
|
hOutFile, pBuffer, dwSizeLow, & dwSizeHigh, NULL);
|
|
if ((bResult == FALSE) || (dwSizeLow != dwSizeHigh)) {
|
|
pdhStatus = PDH_INVALID_DATA;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
lCurrent += pPdhBlgHeader->dwLength;
|
|
}
|
|
|
|
Cleanup:
|
|
if (pBuffer != NULL){
|
|
MemoryFree(pBuffer);
|
|
}
|
|
|
|
return pdhStatus;
|
|
}
|
|
|
|
PDH_STATUS
|
|
R_PdhAppendLog( LPTSTR szSource, LPTSTR szAppend )
|
|
{
|
|
HANDLE hSource = INVALID_HANDLE_VALUE;
|
|
HANDLE hAppend = INVALID_HANDLE_VALUE;
|
|
BOOL bResult = TRUE;
|
|
PDH_STATUS pdhStatus;
|
|
|
|
DWORD dwFormat;
|
|
PDH_TIME_INFO TimeSource;
|
|
PDH_TIME_INFO TimeAppend;
|
|
LPTSTR mszSourceHeader = NULL;
|
|
LPTSTR mszAppendHeader = NULL;
|
|
DWORD dwSourceSize = 0;
|
|
DWORD dwAppendSize = 0;
|
|
|
|
DWORD dwNumEntries = 1;
|
|
DWORD dwBufferSize = sizeof(PDH_TIME_INFO);
|
|
|
|
//
|
|
// Compare the log types, both must be Windows 2000 binary
|
|
//
|
|
|
|
pdhStatus = R_PdhGetLogFileType( szSource, &dwFormat );
|
|
if( ERROR_SUCCESS != pdhStatus || PDH_LOG_TYPE_RETIRED_BIN_ != dwFormat ){
|
|
if( ERROR_SUCCESS == pdhStatus ){
|
|
pdhStatus = PDH_TYPE_MISMATCH;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
pdhStatus = R_PdhGetLogFileType( szAppend, &dwFormat );
|
|
if( ERROR_SUCCESS != pdhStatus || PDH_LOG_TYPE_RETIRED_BIN_ != dwFormat ){
|
|
if( ERROR_SUCCESS == pdhStatus ){
|
|
pdhStatus = PDH_TYPE_MISMATCH;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Compare the time ranges, source file must start after append end
|
|
//
|
|
|
|
ZeroMemory( &TimeSource, sizeof( PDH_TIME_INFO ) );
|
|
ZeroMemory( &TimeAppend, sizeof( PDH_TIME_INFO ) );
|
|
|
|
pdhStatus = PdhGetDataSourceTimeRange(
|
|
szSource,
|
|
&dwNumEntries,
|
|
&TimeSource,
|
|
&dwBufferSize
|
|
);
|
|
if( ERROR_SUCCESS != pdhStatus ){
|
|
goto cleanup;
|
|
}
|
|
|
|
pdhStatus = PdhGetDataSourceTimeRange(
|
|
szAppend,
|
|
&dwNumEntries,
|
|
&TimeAppend,
|
|
&dwBufferSize
|
|
);
|
|
if( ERROR_SUCCESS != pdhStatus ){
|
|
goto cleanup;
|
|
}
|
|
|
|
if( TimeSource.EndTime > TimeAppend.StartTime ){
|
|
pdhStatus = PDH_TIME_MISMATCH;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Compare the headers, must be exact match
|
|
//
|
|
|
|
pdhStatus = R_PdhBlgLogFileHeader( szSource, szAppend );
|
|
if( ERROR_SUCCESS != pdhStatus ){
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// All good, now do the actual append
|
|
//
|
|
|
|
hSource = CreateFileW(
|
|
szSource,
|
|
GENERIC_READ|GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
hAppend = CreateFileW(
|
|
szAppend,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if ( INVALID_HANDLE_VALUE == hSource || INVALID_HANDLE_VALUE == hAppend ){
|
|
pdhStatus = PDH_LOG_FILE_OPEN_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
SetFilePointer( hSource, NULL, NULL, FILE_END );
|
|
pdhStatus = R_PdhRelogCopyFile( hSource, hAppend, FALSE);
|
|
|
|
cleanup:
|
|
MemoryFree( mszSourceHeader );
|
|
MemoryFree( mszAppendHeader );
|
|
|
|
if ( INVALID_HANDLE_VALUE != hAppend ) {
|
|
CloseHandle(hAppend);
|
|
}
|
|
if ( INVALID_HANDLE_VALUE != hSource ) {
|
|
CloseHandle(hSource);
|
|
}
|
|
|
|
return pdhStatus;
|
|
}
|
|
|
|
|