Trying to implement a task scheduler COM handler

0

The prototype of ITaskHandler::Start is the following:

HRESULT ( STDMETHODCALLTYPE Start )( 
            __RPC__in ITaskHandler * This,
            /* [in] */ __RPC__in_opt IUnknown *pHandlerServices,
            /* [in] */ __RPC__in BSTR data)

How come pHandlerServices is optional - and if I get a NULL (as it's my case) - how can I notify the task scheduler that I've completed the task.

OK - here is the deal I implemented QueryInterface of the class to always return the same object thinking - ITaskHandler will immediately be queried. However this wasn't the case - the first query was for IClassFactory and the function signature of CreateInstance had the second parameter pUnkOuter NULL which overlaps with the second parameter of my implementation of ITaskHandler_Start. Nevertheless it's weird that pHandlerServices is marked as optional.

Here is my current implementation of the handler which is still not working (the last run result is No such interface supported (0x80004002)) - my interface ITaskHandler is never queried for. I even went so far implementing ICallFactory but alias with no luck (CreateCall is never called) - here is the code:

#define COBJMACROS
#include <windows.h>
#include <objbase.h>
#include <unknwn.h>

    // {179D1704-49C5-4111-B3CF-C528ABB014D0}
DEFINE_GUID(CLSID_IRmouseHandler, 
0x179d1704, 0x49c5, 0x4111, 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0);

#define wstringCLSID_IRmouseHandler L"{179D1704-49C5-4111-B3CF-C528ABB014D0}"
static const GUID CLSID_IRmouseHandler = 
{ 0x179d1704, 0x49c5, 0x4111, { 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0 } };

// {D363EF80-5C42-46D8-847B-B3A27A3BD0E3}
DEFINE_GUID(IID_IRmouseHandler, 
0xd363ef80, 0x5c42, 0x46d8, 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3);

static const GUID IID_IRmouseHandler = 
{ 0xd363ef80, 0x5c42, 0x46d8, { 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3 } };

#include <taskschd.h>

#include <ObjIdl.h>

#define stub(x)\
\
STDMETHODCALLTYPE x() {\
    MessageBox(\
        NULL,\
        "ITaskHandler_" #x,\
        "Account Details",\
        MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2\
    );}

extern ITaskHandler tskhandler; extern IClassFactory factory; extern ICallFactory callfactory;

stub(CreateCall)

HRESULT ( STDMETHODCALLTYPE CreateInstance )( 
            IClassFactory * This,
            /* [annotation][unique][in] */ 
            _In_opt_  IUnknown *pUnkOuter,
            /* [annotation][in] */ 
            _In_  REFIID riid,
            /* [annotation][iid_is][out] */ 
            _COM_Outptr_  void **ppvObject) { return QueryInterface(This, riid, ppvObject);}

HRESULT STDMETHODCALLTYPE QueryInterface(
            __RPC__in ITaskHandler * This,
            /* [in] */ __RPC__in REFIID riid,
            /* [annotation][iid_is][out] */ 
            _COM_Outptr_  void **ppvObject) {
    if(!ppvObject) return E_POINTER;
    if(!memcmp(riid, &IID_ITaskHandler, sizeof *riid) || !memcmp(riid, &IID_IUnknown, sizeof *riid))*ppvObject = &tskhandler;
    else if(!memcmp(riid, &IID_ICallFactory, sizeof *riid))*ppvObject = &callfactory;
    else if(!memcmp(riid, &IID_IClassFactory, sizeof *riid))*ppvObject = &factory;
    else return E_NOINTERFACE;
    return S_OK;}



ULONG STDMETHODCALLTYPE AddRef(){}
stub(Release)
HRESULT ( STDMETHODCALLTYPE Start )( 
            __RPC__in ITaskHandler * This,
            /* [in] */ __RPC__in_opt IUnknown *pHandlerServices,
            /* [in] */ __RPC__in BSTR data) {ITaskHandlerStatus *pHandlerStatus;
            IUnknown_QueryInterface(pHandlerServices,&IID_ITaskHandlerStatus,&pHandlerStatus),
            ITaskHandlerStatus_TaskCompleted(pHandlerStatus,S_OK);return S_OK;}
stub(Stop)
stub(Pause)
stub(Resume)

ITaskHandler tskhandler = {.lpVtbl = &(struct ITaskHandlerVtbl){.QueryInterface=QueryInterface,.Resume=Resume,
.AddRef=AddRef,.Release=Release,.Start=Start,.Stop=Stop,.Pause=Pause}};

IClassFactory factory = {.lpVtbl = &(struct IClassFactoryVtbl){.QueryInterface=QueryInterface,
.AddRef=AddRef,.Release=Release,.CreateInstance=CreateInstance}};

ICallFactory callfactory = {.lpVtbl = &(struct ICallFactoryVtbl){.QueryInterface=QueryInterface,
.AddRef=AddRef,.Release=Release,.CreateCall=CreateCall}};

int WinMain(
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR     lpCmdLine,
  int       nShowCmd
) { DWORD dwToken; //AddVectoredExceptionHandler(1,PvectoredExceptionHandler);
    CoInitializeEx(NULL,0), CoRegisterClassObject(&CLSID_IRmouseHandler,&tskhandler,CLSCTX_LOCAL_SERVER,REGCLS_MULTIPLEUSE,&dwToken),Sleep(INFINITE);}
c
winapi
com
taskscheduler
asked on Stack Overflow Jun 8, 2019 by AnArrayOfFunctions • edited Jun 12, 2019 by AnArrayOfFunctions

2 Answers

1

The key to this as @HansPassant notes is that Task Scheduler will only run COM objects out of process. In order to do this you need register your COM object to use the system supplied DllSurrogate.

Here are the COM registration keys for my example

HKEY_CLASSES_ROOT\AppID\{6B9279D0-D220-4288-AFDF-E424F558FEF2}
   DllSurrogate   REG_SZ ""
Computer\HKEY_CLASSES_ROOT\CLSID\{36A976F4-698B-4B50-BE2C-83F815575199}
   Default        REG_SZ Path\To\your_com.dll
   AppID          REG_SZ {6B9279D0-D220-4288-AFDF-E424F558FEF2}
   ThreadingModel REG_SZ Both

Working example code (sorry C++) - it basically just a default COM implementation with the addition of ITaskHandler. It at least calls Start. If you want to use you own code, I suggest that you test the COM object loads in a simple test program before worrying about the Task Scheduler.

// {36A976F4-698B-4B50-BE2C-83F815575199}
DEFINE_GUID(CLSID_TestObject,
    0x36a976f4, 0x698b, 0x4b50, 0xbe, 0x2c, 0x83, 0xf8, 0x15, 0x57, 0x51, 0x99);

int main()
{
    CoInitialize(nullptr);
    ITaskHandler* handler = nullptr;
    HRESULT hr = CoCreateInstance(CLSID_TestObject, nullptr, CLSCTX_LOCAL_SERVER, IID_ITaskHandler, (LPVOID*)&handler);
    fprintf(stderr, "CoCreateInstance %08x\r\n", hr);
    return 0;
}

DllMain.cpp

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    return TRUE;
}

