8

I am trying to write a windows Logon trigger task using C++ on Windows 7.

I am following this microsoft tutorial.

But I am facing problem in saving the task to root folder. Here:

//  ------------------------------------------------------
    //  Save the task in the root folder.
    IRegisteredTask *pRegisteredTask = NULL;

    hr = pRootFolder->RegisterTaskDefinition(
            _bstr_t( wszTaskName ),
            pTask,
            TASK_CREATE_OR_UPDATE, 
            _variant_t(L"Builtin\\Administrators"), 
            _variant_t(), 
            TASK_LOGON_GROUP,
            _variant_t(L""),
            &pRegisteredTask);

Where the hr is getting error : No Mapping between account names and security ids was done

I also tried replacing _variant_t(L"Builtin\\Administrators") with _variant_t(L"S-1-5-32-544") to NULL out language hard coding issue, still No luck.

How can I make it work?

foobar
  • 2,887
  • 2
  • 30
  • 55
  • Try creating an explicit BSTR (using, say [`_bstr_t`](http://msdn.microsoft.com/en-us/library/zthfhkd6.aspx), and `_variant_t()` for the sddl (rather than `_variant_t(L"")`. – Eric Brown May 28 '14 at 18:16
  • If that fails, use `CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &sid, &dwBytes);` to create the administrator SID, followed by `ConvertSidToStringSid( &sid, &psz);` to convert that to string form. – Eric Brown May 28 '14 at 18:19
  • Hi Eric, Tried both the options you suggested. Unfortunately none worked for me. Getting the same error either way. – foobar Jun 16 '14 at 06:56
  • Looking over the various usages, I suspect the problem is actually in the definition of `pTask`, which you haven't provided. In particular, for group accounts, the code I have retrieves the `IPrincipal` and sets the `RunLevel` and `GroupId` properties on that. – Eric Brown Jun 16 '14 at 16:37
  • Can you give me or point me to a working copy of code? – foobar Jun 16 '14 at 16:40

2 Answers2

6

A definitive solution to creation of a TaskScheduler task on Windows startup

(with Administor privileges, working for Windows 7, 8, etc. Note that this won't display an UAC popup on Windows startup "Are you sure to run this software with Admin rights?", that's why the TaskScheduler method is more interesting in this case than the good old HKEY_LOCAL_MACHINE\...\CurrentVersion\Run solution)

There are a few things to update in this tutorial to make it work:

  • _variant_t(L"S-1-5-32-544") instead of _variant_t(L"Builtin\\Administrators")
  • _CRT_SECURE_NO_WARNINGS
  • In VC++, Project Properties > Configuration Properties > Linker > Manifest file > UAC Execution Level > requireAdministrator
  • Remove the date boundaries which are now outdated !
  • Replace hr = pLogonTrigger->put_UserId(_bstr_t(L"DOMAIN\\UserName")); by either a hardcoded Domain\Username, or by some Domain\Username detection code (I couldn't make it work), or just comment this line, it worked for me!
  • Add some code for TASK_RUNLEVEL_HIGHEST
  • Add some code to enable the task even if running from a laptop on batteries (default would be "don't run task if on batteries"!), and some code to prevent the .exe to be killed after some time (By default, a task will be stopped 72 hours after it starts to run), etc.

Then you'll get the famous:

Success! Task successfully registered.


Phew! After a few hours per day and some edits, now here is a working full main.cpp:

#define SECURITY_WIN32
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <comdef.h>
#include <Security.h>
#include <taskschd.h>
#pragma comment(lib, "taskschd.lib")
#pragma comment(lib, "comsupp.lib")

using namespace std;

#define TASKNAME L"Logon Trigger Test Task"

int __cdecl wmain()
{
    //  Get the windows directory and set the path to notepad.exe.
    wstring wstrExecutablePath = _wgetenv(L"WINDIR");
    wstrExecutablePath += L"\\SYSTEM32\\NOTEPAD.EXE";

    //  Initialize COM  
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr)) return 1;

    //  Set general COM security levels.
    hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
    if (FAILED(hr)) goto cleanup0;

    //  Create an instance of the Task Service.     
    ITaskService *pService = NULL;
    hr = CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void**)&pService);
    if (FAILED(hr)) goto cleanup0;

    //  Connect to the task service.    
    hr = pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
    if (FAILED(hr)) goto cleanup1;

    //  Get the pointer to the root task folder.  This folder will hold the new task that is registered.
    ITaskFolder *pRootFolder = NULL;
    hr = pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
    if (FAILED(hr)) goto cleanup1;

    //  If the same task exists, remove it. 
    pRootFolder->DeleteTask(_bstr_t(TASKNAME), 0);

    //  Create the task builder object to create the task.  
    ITaskDefinition *pTask = NULL;
    hr = pService->NewTask(0, &pTask);

    // COM clean up.  Pointer is no longer used.    
    pService->Release();
    if (FAILED(hr)) { pRootFolder->Release(); CoUninitialize(); return 1; }

    //  Get the registration info for setting the identification.   
    IRegistrationInfo *pRegInfo = NULL;
    hr = pTask->get_RegistrationInfo(&pRegInfo);
    if (FAILED(hr)) goto cleanup2;

    hr = pRegInfo->put_Author(L"Author Name");
    pRegInfo->Release();
    if (FAILED(hr)) goto cleanup2;

    //  Create the settings for the task
    ITaskSettings *pSettings = NULL;
    hr = pTask->get_Settings(&pSettings);
    if (FAILED(hr)) goto cleanup2;
    
    //  Set setting values for the task. 
    pSettings->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
    pSettings->put_StopIfGoingOnBatteries(VARIANT_FALSE);
    pSettings->put_ExecutionTimeLimit(_bstr_t(L"PT0S"));
    pSettings->Release();
    if (FAILED(hr)) goto cleanup2;

    //  Get the trigger collection to insert the logon trigger.
    ITriggerCollection *pTriggerCollection = NULL;
    hr = pTask->get_Triggers(&pTriggerCollection);
    if (FAILED(hr)) goto cleanup2;

    //  Add the logon trigger to the task.
    ITrigger *pTrigger = NULL;
    hr = pTriggerCollection->Create(TASK_TRIGGER_LOGON, &pTrigger);
    pTriggerCollection->Release();
    if (FAILED(hr)) goto cleanup2;

    ILogonTrigger *pLogonTrigger = NULL;
    hr = pTrigger->QueryInterface(IID_ILogonTrigger, (void**)&pLogonTrigger);
    pTrigger->Release();
    if (FAILED(hr)) goto cleanup2;

    hr = pLogonTrigger->put_Id(_bstr_t(L"Trigger1"));
    if (FAILED(hr)) goto cleanup2;

    //  Define the user.  The task will execute when the user logs on. The specified user must be a user on this computer.  
    //hr = pLogonTrigger->put_UserId(_bstr_t(L"DOMAIN\\UserName"));

    pLogonTrigger->Release();
    if (FAILED(hr)) goto cleanup2;

    IPrincipal *pPrincipal;
    hr = pTask->get_Principal(&pPrincipal);
    if (FAILED(hr)) goto cleanup2;

    hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
    if (FAILED(hr)) goto cleanup2;


    //  Add an Action to the task. This task will execute .exe
    IActionCollection *pActionCollection = NULL;

    //  Get the task action collection pointer.
    hr = pTask->get_Actions(&pActionCollection);
    if (FAILED(hr)) goto cleanup2;

    //  Create the action, specifying that it is an executable action.
    IAction *pAction = NULL;
    hr = pActionCollection->Create(TASK_ACTION_EXEC, &pAction);
    pActionCollection->Release();
    if (FAILED(hr)) goto cleanup2;

    //  QI for the executable task pointer.
    IExecAction *pExecAction = NULL;
    hr = pAction->QueryInterface(IID_IExecAction, (void**)&pExecAction);
    pAction->Release();
    if (FAILED(hr)) goto cleanup2;

    //  Set the path of the executable.
    hr = pExecAction->put_Path(_bstr_t(wstrExecutablePath.c_str()));
    pExecAction->Release();
    if (FAILED(hr)) goto cleanup2;

    //  Save the task in the root folder.
    IRegisteredTask *pRegisteredTask = NULL;
    hr = pRootFolder->RegisterTaskDefinition(_bstr_t(TASKNAME), pTask, TASK_CREATE_OR_UPDATE, _variant_t(L"S-1-5-32-544"), _variant_t(), TASK_LOGON_GROUP, _variant_t(L""), &pRegisteredTask);      //_variant_t(L"Builtin\\Administrators"),
    if (FAILED(hr)) goto cleanup2;

    printf("Success! Task successfully registered.");
    getchar();

    pRootFolder->Release();
    pTask->Release();
    pRegisteredTask->Release();
    CoUninitialize();
    return 0;

