1942 lines
47 KiB
C++
1942 lines
47 KiB
C++
|
//---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
// Copyright (C) Microsoft Corporation, 1995
|
||
|
//
|
||
|
// File: cserv.cxx
|
||
|
//
|
||
|
// Contents: Contains methods for the following objects
|
||
|
// CWinNTService,
|
||
|
//
|
||
|
// History: 12/11/95 ramv (Ram Viswanathan) Created.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
#include "winnt.hxx"
|
||
|
#pragma hdrstop
|
||
|
#define INITGUID
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// class CWinNTService methods
|
||
|
//
|
||
|
|
||
|
DEFINE_IDispatch_ExtMgr_Implementation(CWinNTService);
|
||
|
DEFINE_IADsExtension_ExtMgr_Implementation(CWinNTService);
|
||
|
DEFINE_IADs_TempImplementation(CWinNTService);
|
||
|
DEFINE_IADs_PutGetImplementation(CWinNTService,ServiceClass,gdwServiceTableSize);
|
||
|
DEFINE_IADsPropertyList_Implementation(CWinNTService, ServiceClass,gdwServiceTableSize)
|
||
|
|
||
|
CWinNTService::CWinNTService()
|
||
|
{
|
||
|
_pDispMgr = NULL;
|
||
|
_pExtMgr = NULL;
|
||
|
_pPropertyCache = NULL;
|
||
|
|
||
|
_pszServiceName = NULL;
|
||
|
_pszServerName = NULL;
|
||
|
_pszPath = NULL;
|
||
|
|
||
|
_schSCManager = NULL;
|
||
|
_schService = NULL;
|
||
|
_dwWaitHint = 0;
|
||
|
_dwCheckPoint = 0;
|
||
|
_fValidHandle = FALSE;
|
||
|
|
||
|
ENLIST_TRACKING(CWinNTService);
|
||
|
}
|
||
|
|
||
|
CWinNTService::~CWinNTService()
|
||
|
{
|
||
|
if(_fValidHandle){
|
||
|
//
|
||
|
// an open handle exists, blow it away
|
||
|
//
|
||
|
WinNTCloseService();
|
||
|
_fValidHandle = FALSE;
|
||
|
}
|
||
|
|
||
|
if(_pszServiceName){
|
||
|
FreeADsStr(_pszServiceName);
|
||
|
}
|
||
|
|
||
|
if(_pszServerName){
|
||
|
FreeADsStr(_pszServerName);
|
||
|
}
|
||
|
|
||
|
if(_pszPath){
|
||
|
FreeADsStr(_pszPath);
|
||
|
}
|
||
|
|
||
|
delete _pExtMgr; // created last, destroyed first
|
||
|
|
||
|
delete _pDispMgr;
|
||
|
|
||
|
delete _pPropertyCache;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::Create
|
||
|
//
|
||
|
// Synopsis: Static function used to create a Service object. This
|
||
|
// will be called by BindToObject
|
||
|
//
|
||
|
// Arguments: [ppWinNTService] -- Ptr to a ptr to a new Service object.
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 12-11-95 RamV Created.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT
|
||
|
CWinNTService::Create(LPTSTR pszADsParent,
|
||
|
LPTSTR pszDomainName,
|
||
|
LPTSTR pszServerName,
|
||
|
LPTSTR pszServiceName,
|
||
|
DWORD dwObjectState,
|
||
|
REFIID riid,
|
||
|
CWinNTCredentials& Credentials,
|
||
|
LPVOID * ppvoid
|
||
|
)
|
||
|
|
||
|
{
|
||
|
CWinNTService FAR * pCWinNTService = NULL;
|
||
|
HRESULT hr;
|
||
|
|
||
|
//
|
||
|
// Create the Service Object
|
||
|
//
|
||
|
|
||
|
|
||
|
hr = AllocateServiceObject(pszServerName,
|
||
|
pszServiceName,
|
||
|
&pCWinNTService);
|
||
|
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
ADsAssert(pCWinNTService->_pDispMgr);
|
||
|
|
||
|
|
||
|
hr = pCWinNTService->InitializeCoreObject(pszADsParent,
|
||
|
pszServiceName,
|
||
|
SERVICE_CLASS_NAME,
|
||
|
SERVICE_SCHEMA_NAME,
|
||
|
CLSID_WinNTService,
|
||
|
dwObjectState);
|
||
|
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
hr = SetLPTSTRPropertyInCache(pCWinNTService->_pPropertyCache,
|
||
|
TEXT("HostComputer"),
|
||
|
pCWinNTService->_Parent,
|
||
|
TRUE
|
||
|
);
|
||
|
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
pCWinNTService->_Credentials = Credentials;
|
||
|
hr = pCWinNTService->_Credentials.RefServer(pszServerName);
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
|
||
|
//
|
||
|
// Load ext mgr and extensions
|
||
|
//
|
||
|
|
||
|
hr = ADSILoadExtensionManager(
|
||
|
SERVICE_CLASS_NAME,
|
||
|
(IADsService *) pCWinNTService,
|
||
|
pCWinNTService->_pDispMgr,
|
||
|
Credentials,
|
||
|
&pCWinNTService->_pExtMgr
|
||
|
);
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
ADsAssert(pCWinNTService->_pExtMgr);
|
||
|
|
||
|
// check if the call is from UMI
|
||
|
if(Credentials.GetFlags() & ADS_AUTH_RESERVED) {
|
||
|
//
|
||
|
// we do not pass riid to InitUmiObject below. This is because UMI object
|
||
|
// does not support IDispatch. There are several places in ADSI code where
|
||
|
// riid passed into this function is defaulted to IID_IDispatch -
|
||
|
// IADsContainer::Create for example. To handle these cases, we always
|
||
|
// request IID_IUnknown from the UMI object. Subsequent code within UMI
|
||
|
// will QI for the appropriate interface.
|
||
|
//
|
||
|
if(3 == pCWinNTService->_dwNumComponents) {
|
||
|
pCWinNTService->_CompClasses[0] = L"Domain";
|
||
|
pCWinNTService->_CompClasses[1] = L"Computer";
|
||
|
pCWinNTService->_CompClasses[2] = L"Service";
|
||
|
}
|
||
|
else if(2 == pCWinNTService->_dwNumComponents) {
|
||
|
// no workstation services
|
||
|
pCWinNTService->_CompClasses[0] = L"Computer";
|
||
|
pCWinNTService->_CompClasses[1] = L"Service";
|
||
|
}
|
||
|
else
|
||
|
BAIL_ON_FAILURE(hr = UMI_E_FAIL);
|
||
|
|
||
|
hr = pCWinNTService->InitUmiObject(
|
||
|
pCWinNTService->_Credentials,
|
||
|
ServiceClass,
|
||
|
gdwServiceTableSize,
|
||
|
pCWinNTService->_pPropertyCache,
|
||
|
(IUnknown *)(INonDelegatingUnknown *) pCWinNTService,
|
||
|
pCWinNTService->_pExtMgr,
|
||
|
IID_IUnknown,
|
||
|
ppvoid
|
||
|
);
|
||
|
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
//
|
||
|
// UMI object was created and the interface was obtained successfully.
|
||
|
// UMI object now has a reference to the inner unknown of IADs, since
|
||
|
// the call to Release() below is not going to be made in this case.
|
||
|
//
|
||
|
RRETURN(hr);
|
||
|
}
|
||
|
|
||
|
hr = pCWinNTService->QueryInterface(riid, (void **)ppvoid);
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
pCWinNTService->Release();
|
||
|
|
||
|
RRETURN(hr);
|
||
|
|
||
|
error:
|
||
|
|
||
|
delete pCWinNTService;
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
CWinNTService::AllocateServiceObject(
|
||
|
LPTSTR pszServerName,
|
||
|
LPTSTR pszServiceName,
|
||
|
CWinNTService ** ppService
|
||
|
)
|
||
|
{
|
||
|
CWinNTService FAR * pService = NULL;
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
pService = new CWinNTService();
|
||
|
if (pService == NULL) {
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
pService->_pDispMgr = new CAggregatorDispMgr;
|
||
|
if (pService->_pDispMgr == NULL) {
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
|
||
|
pService->_pszServerName =
|
||
|
AllocADsStr(pszServerName);
|
||
|
|
||
|
if(!(pService->_pszServerName)){
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
|
||
|
pService->_pszServiceName =
|
||
|
AllocADsStr(pszServiceName);
|
||
|
|
||
|
if(!(pService->_pszServiceName)){
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
hr = LoadTypeInfoEntry(pService->_pDispMgr,
|
||
|
LIBID_ADs,
|
||
|
IID_IADsService,
|
||
|
(IADsService *)pService,
|
||
|
DISPID_REGULAR
|
||
|
);
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
hr = LoadTypeInfoEntry(pService->_pDispMgr,
|
||
|
LIBID_ADs,
|
||
|
IID_IADsServiceOperations,
|
||
|
(IADsServiceOperations *)pService,
|
||
|
DISPID_REGULAR
|
||
|
);
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
|
||
|
hr = CPropertyCache::createpropertycache(
|
||
|
ServiceClass,
|
||
|
gdwServiceTableSize,
|
||
|
(CCoreADsObject *)pService,
|
||
|
&(pService->_pPropertyCache)
|
||
|
);
|
||
|
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
|
||
|
(pService->_pDispMgr)->RegisterPropertyCache(
|
||
|
pService->_pPropertyCache
|
||
|
);
|
||
|
|
||
|
*ppService = pService;
|
||
|
|
||
|
RRETURN(hr);
|
||
|
|
||
|
error:
|
||
|
|
||
|
//
|
||
|
// direct memeber assignement assignement at pt of creation, so
|
||
|
// do NOT delete _pPropertyCache or _pDisMgr here to avoid attempt
|
||
|
// of deletion again in pPrintJob destructor and AV
|
||
|
//
|
||
|
|
||
|
delete pService;
|
||
|
|
||
|
RRETURN(hr);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* IUnknown methods for service object */
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Function: QueryInterface
|
||
|
//
|
||
|
// Synopsis: If this object is aggregated within another object, then
|
||
|
// all calls will delegate to the outer object. Otherwise, the
|
||
|
// non-delegating QI is called
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// iid interface requested
|
||
|
// ppInterface Returns pointer to interface requested. NULL if interface
|
||
|
// is not supported.
|
||
|
//
|
||
|
// Returns: S_OK on success. Error code otherwise.
|
||
|
//
|
||
|
// Modifies: *ppInterface to return interface pointer
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
STDMETHODIMP CWinNTService::QueryInterface(
|
||
|
REFIID iid,
|
||
|
LPVOID *ppInterface
|
||
|
)
|
||
|
{
|
||
|
if(_pUnkOuter != NULL)
|
||
|
RRETURN(_pUnkOuter->QueryInterface(
|
||
|
iid,
|
||
|
ppInterface
|
||
|
));
|
||
|
|
||
|
RRETURN(NonDelegatingQueryInterface(
|
||
|
iid,
|
||
|
ppInterface
|
||
|
));
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Function: AddRef
|
||
|
//
|
||
|
// Synopsis: IUnknown::AddRef. If this object is aggregated within
|
||
|
// another, all calls will delegate to the outer object.
|
||
|
// Otherwise, the non-delegating AddRef is called
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// None
|
||
|
//
|
||
|
// Returns: New reference count
|
||
|
//
|
||
|
// Modifies: Nothing
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
STDMETHODIMP_(ULONG) CWinNTService::AddRef(void)
|
||
|
{
|
||
|
if(_pUnkOuter != NULL)
|
||
|
RRETURN(_pUnkOuter->AddRef());
|
||
|
|
||
|
RRETURN(NonDelegatingAddRef());
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Function: Release
|
||
|
//
|
||
|
// Synopsis: IUnknown::Release. If this object is aggregated within
|
||
|
// another, all calls will delegate to the outer object.
|
||
|
// Otherwise, the non-delegating Release is called
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// None
|
||
|
//
|
||
|
// Returns: New reference count
|
||
|
//
|
||
|
// Modifies: Nothing
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
STDMETHODIMP_(ULONG) CWinNTService::Release(void)
|
||
|
{
|
||
|
if(_pUnkOuter != NULL)
|
||
|
RRETURN(_pUnkOuter->Release());
|
||
|
|
||
|
RRETURN(NonDelegatingRelease());
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::NonDelegatingQueryInterface(REFIID riid, LPVOID FAR* ppvObj)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if(!ppvObj){
|
||
|
RRETURN(E_POINTER);
|
||
|
}
|
||
|
if (IsEqualIID(riid, IID_IUnknown))
|
||
|
{
|
||
|
*ppvObj = (IADsService *)this;
|
||
|
}
|
||
|
|
||
|
else if (IsEqualIID(riid, IID_IDispatch))
|
||
|
{
|
||
|
*ppvObj = (IADsService *)this;
|
||
|
}
|
||
|
|
||
|
else if (IsEqualIID(riid, IID_ISupportErrorInfo))
|
||
|
{
|
||
|
*ppvObj = (ISupportErrorInfo FAR *)this;
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IADsPropertyList))
|
||
|
{
|
||
|
*ppvObj = (IADsPropertyList *)this;
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IADs))
|
||
|
{
|
||
|
*ppvObj = (IADsService FAR *) this;
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IADsService))
|
||
|
{
|
||
|
*ppvObj = (IADsService FAR *) this;
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IADsServiceOperations))
|
||
|
{
|
||
|
*ppvObj = (IADsServiceOperations FAR *) this;
|
||
|
}
|
||
|
else if( (_pDispatch != NULL) &&
|
||
|
IsEqualIID(riid, IID_IADsExtension) )
|
||
|
{
|
||
|
*ppvObj = (IADsExtension *) this;
|
||
|
}
|
||
|
else if (_pExtMgr)
|
||
|
{
|
||
|
RRETURN( _pExtMgr->QueryInterface(riid, ppvObj));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*ppvObj = NULL;
|
||
|
RRETURN(E_NOINTERFACE);
|
||
|
}
|
||
|
((LPUNKNOWN)*ppvObj)->AddRef();
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
|
||
|
/* ISupportErrorInfo method */
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::InterfaceSupportsErrorInfo(
|
||
|
THIS_ REFIID riid
|
||
|
)
|
||
|
{
|
||
|
if (IsEqualIID(riid, IID_IADs) ||
|
||
|
IsEqualIID(riid, IID_IADsService) ||
|
||
|
IsEqualIID(riid, IID_IADsServiceOperations) ||
|
||
|
IsEqualIID(riid, IID_IADsPropertyList)) {
|
||
|
RRETURN(S_OK);
|
||
|
} else {
|
||
|
RRETURN(S_FALSE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: SetInfo
|
||
|
//
|
||
|
// Synopsis:
|
||
|
//
|
||
|
// Arguments: void
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: RamV Created
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::SetInfo(THIS)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
DWORD dwServiceType;
|
||
|
DWORD dwStartType;
|
||
|
DWORD dwErrorControl;
|
||
|
LPTSTR pszPath = NULL;
|
||
|
LPTSTR pszLoadOrderGroup = NULL;
|
||
|
LPTSTR pszServiceStartName = NULL;
|
||
|
LPTSTR pszDependencies = NULL;
|
||
|
LPTSTR pszDisplayName = NULL;
|
||
|
SC_LOCK sclLock = NULL;
|
||
|
BOOL fRetval = FALSE;
|
||
|
LPQUERY_SERVICE_CONFIG lpqServiceConfig = NULL;
|
||
|
|
||
|
if (GetObjectState() == ADS_OBJECT_UNBOUND) {
|
||
|
hr = WinNTAddService();
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
SetObjectState(ADS_OBJECT_BOUND);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
hr = WinNTOpenService(SC_MANAGER_ALL_ACCESS,
|
||
|
SERVICE_ALL_ACCESS);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
hr = GetServiceConfigInfo(&lpqServiceConfig);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
hr = GetLPTSTRPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("Path"),
|
||
|
&pszPath
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
lpqServiceConfig->lpBinaryPathName = pszPath;
|
||
|
}
|
||
|
|
||
|
hr = GetLPTSTRPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("LoadOrderGroup"),
|
||
|
&pszLoadOrderGroup
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
|
||
|
lpqServiceConfig->lpLoadOrderGroup = pszLoadOrderGroup;
|
||
|
}
|
||
|
|
||
|
hr = GetNulledStringPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("Dependencies"),
|
||
|
&pszDependencies
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
|
||
|
lpqServiceConfig->lpDependencies = pszDependencies;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Issue: Service Account Name property has been disabled from being a
|
||
|
// writeable property because ChangeServiceConfig AVs services.exe
|
||
|
// on the server machine when this property is changed
|
||
|
// RamV - Aug-11-96.
|
||
|
|
||
|
/*
|
||
|
|
||
|
hr = GetLPTSTRPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("ServiceAccountName"),
|
||
|
&pszServiceStartName
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
|
||
|
lpqServiceConfig->lpServiceStartName = pszServiceStartName;
|
||
|
}
|
||
|
|
||
|
*/
|
||
|
|
||
|
hr = GetLPTSTRPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("DisplayName"),
|
||
|
&pszDisplayName
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
|
||
|
lpqServiceConfig->lpDisplayName = pszDisplayName;
|
||
|
}
|
||
|
|
||
|
hr = GetDWORDPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("ServiceType"),
|
||
|
&dwServiceType
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
|
||
|
lpqServiceConfig->dwServiceType = dwServiceType;
|
||
|
}
|
||
|
|
||
|
hr = GetDWORDPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("StartType"),
|
||
|
&dwStartType
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
|
||
|
lpqServiceConfig->dwStartType = dwStartType;
|
||
|
}
|
||
|
|
||
|
hr = GetDWORDPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("ErrorControl"),
|
||
|
&dwErrorControl
|
||
|
);
|
||
|
if(SUCCEEDED(hr)){
|
||
|
|
||
|
lpqServiceConfig->dwErrorControl = dwErrorControl;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// set hr to S_OK. why? we dont care about the errors we hit so far
|
||
|
//
|
||
|
|
||
|
hr = S_OK;
|
||
|
|
||
|
//
|
||
|
// put a lock on the database corresponding to this service
|
||
|
//
|
||
|
|
||
|
sclLock = LockServiceDatabase(_schSCManager);
|
||
|
|
||
|
if(sclLock == NULL){
|
||
|
//
|
||
|
// Exit if database cannot be locked
|
||
|
//
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// change the service configuration. Pass in all the changed parameters.
|
||
|
// Since there is but one info level for services, use the
|
||
|
// internal values as parameters.
|
||
|
//
|
||
|
|
||
|
fRetval = ChangeServiceConfig(_schService,
|
||
|
lpqServiceConfig->dwServiceType,
|
||
|
lpqServiceConfig->dwStartType,
|
||
|
lpqServiceConfig->dwErrorControl,
|
||
|
lpqServiceConfig->lpBinaryPathName,
|
||
|
lpqServiceConfig->lpLoadOrderGroup,
|
||
|
NULL,
|
||
|
lpqServiceConfig->lpDependencies,
|
||
|
lpqServiceConfig->lpServiceStartName,
|
||
|
NULL,
|
||
|
lpqServiceConfig->lpDisplayName
|
||
|
);
|
||
|
|
||
|
if (fRetval == FALSE) {
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
}
|
||
|
|
||
|
if(SUCCEEDED(hr))
|
||
|
_pPropertyCache->ClearModifiedFlags();
|
||
|
|
||
|
cleanup:
|
||
|
|
||
|
if(lpqServiceConfig){
|
||
|
FreeADsMem(lpqServiceConfig);
|
||
|
}
|
||
|
|
||
|
if(sclLock){
|
||
|
UnlockServiceDatabase(sclLock);
|
||
|
}
|
||
|
|
||
|
WinNTCloseService();
|
||
|
|
||
|
if(pszPath){
|
||
|
FreeADsStr(pszPath);
|
||
|
}
|
||
|
|
||
|
if(pszLoadOrderGroup){
|
||
|
FreeADsStr(pszLoadOrderGroup);
|
||
|
}
|
||
|
if(pszServiceStartName){
|
||
|
FreeADsStr(pszServiceStartName);
|
||
|
}
|
||
|
if(pszDependencies){
|
||
|
FreeADsStr(pszDependencies);
|
||
|
}
|
||
|
if(pszDisplayName){
|
||
|
FreeADsStr(pszDisplayName);
|
||
|
}
|
||
|
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: GetInfo
|
||
|
//
|
||
|
// Synopsis: Currently implemented
|
||
|
//
|
||
|
// Arguments: void
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 12/11/95 RamV Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::GetInfo(THIS)
|
||
|
{
|
||
|
|
||
|
RRETURN (GetInfo(1, TRUE));
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::ImplicitGetInfo(THIS)
|
||
|
{
|
||
|
|
||
|
RRETURN (GetInfo(1, FALSE));
|
||
|
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::GetInfo
|
||
|
//
|
||
|
// Synopsis: Binds to real Service as specified in _ServiceName and
|
||
|
// attempts to refresh the Service object from the real Service.
|
||
|
//
|
||
|
// Arguments: dwApiLevel (ignored), fExplicit (ignored)
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 01/08/96 RamV Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::GetInfo(THIS_ DWORD dwApiLevel, BOOL fExplicit)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
LPQUERY_SERVICE_CONFIG pMem = NULL;
|
||
|
BYTE FastConfigInfo[256];
|
||
|
SERVICE_STATUS ssStatusInfo;
|
||
|
DWORD dwBufAllocated = 256;
|
||
|
DWORD dwBufNeeded;
|
||
|
DWORD dwLastError;
|
||
|
BOOL fRetval;
|
||
|
|
||
|
//
|
||
|
// GETTING NT SERVICE INFO
|
||
|
//
|
||
|
// Getting information about an NT service requires three calls.
|
||
|
// One to get configuration information, and one to get current
|
||
|
// status information, and one to get security information.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// Open the service
|
||
|
//
|
||
|
|
||
|
|
||
|
hr = WinNTOpenService(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS,
|
||
|
GENERIC_READ );
|
||
|
|
||
|
if (FAILED(hr)) {
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Query for Service Status first.
|
||
|
//
|
||
|
fRetval = QueryServiceStatus(_schService,
|
||
|
&ssStatusInfo );
|
||
|
|
||
|
if (fRetval == FALSE) {
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
|
||
|
WinNTCloseService();
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
fRetval = QueryServiceConfig(_schService,
|
||
|
(LPQUERY_SERVICE_CONFIG)(&FastConfigInfo),
|
||
|
dwBufAllocated,
|
||
|
&dwBufNeeded
|
||
|
);
|
||
|
|
||
|
if (fRetval == FALSE) {
|
||
|
dwLastError = GetLastError();
|
||
|
switch (dwLastError) {
|
||
|
case ERROR_INSUFFICIENT_BUFFER:
|
||
|
//
|
||
|
// Allocate more memory and try again.
|
||
|
//
|
||
|
dwBufAllocated = dwBufNeeded;
|
||
|
pMem = (LPQUERY_SERVICE_CONFIG)AllocADsMem(dwBufAllocated);
|
||
|
if (pMem == NULL) {
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
fRetval = QueryServiceConfig(_schService,
|
||
|
pMem,
|
||
|
dwBufAllocated,
|
||
|
&dwBufNeeded
|
||
|
);
|
||
|
|
||
|
if (fRetval == FALSE) {
|
||
|
WinNTCloseService();
|
||
|
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
if(FAILED(hr)){
|
||
|
WinNTCloseService();
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WinNTCloseService();
|
||
|
|
||
|
//
|
||
|
// clear all properties from cache first if explicit GetInfo
|
||
|
//
|
||
|
if (fExplicit) {
|
||
|
_pPropertyCache->flushpropcache();
|
||
|
}
|
||
|
|
||
|
if(pMem){
|
||
|
hr = UnMarshall(pMem, fExplicit);
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
}else{
|
||
|
hr = UnMarshall((LPQUERY_SERVICE_CONFIG) FastConfigInfo, fExplicit);
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
}
|
||
|
cleanup:
|
||
|
if(pMem)
|
||
|
FreeADsMem(pMem);
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::UnMarshall(THIS_ LPQUERY_SERVICE_CONFIG lpConfigInfo,
|
||
|
BOOL fExplicit)
|
||
|
{
|
||
|
DWORD dwADsServiceType;
|
||
|
DWORD dwADsStartType;
|
||
|
DWORD dwADsErrorControl;
|
||
|
HRESULT hr;
|
||
|
|
||
|
hr = SetLPTSTRPropertyInCache(_pPropertyCache,
|
||
|
TEXT("Path"),
|
||
|
lpConfigInfo->lpBinaryPathName,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
hr = SetLPTSTRPropertyInCache(_pPropertyCache,
|
||
|
TEXT("LoadOrderGroup"),
|
||
|
lpConfigInfo->lpLoadOrderGroup,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
|
||
|
|
||
|
hr = SetNulledStringPropertyInCache(_pPropertyCache,
|
||
|
TEXT("Dependencies"),
|
||
|
lpConfigInfo->lpDependencies,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
hr = SetLPTSTRPropertyInCache(_pPropertyCache,
|
||
|
TEXT("ServiceAccountName"),
|
||
|
lpConfigInfo->lpServiceStartName,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
hr = SetLPTSTRPropertyInCache(_pPropertyCache,
|
||
|
TEXT("DisplayName"),
|
||
|
lpConfigInfo->lpDisplayName,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
|
||
|
//
|
||
|
// 0x133 is the bit mask for valid values of ADs ServiceTypes
|
||
|
//
|
||
|
|
||
|
dwADsServiceType = lpConfigInfo->dwServiceType & 0x133;
|
||
|
|
||
|
hr = SetDWORDPropertyInCache(_pPropertyCache,
|
||
|
TEXT("ServiceType"),
|
||
|
dwADsServiceType ,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
hr = SetDWORDPropertyInCache(_pPropertyCache,
|
||
|
TEXT("StartType"),
|
||
|
lpConfigInfo->dwStartType,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
|
||
|
hr = SetDWORDPropertyInCache(_pPropertyCache,
|
||
|
TEXT("ErrorControl"),
|
||
|
lpConfigInfo->dwErrorControl,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
hr = SetLPTSTRPropertyInCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("Name"),
|
||
|
_Name,
|
||
|
fExplicit
|
||
|
);
|
||
|
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// helper function WinNTAddService
|
||
|
//
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
CWinNTService::WinNTAddService(void)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
SC_HANDLE schService = NULL;
|
||
|
SC_HANDLE schSCManager = NULL;
|
||
|
TCHAR szServerName[MAX_PATH];
|
||
|
BOOL fRetval;
|
||
|
LPTSTR pszDisplayName = NULL;
|
||
|
LPTSTR pszPath = NULL;
|
||
|
LPTSTR pszLoadOrderGroup = NULL;
|
||
|
DWORD dwServiceType;
|
||
|
DWORD dwStartType;
|
||
|
DWORD dwErrorControl;
|
||
|
|
||
|
hr = GetServerFromPath(_ADsPath,szServerName);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
//
|
||
|
// open the SCM for this server
|
||
|
//
|
||
|
|
||
|
schSCManager = OpenSCManager(szServerName,
|
||
|
NULL,
|
||
|
SC_MANAGER_ALL_ACCESS);
|
||
|
|
||
|
if(schSCManager == NULL){
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
hr = GetLPTSTRPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("DisplayName"),
|
||
|
&pszDisplayName
|
||
|
);
|
||
|
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
hr = GetDWORDPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("ServiceType"),
|
||
|
&dwServiceType
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
hr = GetDWORDPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("StartType"),
|
||
|
&dwStartType
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
hr = GetDWORDPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("ErrorControl"),
|
||
|
&dwErrorControl
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
hr = GetLPTSTRPropertyFromCache(
|
||
|
_pPropertyCache,
|
||
|
TEXT("Path"),
|
||
|
&pszPath
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
schService = CreateService(schSCManager,
|
||
|
_pszServiceName,
|
||
|
pszDisplayName,
|
||
|
SERVICE_ALL_ACCESS,
|
||
|
dwServiceType,
|
||
|
dwStartType,
|
||
|
dwErrorControl,
|
||
|
pszPath,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL );
|
||
|
|
||
|
if(schService == NULL){
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
if(schSCManager){
|
||
|
fRetval = CloseServiceHandle(schSCManager);
|
||
|
if(!fRetval && SUCCEEDED(hr)){
|
||
|
RRETURN(HRESULT_FROM_WIN32(GetLastError()));
|
||
|
}
|
||
|
}
|
||
|
if(schService){
|
||
|
fRetval = CloseServiceHandle(schService);
|
||
|
if(!fRetval && SUCCEEDED(hr)){
|
||
|
RRETURN(HRESULT_FROM_WIN32(GetLastError()));
|
||
|
}
|
||
|
}
|
||
|
if(pszDisplayName){
|
||
|
FreeADsStr(pszDisplayName);
|
||
|
}
|
||
|
|
||
|
if(pszPath){
|
||
|
FreeADsStr(pszPath);
|
||
|
}
|
||
|
|
||
|
if(pszLoadOrderGroup){
|
||
|
FreeADsStr(pszLoadOrderGroup);
|
||
|
}
|
||
|
|
||
|
RRETURN(hr);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_HostComputer(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
if(!retval){
|
||
|
RRETURN_EXP_IF_ERR(E_ADS_BAD_PARAMETER);
|
||
|
}
|
||
|
hr = ADsAllocString(_Parent, retval);
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_HostComputer(THIS_ BSTR bstrHostComputer)
|
||
|
{
|
||
|
RRETURN_EXP_IF_ERR(E_ADS_PROPERTY_NOT_SUPPORTED);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_DisplayName(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_BSTR((IADsService *)this, DisplayName);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_DisplayName(THIS_ BSTR bstrDisplayName)
|
||
|
{
|
||
|
PUT_PROPERTY_BSTR((IADsService *)this, DisplayName);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_Version(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_BSTR((IADsService *)this, Version);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_Version(THIS_ BSTR bstrVersion)
|
||
|
{
|
||
|
PUT_PROPERTY_BSTR((IADsService *)this, Version);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_ServiceType(THIS_ long FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_LONG((IADsService *)this, ServiceType);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_ServiceType(THIS_ long lServiceType)
|
||
|
{
|
||
|
PUT_PROPERTY_LONG((IADsService *)this, ServiceType);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_StartType(THIS_ LONG FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_LONG((IADsService *)this, StartType);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_StartType(THIS_ LONG lStartType)
|
||
|
{
|
||
|
PUT_PROPERTY_LONG((IADsService *)this, StartType);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_Path(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_BSTR((IADsService *)this, Path);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_Path(THIS_ BSTR bstrPath)
|
||
|
{
|
||
|
|
||
|
PUT_PROPERTY_BSTR((IADsService *)this, Path);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_StartupParameters(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_BSTR((IADsService *)this, StartupParameters);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_StartupParameters(THIS_ BSTR bstrStartupParameters) {
|
||
|
PUT_PROPERTY_BSTR((IADsService *)this, StartupParameters);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_ErrorControl(THIS_ LONG FAR* retval)
|
||
|
{
|
||
|
|
||
|
GET_PROPERTY_LONG((IADsService *)this, ErrorControl);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_ErrorControl(THIS_ LONG lErrorControl)
|
||
|
{
|
||
|
PUT_PROPERTY_LONG((IADsService *)this, ErrorControl);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_LoadOrderGroup(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_BSTR((IADsService *)this, LoadOrderGroup);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_LoadOrderGroup(THIS_ BSTR bstrLoadOrderGroup)
|
||
|
{
|
||
|
|
||
|
PUT_PROPERTY_BSTR((IADsService *)this, LoadOrderGroup);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_ServiceAccountName(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_BSTR((IADsService *)this, ServiceAccountName);
|
||
|
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_ServiceAccountName(THIS_ BSTR bstrServiceAccountName)
|
||
|
{
|
||
|
RRETURN_EXP_IF_ERR(E_ADS_PROPERTY_NOT_SUPPORTED);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_ServiceAccountPath(THIS_ BSTR FAR* retval)
|
||
|
{
|
||
|
RRETURN_EXP_IF_ERR(E_ADS_PROPERTY_NOT_SUPPORTED);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_ServiceAccountPath(THIS_ BSTR bstrServiceAccountName)
|
||
|
{
|
||
|
RRETURN_EXP_IF_ERR(E_ADS_PROPERTY_NOT_SUPPORTED);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_Dependencies(THIS_ VARIANT FAR* retval)
|
||
|
{
|
||
|
GET_PROPERTY_VARIANT((IADsService *)this, Dependencies);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::put_Dependencies(THIS_ VARIANT vDependencies)
|
||
|
{
|
||
|
PUT_PROPERTY_VARIANT((IADsService *)this, Dependencies);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::SetPassword(THIS_ BSTR bstrNewPassword)
|
||
|
{
|
||
|
|
||
|
|
||
|
//
|
||
|
// This routine should merely change password. Even if any other
|
||
|
// properties are set in the configuration functional set then they
|
||
|
// will not be touched.
|
||
|
// Therefore we do a QueryServiceConfig and get all the configuration
|
||
|
// related information, merely change the password and send it back.
|
||
|
// For this reason, it is not possible to reuse GetInfo or SetInfo
|
||
|
// because they change service config properties.
|
||
|
//
|
||
|
|
||
|
BOOL fRetval;
|
||
|
LPQUERY_SERVICE_CONFIG pMem = NULL;
|
||
|
HRESULT hr;
|
||
|
|
||
|
hr = WinNTOpenService(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS,
|
||
|
SERVICE_ALL_ACCESS);
|
||
|
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
hr = GetServiceConfigInfo(&pMem);
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
//
|
||
|
// just change the field corresponding to password.
|
||
|
//
|
||
|
|
||
|
fRetval = ChangeServiceConfig(_schService,
|
||
|
pMem->dwServiceType,
|
||
|
pMem->dwStartType,
|
||
|
pMem->dwErrorControl,
|
||
|
pMem->lpBinaryPathName,
|
||
|
pMem->lpLoadOrderGroup,
|
||
|
NULL,
|
||
|
pMem->lpDependencies,
|
||
|
pMem->lpServiceStartName,
|
||
|
(LPTSTR)bstrNewPassword,
|
||
|
pMem->lpDisplayName
|
||
|
);
|
||
|
|
||
|
if(!fRetval){
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
if(pMem){
|
||
|
FreeADsMem(pMem);
|
||
|
}
|
||
|
WinNTCloseService();
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::Start
|
||
|
//
|
||
|
// Synopsis: Attempts to start the service specified in _bstrServiceName on
|
||
|
// the server named in _bstrPath.
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 01/04/96 RamV Created
|
||
|
//
|
||
|
// Notes:
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::Start(THIS)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
hr = WinNTControlService(WINNT_START_SERVICE);
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::Stop
|
||
|
//
|
||
|
// Synopsis: Attempts to stop the service specified in _bstrServiceName on
|
||
|
// the server named in _bstrPath.
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 01/04/96 RamV Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::Stop(THIS)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
hr = WinNTControlService(WINNT_STOP_SERVICE);
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::Pause
|
||
|
//
|
||
|
// Synopsis: Attempts to pause the service named _bstrServiceName on the
|
||
|
// server named in _bstrPath.
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 01-04-96 RamV Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::Pause(THIS)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
hr = WinNTControlService(WINNT_PAUSE_SERVICE);
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::Continue
|
||
|
//
|
||
|
// Synopsis: Attempts to "unpause" the service specified in _bstrServiceName
|
||
|
// on the server named in _bstrPath.
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 01/04/96 RamV Created
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::Continue(THIS)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
hr = WinNTControlService(WINNT_CONTINUE_SERVICE);
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Helper Functions
|
||
|
//
|
||
|
|
||
|
HRESULT
|
||
|
CWinNTService::GetServiceConfigInfo(LPQUERY_SERVICE_CONFIG *ppMem)
|
||
|
{
|
||
|
//
|
||
|
//gets the service configuration information into ppMem
|
||
|
//
|
||
|
|
||
|
BOOL fRetval;
|
||
|
DWORD dwBufAllocated = 0;
|
||
|
DWORD dwBufNeeded = 0;
|
||
|
DWORD dwLastError;
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
ADsAssert(ppMem);
|
||
|
*ppMem = (LPQUERY_SERVICE_CONFIG)AllocADsMem(dwBufAllocated);
|
||
|
|
||
|
if (*ppMem == NULL){
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
ADsAssert(_schService);
|
||
|
|
||
|
fRetval = QueryServiceConfig(_schService,
|
||
|
(LPQUERY_SERVICE_CONFIG)(*ppMem),
|
||
|
dwBufAllocated,
|
||
|
&dwBufNeeded);
|
||
|
|
||
|
if (fRetval == FALSE) {
|
||
|
dwLastError = GetLastError();
|
||
|
switch (dwLastError) {
|
||
|
case ERROR_INSUFFICIENT_BUFFER:
|
||
|
//
|
||
|
// Allocate more memory and try again.
|
||
|
//
|
||
|
FreeADsMem(*ppMem);
|
||
|
*ppMem = NULL;
|
||
|
|
||
|
dwBufAllocated = dwBufNeeded;
|
||
|
*ppMem = (LPQUERY_SERVICE_CONFIG)AllocADsMem(dwBufAllocated);
|
||
|
if (*ppMem == NULL) {
|
||
|
BAIL_IF_ERROR(hr = E_OUTOFMEMORY);
|
||
|
}
|
||
|
|
||
|
fRetval = QueryServiceConfig(_schService,
|
||
|
*ppMem,
|
||
|
dwBufAllocated,
|
||
|
&dwBufNeeded);
|
||
|
|
||
|
if (fRetval == FALSE) {
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
}
|
||
|
|
||
|
if(*ppMem){
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
RRETURN(hr);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
CWinNTService::WinNTControlService( DWORD dwControl)
|
||
|
{
|
||
|
//
|
||
|
// abstracts out the common code of Start,Stop,Pause and Resume
|
||
|
//
|
||
|
|
||
|
HRESULT hr =S_OK, hrclose=S_OK, hrcontrol=S_OK;
|
||
|
SERVICE_STATUS ssStatusInfo;
|
||
|
BOOL fRetval;
|
||
|
|
||
|
|
||
|
if(_fValidHandle){
|
||
|
//
|
||
|
// an open handle exists, blow it away
|
||
|
//
|
||
|
hrclose = WinNTCloseService();
|
||
|
BAIL_ON_FAILURE(hrclose);
|
||
|
_fValidHandle = FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
hr = WinNTOpenService(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS,
|
||
|
GENERIC_EXECUTE| SERVICE_INTERROGATE
|
||
|
);
|
||
|
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
|
||
|
_fValidHandle = TRUE;
|
||
|
|
||
|
switch(dwControl){
|
||
|
|
||
|
case WINNT_START_SERVICE:
|
||
|
fRetval = StartService(_schService,
|
||
|
0,
|
||
|
NULL );
|
||
|
|
||
|
|
||
|
if(!fRetval){
|
||
|
hrcontrol = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto error;
|
||
|
}
|
||
|
_dwOpPending = PENDING_START;
|
||
|
break;
|
||
|
|
||
|
case WINNT_STOP_SERVICE:
|
||
|
fRetval = ControlService(_schService,
|
||
|
SERVICE_CONTROL_STOP,
|
||
|
&ssStatusInfo);
|
||
|
|
||
|
if(!fRetval){
|
||
|
hrcontrol = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto error;
|
||
|
}
|
||
|
_dwOpPending = PENDING_STOP;
|
||
|
break;
|
||
|
|
||
|
case WINNT_PAUSE_SERVICE:
|
||
|
fRetval = ControlService(_schService,
|
||
|
SERVICE_CONTROL_PAUSE,
|
||
|
&ssStatusInfo);
|
||
|
|
||
|
if(!fRetval){
|
||
|
hrcontrol = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto error;
|
||
|
}
|
||
|
_dwOpPending = PENDING_PAUSE;
|
||
|
break;
|
||
|
|
||
|
case WINNT_CONTINUE_SERVICE:
|
||
|
fRetval = ControlService(_schService,
|
||
|
SERVICE_CONTROL_CONTINUE,
|
||
|
&ssStatusInfo);
|
||
|
|
||
|
if(!fRetval){
|
||
|
hrcontrol = HRESULT_FROM_WIN32(GetLastError());
|
||
|
goto error;
|
||
|
}
|
||
|
_dwOpPending = PENDING_CONTINUE;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
hrcontrol = E_FAIL;
|
||
|
goto error;
|
||
|
}
|
||
|
_dwTimeStarted = GetTickCount();
|
||
|
_dwWaitHint = 10000; //10 seconds
|
||
|
_dwCheckPoint = 0;
|
||
|
|
||
|
RRETURN(S_OK);
|
||
|
|
||
|
error:
|
||
|
if(FAILED(hrcontrol)){
|
||
|
_fValidHandle = FALSE;
|
||
|
RRETURN(hrcontrol);
|
||
|
}
|
||
|
else if(FAILED(hrclose)){
|
||
|
RRETURN(hrclose);
|
||
|
}
|
||
|
else{
|
||
|
RRETURN(hr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::WinNTOpenService
|
||
|
//
|
||
|
// Synopsis: Opens the Service Control Manager on the machine specified in
|
||
|
// _bstrPath, then opens the Service specified in _bstrServiceName.
|
||
|
// The handle to the SCM is placed in _schSCManager, and the
|
||
|
// handle to the service is placed in _schService.
|
||
|
//
|
||
|
// Arguments: [dwSCMDesiredAccess] -- type of SCM access needed
|
||
|
// [dwSvrDesiredAccess] -- type of Service access required
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 03-17-95 t-skwan Created
|
||
|
// 01/04/96 RamV Modified
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
HRESULT
|
||
|
CWinNTService::WinNTOpenService(
|
||
|
DWORD dwSCMDesiredAccess,
|
||
|
DWORD dwSvrDesiredAccess
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
DWORD dwLastError;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Open the Service Control Manager.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// OpenSCManager(
|
||
|
// LPCTSTR lpszMachineName,
|
||
|
// LPCTSTR lpszDatabaseName.
|
||
|
// DWORD fdwDesiredAccess)
|
||
|
//
|
||
|
|
||
|
|
||
|
_schSCManager = OpenSCManager(_pszServerName,
|
||
|
NULL,
|
||
|
dwSCMDesiredAccess);
|
||
|
|
||
|
if (_schSCManager == NULL) {
|
||
|
|
||
|
dwLastError = GetLastError();
|
||
|
hr = HRESULT_FROM_WIN32(dwLastError);
|
||
|
RRETURN(hr);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get a handle to the specified service.
|
||
|
//
|
||
|
|
||
|
|
||
|
_schService = OpenService(_schSCManager,
|
||
|
_pszServiceName,
|
||
|
dwSvrDesiredAccess);
|
||
|
|
||
|
if(_schService == NULL) {
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
CloseServiceHandle(_schSCManager);
|
||
|
_schSCManager = NULL;
|
||
|
RRETURN(hr);
|
||
|
}
|
||
|
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: CWinNTService::WinNTCloseService
|
||
|
//
|
||
|
// Synopsis: Closes the Service handle and the Service Control Manager
|
||
|
// handle.
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Returns: HRESULT.
|
||
|
//
|
||
|
// Modifies:
|
||
|
//
|
||
|
// History: 03-17-95 t-skwan Created
|
||
|
// 01/04/96 RamV Modified
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
HRESULT
|
||
|
CWinNTService::WinNTCloseService()
|
||
|
{
|
||
|
BOOL fRetval = TRUE;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Close the Service handle.
|
||
|
//
|
||
|
if(_schService){
|
||
|
fRetval = CloseServiceHandle(_schService);
|
||
|
_schService = NULL;
|
||
|
}
|
||
|
|
||
|
if (!fRetval) {
|
||
|
//
|
||
|
// Ack. What do we do if there is an error closing a service?
|
||
|
//
|
||
|
RRETURN(HRESULT_FROM_WIN32(GetLastError()));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Close the Service Control Manager.
|
||
|
//
|
||
|
|
||
|
if(_schSCManager){
|
||
|
fRetval = CloseServiceHandle(_schSCManager);
|
||
|
_schSCManager = NULL;
|
||
|
}
|
||
|
if (!fRetval) {
|
||
|
//
|
||
|
// Ack. What do we do if there is an error closing an SCM?
|
||
|
//
|
||
|
RRETURN(HRESULT_FROM_WIN32(GetLastError()));
|
||
|
}
|
||
|
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP
|
||
|
CWinNTService::get_Status(THIS_ long FAR* plStatusCode)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
BOOL fRetval = FALSE, found = FALSE;
|
||
|
SERVICE_STATUS Status;
|
||
|
DWORD dwStatus = 0;
|
||
|
|
||
|
|
||
|
if(plStatusCode == NULL){
|
||
|
RRETURN_EXP_IF_ERR(E_POINTER);
|
||
|
}
|
||
|
|
||
|
*plStatusCode = -1; //-1 is an invalid code
|
||
|
|
||
|
if(!(_fValidHandle)){
|
||
|
|
||
|
//
|
||
|
// currently not waiting on any service
|
||
|
//
|
||
|
|
||
|
hr = WinNTOpenService(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS,
|
||
|
GENERIC_EXECUTE|SERVICE_INTERROGATE);
|
||
|
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
fRetval = ControlService(_schService,
|
||
|
SERVICE_CONTROL_INTERROGATE,
|
||
|
&Status);
|
||
|
|
||
|
|
||
|
if(!fRetval){
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
if(hr == HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_ACTIVE)){
|
||
|
dwStatus = SERVICE_STOPPED;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
dwStatus = Status.dwCurrentState;
|
||
|
|
||
|
hr = WinNTCloseService();
|
||
|
|
||
|
goto cleanup;
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// if you are here
|
||
|
// you are waiting for a service to complete
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// NOTE: QueryServiceStatus queries the SCM rather than
|
||
|
// the service directly so to get a more upto date answer
|
||
|
// we need to use control service with interrogate option
|
||
|
//
|
||
|
|
||
|
|
||
|
hr = WinNTOpenService(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS,
|
||
|
GENERIC_EXECUTE|SERVICE_INTERROGATE);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
fRetval = ControlService(_schService,
|
||
|
SERVICE_CONTROL_INTERROGATE,
|
||
|
&Status);
|
||
|
|
||
|
|
||
|
|
||
|
if(!fRetval){
|
||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
|
if(hr == HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_ACTIVE)){
|
||
|
dwStatus = SERVICE_STOPPED;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
hr = EvalPendingOperation(PENDING_START,
|
||
|
SERVICE_RUNNING,
|
||
|
SERVICE_START_PENDING,
|
||
|
&Status,
|
||
|
&dwStatus
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
if(dwStatus != 0){
|
||
|
//
|
||
|
// the correct scenario was found
|
||
|
//
|
||
|
goto cleanup;
|
||
|
}
|
||
|
hr = EvalPendingOperation(PENDING_STOP,
|
||
|
SERVICE_STOPPED,
|
||
|
SERVICE_STOP_PENDING,
|
||
|
&Status,
|
||
|
&dwStatus
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
if(dwStatus != 0){
|
||
|
//
|
||
|
// the correct scenario was found
|
||
|
//
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
hr = EvalPendingOperation(PENDING_PAUSE,
|
||
|
SERVICE_PAUSED,
|
||
|
SERVICE_PAUSE_PENDING,
|
||
|
&Status,
|
||
|
&dwStatus
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
if(dwStatus != 0){
|
||
|
//
|
||
|
// the correct scenario was found
|
||
|
//
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
hr = EvalPendingOperation(PENDING_CONTINUE,
|
||
|
SERVICE_RUNNING,
|
||
|
SERVICE_CONTINUE_PENDING,
|
||
|
&Status,
|
||
|
&dwStatus
|
||
|
);
|
||
|
|
||
|
BAIL_IF_ERROR(hr);
|
||
|
|
||
|
ADsAssert(dwStatus != 0); //we must find the appropriate scenario
|
||
|
|
||
|
cleanup:
|
||
|
if(SUCCEEDED(hr)){
|
||
|
//
|
||
|
// instead of a conversion routine, we return WinNT Status Code
|
||
|
//
|
||
|
|
||
|
*plStatusCode = dwStatus;
|
||
|
|
||
|
}
|
||
|
RRETURN_EXP_IF_ERR(hr);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
CWinNTService::EvalPendingOperation(
|
||
|
THIS_ DWORD dwOpPending,
|
||
|
DWORD dwStatusDone,
|
||
|
DWORD dwStatusPending,
|
||
|
LPSERVICE_STATUS pStatus,
|
||
|
DWORD *pdwRetval
|
||
|
)
|
||
|
|
||
|
{
|
||
|
|
||
|
DWORD dwCurrentStatus;
|
||
|
BOOL fRetval;
|
||
|
HRESULT hr =S_OK;
|
||
|
DWORD dwNow;
|
||
|
|
||
|
dwCurrentStatus = pStatus->dwCurrentState;
|
||
|
|
||
|
if(_dwOpPending == dwOpPending){
|
||
|
|
||
|
if(dwCurrentStatus == dwStatusDone){
|
||
|
//
|
||
|
//was pending, is now completed
|
||
|
//
|
||
|
|
||
|
_dwOpPending = NOTPENDING;
|
||
|
*pdwRetval = dwStatusDone;
|
||
|
hr = WinNTCloseService();
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
_fValidHandle = FALSE;
|
||
|
RRETURN(S_OK);
|
||
|
|
||
|
}
|
||
|
else if(dwCurrentStatus = dwStatusPending){
|
||
|
//
|
||
|
//see if progress has been made since the last time we checked
|
||
|
//
|
||
|
|
||
|
if(pStatus->dwCheckPoint !=_dwCheckPoint){
|
||
|
//
|
||
|
// progress was made
|
||
|
//
|
||
|
*pdwRetval = dwStatusPending;
|
||
|
_dwCheckPoint = pStatus->dwCheckPoint;
|
||
|
_dwWaitHint = pStatus->dwWaitHint;
|
||
|
_dwTimeStarted = GetTickCount();
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
|
||
|
dwNow = GetTickCount();
|
||
|
|
||
|
|
||
|
if(2*_dwWaitHint < TickCountDiff(dwNow,_dwTimeStarted)){
|
||
|
//
|
||
|
// you can still wait
|
||
|
//
|
||
|
*pdwRetval = dwStatusPending;
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
|
||
|
else{
|
||
|
|
||
|
//
|
||
|
// took too long without signs of progress
|
||
|
//
|
||
|
|
||
|
*pdwRetval = SERVICE_ERROR;
|
||
|
_dwOpPending = NOTPENDING;
|
||
|
hr = WinNTCloseService();
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
_fValidHandle = FALSE;
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
}
|
||
|
else{
|
||
|
|
||
|
//
|
||
|
// an operation is pending but we arent going anywhere
|
||
|
// recover gracefully
|
||
|
//
|
||
|
|
||
|
_dwOpPending = NOTPENDING;
|
||
|
hr = WinNTCloseService();
|
||
|
BAIL_ON_FAILURE(hr);
|
||
|
_fValidHandle = FALSE;
|
||
|
*pdwRetval = SERVICE_ERROR;
|
||
|
RRETURN(S_OK);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
error:
|
||
|
RRETURN(hr);
|
||
|
}
|