// {36A976F4-698B-4B50-BE2C-83F815575199}
DEFINE_GUID(CLSID_TestObj,
    0x36a976f4, 0x698b, 0x4b50, 0xbe, 0x2c, 0x83, 0xf8, 0x15, 0x57, 0x51, 0x99);

long g_nComObjsInUse;

STDAPI DllGetClassObject(const CLSID& clsid,
    const IID& iid,
    void** ppv)
{
    OutputDebugStringW(L"DllGetClassObject");
    if (IsEqualGUID(clsid, CLSID_TestObj))
    {
        TestClassFactory *pAddFact = new TestClassFactory();
        if (pAddFact == NULL)
            return E_OUTOFMEMORY;
        else
        {
            return pAddFact->QueryInterface(iid, ppv);
        }
    }
    return CLASS_E_CLASSNOTAVAILABLE;
}

STDAPI DllCanUnloadNow()
{
    OutputDebugStringW(L"DllCanUnloadNow");
    if (g_nComObjsInUse == 0)
    {
        return S_OK;
    }
    else
    {
        return S_FALSE;
    }
}

TestObj.h

extern long g_nComObjsInUse;

class CTestObj :
    public ITaskHandler
{
public:
    CTestObj();
    virtual ~CTestObj();

    //IUnknown interface 
    HRESULT __stdcall QueryInterface( REFIID riid,void **ppObj) override;
    ULONG   __stdcall AddRef() override;
    ULONG   __stdcall Release() override;

    //IAdd interface
    HRESULT __stdcall Start(IUnknown* handler, BSTR data) override;
    HRESULT __stdcall Stop(HRESULT* retCode) override;
    HRESULT __stdcall Pause() override;
    HRESULT __stdcall Resume() override;
private:
    long m_nRefCount;   //for managing the reference count
};