cleanup0:
    CoUninitialize();
    return 1;

cleanup1:
    pService->Release();
    CoUninitialize();
    return 1;

cleanup2:
    pRootFolder->Release();
    pTask->Release();
    CoUninitialize();
    return 1;
}
Community
  • 1
  • 1
Basj
  • 41,386
  • 99
  • 383
  • 673
2

I suspect the demo code you have is XP-era, and hasn't been updated to match the Vista/Win7 rules.

I updated the sample to set the LUA settings after setting the logon trigger, and it seems to work:

    hr = pLogonTrigger->put_UserId(_bstr_t(L"DOMAIN\username"));
    if (FAILED(hr))
    {
        printf("\nCannot add user ID to logon trigger: %x", hr);
        CoUninitialize();
        return 1;
    }


    //*** NEW**** Set the LUA settings
    CComPtr<IPrincipal>         pPrincipal;

    hr = pTask->get_Principal(&pPrincipal);
    if (SUCCEEDED(hr))
    {
        hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_LUA);
    }
    if (SUCCEEDED(hr))
    {
        hr = pPrincipal->put_GroupId(_bstr_t(L"Builtin\\Administrators"));
    }
    if (FAILED(hr))
    {
        printf("\nCannot set runlevel/groupid: %x", hr);
        CoUninitialize();
        return 1;
    }

