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.
580 lines
16 KiB
580 lines
16 KiB
/******************************************************************************
|
|
|
|
Copyright (c) 1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
SystemMonitor.cpp
|
|
|
|
Abstract:
|
|
This file contains the implementation of the CPCHSystemMonitor class,
|
|
which implements the data collection functionality.
|
|
|
|
Revision History:
|
|
Davide Massarenti (Dmassare) 08/25/99
|
|
created
|
|
|
|
******************************************************************************/
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include <initguid.h>
|
|
#include <mstask.h> // for task scheduler apis
|
|
#include <msterr.h>
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static const ULONG INITIAL_RESCHEDULE_TIME = 10 * 60; // (10 minutes)
|
|
static const ULONG DATACOLLECTION_RESCHEDULE_TIME = 6 * 60 * 60; // (6 hours)
|
|
static const ULONG DATACOLLECTION_IDLE_TIME = 5; // (5 minutes)
|
|
static const ULONG SECONDS_IN_A_DAY = 24 * 60 * 60;
|
|
static const ULONG SECONDS_IN_A_MINUTE = 60;
|
|
static const ULONG MINUTES_IN_A_DAY = 24 * 60;
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static HRESULT Exec( LPCWSTR szExec )
|
|
{
|
|
MPC::wstring strCmdLine( szExec ); MPC::SubstituteEnvVariables( strCmdLine );
|
|
|
|
return MPC::ExecuteCommand( strCmdLine );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
CPCHSystemMonitor::CPCHSystemMonitor()
|
|
{
|
|
__HCP_FUNC_ENTRY( "CPCHSystemMonitor::CPCHSystemMonitor" );
|
|
|
|
m_fLoadCache = false; // bool m_fLoadCache;
|
|
m_fScanBatch = true; // bool m_fScanBatch;
|
|
m_fDataCollection = false; // bool m_fDataCollection;
|
|
|
|
(void)MPC::_MPC_Module.RegisterCallback( this, (void (CPCHSystemMonitor::*)())Shutdown );
|
|
}
|
|
|
|
CPCHSystemMonitor::~CPCHSystemMonitor()
|
|
{
|
|
MPC::_MPC_Module.UnregisterCallback( this );
|
|
|
|
Shutdown();
|
|
}
|
|
|
|
////////////////////
|
|
|
|
CPCHSystemMonitor* CPCHSystemMonitor::s_GLOBAL( NULL );
|
|
|
|
HRESULT CPCHSystemMonitor::InitializeSystem()
|
|
{
|
|
if(s_GLOBAL == NULL)
|
|
{
|
|
s_GLOBAL = new CPCHSystemMonitor;
|
|
}
|
|
|
|
return s_GLOBAL ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
void CPCHSystemMonitor::FinalizeSystem()
|
|
{
|
|
if(s_GLOBAL)
|
|
{
|
|
delete s_GLOBAL; s_GLOBAL = NULL;
|
|
}
|
|
}
|
|
|
|
////////////////////
|
|
|
|
void CPCHSystemMonitor::Shutdown()
|
|
{
|
|
Thread_Wait();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CPCHSystemMonitor::EnsureStarted()
|
|
{
|
|
__HCP_FUNC_ENTRY( "CPCHSystemMonitor::EnsureStarted" );
|
|
|
|
HRESULT hr;
|
|
|
|
|
|
if(Thread_IsRunning() == false &&
|
|
Thread_IsAborted() == false )
|
|
{
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, Thread_Start( this, Run, NULL ));
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
|
|
__HCP_FUNC_CLEANUP;
|
|
|
|
__HCP_FUNC_EXIT(hr);
|
|
}
|
|
|
|
HRESULT CPCHSystemMonitor::Run()
|
|
{
|
|
__HCP_FUNC_ENTRY( "CPCHSystemMonitor::Run" );
|
|
|
|
const DWORD s_dwNotify = FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
FILE_NOTIFY_CHANGE_DIR_NAME |
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE |
|
|
FILE_NOTIFY_CHANGE_CREATION;
|
|
|
|
HRESULT hr;
|
|
MPC::wstring strBatch( HC_ROOT_HELPSVC_BATCH ); MPC::SubstituteEnvVariables( strBatch );
|
|
HANDLE hBatchNotification = INVALID_HANDLE_VALUE;
|
|
DWORD dwTimeout = INFINITE;
|
|
MPC::SmartLock<_ThreadModel> lock( this );
|
|
|
|
|
|
::SetThreadPriority( ::GetCurrentThread(), THREAD_PRIORITY_LOWEST ); ::Sleep( 0 ); // Yield processor...
|
|
|
|
////
|
|
//// Don't touch task scheduler, it brings in too many things...
|
|
////
|
|
//// //
|
|
//// // Move forward the scheduled data collection by at least 10 minutes.
|
|
//// // Task scheduler is available only on normal boots.
|
|
//// //
|
|
//// if(::GetSystemMetrics( SM_CLEANBOOT ) == 0)
|
|
//// {
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, TaskScheduler_Add( true ));
|
|
//// }
|
|
|
|
|
|
hBatchNotification = ::FindFirstChangeNotificationW( strBatch.c_str(), TRUE, s_dwNotify );
|
|
|
|
|
|
while(Thread_IsAborted() == false)
|
|
{
|
|
DWORD dwRes;
|
|
|
|
|
|
lock = NULL;
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, RunLoop());
|
|
lock = this;
|
|
|
|
//
|
|
// Done, for now...
|
|
//
|
|
lock = NULL;
|
|
dwRes = Thread_WaitForEvents( hBatchNotification == INVALID_HANDLE_VALUE ? NULL : hBatchNotification, dwTimeout );
|
|
lock = this;
|
|
|
|
switch(dwRes)
|
|
{
|
|
case WAIT_OBJECT_0 :
|
|
case WAIT_ABANDONED_0:
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 1:
|
|
case WAIT_ABANDONED_0 + 1:
|
|
::FindNextChangeNotification( hBatchNotification );
|
|
|
|
dwTimeout = 1*1000; // Don't scan immediately, wait some time.
|
|
break;
|
|
|
|
case WAIT_TIMEOUT:
|
|
dwTimeout = INFINITE;
|
|
m_fScanBatch = true;
|
|
break;
|
|
|
|
|
|
case WAIT_FAILED:
|
|
__MPC_SET_WIN32_ERROR_AND_EXIT(hr, ::GetLastError());
|
|
}
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
|
|
__HCP_FUNC_CLEANUP;
|
|
|
|
if(hBatchNotification != INVALID_HANDLE_VALUE)
|
|
{
|
|
::FindCloseChangeNotification( hBatchNotification );
|
|
}
|
|
|
|
Thread_Abort (); // To tell the MPC:Thread object to close the worker thread...
|
|
Thread_Release(); // To tell the MPC:Thread object to clean up...
|
|
|
|
__HCP_FUNC_EXIT(hr);
|
|
}
|
|
|
|
HRESULT CPCHSystemMonitor::RunLoop()
|
|
{
|
|
__HCP_FUNC_ENTRY( "CPCHSystemMonitor::RunLoop" );
|
|
|
|
HRESULT hr;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Batch processing for the other update packages.
|
|
//
|
|
if(m_fScanBatch)
|
|
{
|
|
CComPtr<CPCHSetOfHelpTopics> sht;
|
|
|
|
m_fScanBatch = false;
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, MPC::CreateInstance( &sht ));
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, sht->ScanBatch());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Load the cache for active SKUs.
|
|
//
|
|
if(m_fLoadCache)
|
|
{
|
|
Taxonomy::LockingHandle handle;
|
|
Taxonomy::InstalledInstanceIterConst itBegin;
|
|
Taxonomy::InstalledInstanceIterConst itEnd;
|
|
Taxonomy::InstalledInstanceIterConst it;
|
|
|
|
|
|
m_fLoadCache = false;
|
|
|
|
|
|
//
|
|
// Get the list of SKU installed on the machine.
|
|
//
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, Taxonomy::InstalledInstanceStore::s_GLOBAL->GrabControl( handle ));
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, Taxonomy::InstalledInstanceStore::s_GLOBAL->SKU_GetList( itBegin, itEnd ));
|
|
|
|
|
|
//
|
|
// Enumerate all of the SKUs, creating the index.
|
|
//
|
|
for(it = itBegin; it != itEnd; it++)
|
|
{
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, Taxonomy::Cache::s_GLOBAL->LoadIfMarked( it->m_inst.m_ths ));
|
|
}
|
|
}
|
|
|
|
if(m_fDataCollection)
|
|
{
|
|
CComPtr<CSAFDataCollection> pdc;
|
|
|
|
m_fDataCollection = false;
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, MPC::CreateInstance( &pdc ));
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, pdc->ExecScheduledCollection());
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, TaskScheduler_Add( false ));
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
|
|
__HCP_FUNC_CLEANUP;
|
|
|
|
__HCP_FUNC_EXIT(hr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CPCHSystemMonitor::LoadCache()
|
|
{
|
|
__HCP_FUNC_ENTRY( "CPCHSystemMonitor::LoadCache" );
|
|
|
|
HRESULT hr;
|
|
MPC::SmartLock<_ThreadModel> lock( this );
|
|
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, EnsureStarted());
|
|
|
|
m_fLoadCache = true;
|
|
Thread_Signal();
|
|
|
|
hr = S_OK;
|
|
|
|
|
|
__HCP_FUNC_CLEANUP;
|
|
|
|
__HCP_FUNC_EXIT(hr);
|
|
}
|
|
|
|
HRESULT CPCHSystemMonitor::TriggerDataCollection( /*[in]*/ bool fStart )
|
|
{
|
|
__HCP_FUNC_ENTRY( "CPCHSystemMonitor::TriggerDataCollection" );
|
|
|
|
HRESULT hr;
|
|
MPC::SmartLock<_ThreadModel> lock( this );
|
|
|
|
if(fStart)
|
|
{
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, EnsureStarted());
|
|
|
|
m_fDataCollection = true;
|
|
Thread_Signal();
|
|
}
|
|
else
|
|
{
|
|
m_fDataCollection = false;
|
|
Thread_Signal();
|
|
}
|
|
|
|
hr = S_OK;
|
|
|
|
|
|
__HCP_FUNC_CLEANUP;
|
|
|
|
__HCP_FUNC_EXIT(hr);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CPCHSystemMonitor::TaskScheduler_Add( /*[in]*/ bool fAfterBoot )
|
|
{
|
|
MPC::wstring strDate;
|
|
|
|
if(SUCCEEDED(MPC::ConvertDateToString( MPC::GetLocalTime(), strDate, /*fGMT*/false, /*fCIM*/true, 0 )))
|
|
{
|
|
static const WCHAR s_szRoot [] = HC_REGISTRY_PCHSVC;
|
|
static const WCHAR s_szDataCollection[] = L"DataCollection";
|
|
|
|
(void)MPC::RegKey_Value_Write( strDate, s_szRoot, s_szDataCollection );
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CPCHSystemMonitor::TaskScheduler_Remove()
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
////HRESULT CPCHSystemMonitor::TaskScheduler_Add( /*[in]*/ bool fAfterBoot )
|
|
////{
|
|
//// __HCP_FUNC_ENTRY( "CPCHSystemMonitor::TaskScheduler_Add" );
|
|
////
|
|
//// HRESULT hr;
|
|
//// CComPtr<ITaskScheduler> pTaskScheduler;
|
|
//// CComPtr<ITask> pTask;
|
|
//// CComPtr<IUnknown> pTaskUnknown;
|
|
//// CComPtr<IScheduledWorkItem> pScheduledWorkItem;
|
|
//// bool fTaskExists = false;
|
|
////
|
|
//// WCHAR rgFileName[MAX_PATH];
|
|
//// CComBSTR bstrTaskName;
|
|
//// CComBSTR bstrComments;
|
|
////
|
|
//// ULONG ulTime = fAfterBoot ? INITIAL_RESCHEDULE_TIME : DATACOLLECTION_RESCHEDULE_TIME;
|
|
//// WORD wIdle = DATACOLLECTION_IDLE_TIME;
|
|
////
|
|
//// ////////////////////////////////////////
|
|
////
|
|
//// //
|
|
//// // Get our complete filename -- needed to create a task in the task scheduler.
|
|
//// //
|
|
//// __MPC_EXIT_IF_CALL_RETURNS_ZERO(hr, ::GetModuleFileNameW( NULL, rgFileName, MAX_PATH ));
|
|
////
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, MPC::LocalizeString( IDS_HELPSVC_TASKNAME , bstrTaskName ));
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, MPC::LocalizeString( IDS_HELPSVC_TASKCOMMENT, bstrComments ));
|
|
////
|
|
//// //
|
|
//// // First create the task scheduler.
|
|
//// //
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, ::CoCreateInstance( CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pTaskScheduler ));
|
|
////
|
|
////
|
|
//// //
|
|
//// // See if the task already exists in the task scheduler
|
|
//// //
|
|
//// if(SUCCEEDED(pTaskScheduler->Activate( bstrTaskName, IID_ITask, &pTaskUnknown )))
|
|
//// {
|
|
//// fTaskExists = true;
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pTaskUnknown->QueryInterface( IID_ITask, (void **)&pTask ));
|
|
//// }
|
|
//// else
|
|
//// {
|
|
//// //
|
|
//// // Create a new task and set its app name and parameters.
|
|
//// //
|
|
//// if(FAILED(hr = pTaskScheduler->NewWorkItem( bstrTaskName, CLSID_CTask, IID_ITask, (IUnknown**)&pTask )))
|
|
//// {
|
|
//// if(hr != ERROR_FILE_EXISTS)
|
|
//// {
|
|
//// __MPC_TRACE_HRESULT(hr);
|
|
//// __MPC_FUNC_LEAVE;
|
|
//// }
|
|
//// }
|
|
//// }
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pTask->QueryInterface( IID_IScheduledWorkItem, (void **)&pScheduledWorkItem ));
|
|
////
|
|
//// //
|
|
//// // Run under SYSTEM.
|
|
//// //
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pScheduledWorkItem->SetAccountInformation( L"", NULL ));
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pTask->SetApplicationName( CComBSTR( rgFileName ) ));
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pTask->SetParameters ( CComBSTR( L"-collect" ) ));
|
|
////
|
|
//// // Set the comment, so we know how this job go there.
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pScheduledWorkItem->SetComment( bstrComments ));
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pScheduledWorkItem->SetFlags ( 0 ));
|
|
////
|
|
////
|
|
////
|
|
////
|
|
//// // Now, fill in the trigger as necessary.
|
|
//// {
|
|
//// CComPtr<ITaskTrigger> pTaskTrigger;
|
|
//// SYSTEMTIME stNow;
|
|
//// DOUBLE dblNextScheduledTime;
|
|
//// TASK_TRIGGER ttTaskTrig;
|
|
////
|
|
////
|
|
//// ::ZeroMemory( &ttTaskTrig, sizeof(ttTaskTrig) );
|
|
//// ttTaskTrig.cbTriggerSize = sizeof(ttTaskTrig);
|
|
////
|
|
////
|
|
//// if(fTaskExists)
|
|
//// {
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pScheduledWorkItem->GetTrigger( 0, &pTaskTrigger ));
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pTaskTrigger ->GetTrigger( &ttTaskTrig ));
|
|
//// }
|
|
//// else
|
|
//// {
|
|
//// WORD wTrigNumber;
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pScheduledWorkItem->CreateTrigger( &wTrigNumber, &pTaskTrigger ));
|
|
//// }
|
|
////
|
|
////
|
|
//// //
|
|
//// // Calculate the exact time of next activation.
|
|
//// //
|
|
//// ::GetLocalTime ( &stNow );
|
|
//// ::SystemTimeToVariantTime( &stNow, &dblNextScheduledTime );
|
|
////
|
|
//// dblNextScheduledTime += (double)ulTime / SECONDS_IN_A_DAY;
|
|
//// ::VariantTimeToSystemTime( dblNextScheduledTime, &stNow );
|
|
////
|
|
////
|
|
//// ttTaskTrig.wBeginYear = stNow.wYear;
|
|
//// ttTaskTrig.wBeginMonth = stNow.wMonth;
|
|
//// ttTaskTrig.wBeginDay = stNow.wDay;
|
|
//// ttTaskTrig.wStartHour = stNow.wHour;
|
|
//// ttTaskTrig.wStartMinute = stNow.wMinute;
|
|
////
|
|
//// ttTaskTrig.MinutesDuration = MINUTES_IN_A_DAY;
|
|
//// ttTaskTrig.MinutesInterval = ulTime / SECONDS_IN_A_MINUTE;
|
|
//// ttTaskTrig.TriggerType = TASK_TIME_TRIGGER_DAILY;
|
|
////
|
|
//// ttTaskTrig.Type.Daily.DaysInterval = 1;
|
|
////
|
|
//// if(wIdle)
|
|
//// {
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pScheduledWorkItem->SetIdleWait( wIdle, 0x7FFF ));
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pScheduledWorkItem->SetFlags ( TASK_FLAG_START_ONLY_IF_IDLE ));
|
|
//// }
|
|
////
|
|
//// // Add this trigger to the task.
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pTaskTrigger->SetTrigger( &ttTaskTrig ));
|
|
//// }
|
|
////
|
|
//// //
|
|
//// // Make the changes permanent.
|
|
//// //
|
|
//// {
|
|
//// CComPtr<IPersistFile> pIPF;
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pTask->QueryInterface( IID_IPersistFile, (void **)&pIPF ));
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, pIPF->Save( NULL, FALSE ));
|
|
//// }
|
|
////
|
|
////
|
|
//// hr = S_OK;
|
|
////
|
|
////
|
|
//// __HCP_FUNC_CLEANUP;
|
|
////
|
|
//// __HCP_FUNC_EXIT(hr);
|
|
////}
|
|
////
|
|
////HRESULT CPCHSystemMonitor::TaskScheduler_Remove()
|
|
////{
|
|
//// __HCP_FUNC_ENTRY( "CPCHSystemMonitor::TaskScheduler_Remove" );
|
|
////
|
|
//// HRESULT hr;
|
|
//// CComPtr<ITaskScheduler> pTaskScheduler;
|
|
//// CComBSTR bstrTaskName;
|
|
////
|
|
////
|
|
//// ////////////////////////////////////////
|
|
////
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, MPC::LocalizeString( IDS_HELPSVC_TASKNAME, bstrTaskName ));
|
|
////
|
|
//// //
|
|
//// // First create the task scheduler.
|
|
//// //
|
|
//// __MPC_EXIT_IF_METHOD_FAILS(hr, ::CoCreateInstance( CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pTaskScheduler ));
|
|
////
|
|
////
|
|
//// if(FAILED(hr = pTaskScheduler->Delete( bstrTaskName )))
|
|
//// {
|
|
//// if(hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
|
|
//// {
|
|
//// __MPC_TRACE_HRESULT(hr);
|
|
//// __MPC_FUNC_LEAVE;
|
|
//// }
|
|
//// }
|
|
////
|
|
////
|
|
//// hr = S_OK;
|
|
////
|
|
////
|
|
//// __HCP_FUNC_CLEANUP;
|
|
////
|
|
//// __HCP_FUNC_EXIT(hr);
|
|
////}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
HRESULT CPCHSystemMonitor::Startup()
|
|
{
|
|
__HCP_FUNC_ENTRY( "CPCHSystemMonitor::Startup" );
|
|
|
|
HRESULT hr;
|
|
|
|
|
|
//
|
|
// This forces the Content Store to be loaded.
|
|
//
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, CPCHContentStore::s_GLOBAL->Acquire());
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, CPCHContentStore::s_GLOBAL->Release());
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Force the loading of the cache.
|
|
//
|
|
{
|
|
Taxonomy::LockingHandle handle;
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, Taxonomy::InstalledInstanceStore::s_GLOBAL->GrabControl( handle ));
|
|
}
|
|
|
|
__MPC_EXIT_IF_METHOD_FAILS(hr, EnsureStarted());
|
|
|
|
hr = S_OK;
|
|
|
|
|
|
__HCP_FUNC_CLEANUP;
|
|
|
|
__HCP_FUNC_EXIT(hr);
|
|
}
|
|
|