TestObj.cpp

HRESULT __stdcall CTestObj::Start(IUnknown* handler, BSTR data)
{
    OutputDebugStringW(L"Start");
    return S_OK;
}

HRESULT __stdcall CTestObj::Stop(HRESULT* retCode)
{
    OutputDebugStringW(L"Stop");
    return S_OK;
}

HRESULT __stdcall CTestObj::Pause()
{
    OutputDebugStringW(L"Pause");
    return S_OK;
}

HRESULT __stdcall CTestObj::Resume()
{
    OutputDebugStringW(L"Resume");
    return S_OK;
}

CTestObj::CTestObj()
{
    InterlockedIncrement(&g_nComObjsInUse);
}

CTestObj::~CTestObj()
{
    InterlockedDecrement(&g_nComObjsInUse);
}

HRESULT __stdcall CTestObj::QueryInterface(REFIID riid, void **ppObj)
{
    OutputDebugStringW(L"QueryInterface");
    if (IsEqualGUID(riid,IID_IUnknown))
    {
        *ppObj = static_cast<IUnknown*>(this);
        AddRef();
        return S_OK;
    }
    if (IsEqualGUID(riid,IID_ITaskHandler))
    {
        *ppObj = static_cast<ITaskHandler*>(this);
        AddRef();
        return S_OK;
    }
    *ppObj = NULL;
    return E_NOINTERFACE;
}

ULONG   __stdcall CTestObj::AddRef()
{
    OutputDebugStringW(L"AddRef");
    return InterlockedIncrement(&m_nRefCount);
}

ULONG   __stdcall CTestObj::Release()
{
    OutputDebugStringW(L"Release");
    long nRefCount = 0;
    nRefCount = InterlockedDecrement(&m_nRefCount);
    if (nRefCount == 0) delete this;
    return nRefCount;
}

TestClassFactory.h

class TestClassFactory : IClassFactory
{
public:
    TestClassFactory();
    ~TestClassFactory();
    HRESULT __stdcall QueryInterface(
        REFIID riid,
        void **ppObj) override;
    ULONG   __stdcall AddRef() override;
    ULONG   __stdcall Release() override;

    HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter,
        const IID& iid,
        void** ppv) override;
    HRESULT __stdcall LockServer(BOOL bLock) override;

private:
    long m_nRefCount;
};

TestClassFactory.cpp

extern long g_nComObjsInUse;

TestClassFactory::TestClassFactory()
{
    InterlockedIncrement(&g_nComObjsInUse);
}


TestClassFactory::~TestClassFactory()
{
    InterlockedDecrement(&g_nComObjsInUse);
}

HRESULT __stdcall TestClassFactory::CreateInstance(IUnknown* pUnknownOuter,
    const IID& iid,
    void** ppv)
{
    OutputDebugStringW(L"CreateInstance");
    if (pUnknownOuter != NULL)
    {
        return CLASS_E_NOAGGREGATION;
    }

    CTestObj* pObject = new CTestObj();
    if (pObject == NULL)
    {
        return E_OUTOFMEMORY;
    }

    return pObject->QueryInterface(iid, ppv);
}