If you need it to run on XP, then it's likely that the get_Principal call will fail, so let that failure through.

Eric Brown
  • 13,774
  • 7
  • 30
  • 71
  • Added the LUA setting block. Still not working. same error. :( Can you share the full code that is working for you? – foobar Jun 16 '14 at 18:00
  • @smilepleeeaz You *did* change `pLogonTrigger->put_UserId(_bstr_t(L"DOMAIN\username"));` to have a real domain & username, right? – Eric Brown Jun 16 '14 at 18:10
  • Oops. I didn't. changing it did scheduled the task. Even without LUA settings. Oh God, Why!!! – foobar Jun 16 '14 at 18:19
  • But the whole point is not to prompt user for username , password.. how do we get that ? – foobar Jun 16 '14 at 18:22
  • Assuming you just want the current user, then [`GetUserNameEx(NameSamCompatible, pchBuf, &cchBuf)`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms724435(v=vs.85).aspx) should do the trick. – Eric Brown Jun 16 '14 at 19:33
  • Well it was just my bad, but you sure helped me enough to figure it out, so thanks, and you deserve the bounty reward. :) – foobar Jun 17 '14 at 06:13
  • BTW , is there a way we can make repetitive logon task? – foobar Jun 17 '14 at 06:13
  • @smilepleeeaz Not sure what you mean by 'repetitive logon task'; consider ITrigger::put_Repetition; this is defined for all triggers, including logon triggers. – Eric Brown Jun 17 '14 at 17:39
  • @Basj Shouldn't you be commenting on *that* answer, not here? Further, the next answer suggests passing null, which is documented to be the local computer. – Eric Brown Jul 21 '17 at 18:10