HRESULT __stdcall TestClassFactory::LockServer(BOOL bLock)
{
    OutputDebugStringW(L"LockServer");
    return E_NOTIMPL;
}

HRESULT __stdcall TestClassFactory::QueryInterface(
    REFIID riid,
    void **ppObj)
{
    OutputDebugStringW(L"QueryInterface");
    if (IsEqualGUID(riid, IID_IUnknown))
    {
        *ppObj = static_cast<IUnknown*>(this);
        AddRef();
        return S_OK;
    }
    if (IsEqualGUID(riid, IID_IClassFactory))
    {
        *ppObj = static_cast<IClassFactory*>(this);
        AddRef();
        return S_OK;
    }
    *ppObj = NULL;
    return E_NOINTERFACE;
}

ULONG   __stdcall TestClassFactory::AddRef()
{
    OutputDebugStringW(L"AddRef");
    return InterlockedIncrement(&m_nRefCount);
}

ULONG   __stdcall TestClassFactory::Release()
{
    OutputDebugStringW(L"Release");
    long nRefCount = 0;
    nRefCount = InterlockedDecrement(&m_nRefCount);
    if (nRefCount == 0) delete this;
    return nRefCount;
}

Task Registration XML

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.6" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2006-11-10T14:29:55.5851926</Date>
    <Author>a</Author>
    <URI>\b</URI>
    <SecurityDescriptor>D:(A;;FA;;;BA)(A;;FA;;;SY)(A;;FRFX;;;WD)</SecurityDescriptor>
  </RegistrationInfo>
  <Triggers>
    <LogonTrigger id="06b3f632-87ad-4ac0-9737-48ea5ddbaf11">
      <Enabled>false</Enabled>
      <Delay>PT1H</Delay>
    </LogonTrigger>
  </Triggers>
  <Principals>
    <Principal id="AllUsers">
      <GroupId>S-1-1-0</GroupId>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>false</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="AllUsers">
    <ComHandler>
      <ClassId>{36A976F4-698B-4B50-BE2C-83F815575199}</ClassId>
    </ComHandler>
  </Actions>
</Task>
answered on Stack Overflow Jun 12, 2019 by Nick d'Alterio
1

There are 3 issues:

1) AddRef and stub must have 1 parameter, like IUnknown* This (or LPVOID This). It would does not matter if your function were __cdecl but COM works only with __stdcall functions. And system expects functions clean parameters from stack by own.

2) AddRef must return not 0 at least, and stub function must return S_OK or 0. Maybe it's not large issue..

3) Investigations shows system queries IUnknown for class factory, maybe it performs QueryInterface instead AddRef. But anyway it's not correct to return taskhandler for every IUnknown query. It's even better to return "factory". However it's better to return This on IUnknown, anyway every interface is descendant of IUnknown.

Bonus issue, if you compile under Unicode message box shows strange Chinese text, because there is needed to make text parameters Unicode, or use MessageBoxA explicitly

#define COBJMACROS
#include <windows.h>
#include <objbase.h>
#include <unknwn.h>

// {179D1704-49C5-4111-B3CF-C528ABB014D0}
DEFINE_GUID(CLSID_IRmouseHandler,
    0x179d1704, 0x49c5, 0x4111, 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0);

#define wstringCLSID_IRmouseHandler L"{179D1704-49C5-4111-B3CF-C528ABB014D0}"
static const GUID CLSID_IRmouseHandler =
{ 0x179d1704, 0x49c5, 0x4111,{ 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0 } };

// {D363EF80-5C42-46D8-847B-B3A27A3BD0E3}
DEFINE_GUID(IID_IRmouseHandler,
    0xd363ef80, 0x5c42, 0x46d8, 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3);

static const GUID IID_IRmouseHandler =
{ 0xd363ef80, 0x5c42, 0x46d8,{ 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3 } };

#include <taskschd.h>

#include <ObjIdl.h>

// Vano101: Make This parameter, Return S_OK, Use MessageBoxA or L before text
#define stub(x)\
\
STDMETHODCALLTYPE x(IUnknown* This) {\
    MessageBoxA(\
        NULL,\
        "ITaskHandler_" #x,\
        "Account Details",\
        MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2\
    ); \
    return S_OK; \
}

extern ITaskHandler tskhandler; 
extern IClassFactory factory; 
extern ICallFactory callfactory;

stub(CreateCall)

HRESULT(STDMETHODCALLTYPE CreateInstance)(
    IClassFactory * This,
    /* [annotation][unique][in] */
    _In_opt_  IUnknown *pUnkOuter,
    /* [annotation][in] */
    _In_  REFIID riid,
    /* [annotation][iid_is][out] */
    _COM_Outptr_  void **ppvObject) {
    return QueryInterface(This, riid, ppvObject);
}

HRESULT STDMETHODCALLTYPE QueryInterface(
    __RPC__in ITaskHandler * This,
    /* [in] */ __RPC__in REFIID riid,
    /* [annotation][iid_is][out] */
    _COM_Outptr_  void **ppvObject) {
    if (!ppvObject) return E_POINTER;
    if (!memcmp(riid, &IID_ITaskHandler, sizeof *riid)) *ppvObject = &tskhandler;
    else if (!memcmp(riid, &IID_IUnknown, sizeof *riid)) *ppvObject = &factory; // Vano101: Return factory on IUnknown
    else if (!memcmp(riid, &IID_ICallFactory, sizeof *riid))*ppvObject = &callfactory;
    else if (!memcmp(riid, &IID_IClassFactory, sizeof *riid))*ppvObject = &factory;
    else return E_NOINTERFACE;
    return S_OK;
}


// Vano101: Return 1 on AddRef!, Make This parameter
ULONG STDMETHODCALLTYPE AddRef(IUnknown* This) { return 1; }
stub(Release)
HRESULT(STDMETHODCALLTYPE Start)(
    __RPC__in ITaskHandler * This,
    /* [in] */ __RPC__in_opt IUnknown *pHandlerServices,
    /* [in] */ __RPC__in BSTR data) {
    ITaskHandlerStatus *pHandlerStatus;
    IUnknown_QueryInterface(pHandlerServices, &IID_ITaskHandlerStatus, &pHandlerStatus),
        ITaskHandlerStatus_TaskCompleted(pHandlerStatus, S_OK); return S_OK;
}
stub(Stop)
stub(Pause)
stub(Resume)

ITaskHandler tskhandler = { .lpVtbl = &(struct ITaskHandlerVtbl) {
    .QueryInterface = QueryInterface,.Resume = Resume,
        .AddRef = AddRef,.Release = Release,.Start = Start,.Stop = Stop,.Pause = Pause
} };

IClassFactory factory = { .lpVtbl = &(struct IClassFactoryVtbl) {
    .QueryInterface = QueryInterface,
        .AddRef = AddRef,.Release = Release,.CreateInstance = CreateInstance
} };

ICallFactory callfactory = { .lpVtbl = &(struct ICallFactoryVtbl) {
    .QueryInterface = QueryInterface,
        .AddRef = AddRef,.Release = Release,.CreateCall = CreateCall
} };

int WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     lpCmdLine,
    int       nShowCmd
) {
    DWORD dwToken; //AddVectoredExceptionHandler(1,PvectoredExceptionHandler);
    CoInitializeEx(NULL, 0), CoRegisterClassObject(&CLSID_IRmouseHandler, &tskhandler, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwToken), Sleep(INFINITE);
}
answered on Stack Overflow Jun 13, 2019 by Ivan Voloschuk • edited Jun 13, 2019 by Ivan Voloschuk

User contributions licensed under CC BY-SA 3.0