I'm working on a project that needs to install a series of Windows Task Scheduler tasks and for that I created a Wix project and a Custom Action that takes care of all the details. That Custom Action was created using C++ to avoid dependencies on the .NET Framework.
At first I started executing SCHTASKS.EXE from a Wix custom action but, after I managed to create the correct command line to install a task correctly, I realized I couldn't set the Action's Working Directory ('Start in' in the Task Scheduler UI) from the SCHTASKS.EXE command line, because it simply didn't have the option...
I decided then to use COM in C++ (#import <taskschd.dll> raw_interfaces_only) to get to the Task Scheduler and tweak the WorkingFolder using the API after installing the Task using SCHTASKS.EXE which was working fine besides that detail.
I managed to get to the task and read its values after installing the task correctly but when I executed put_WorkingDirectory method with the current value that didn't fail actually, but the value wasn't saved into the task.
Does anyone have a clue why I couldn't get there? This is part of the code I used to get to the ExecAction and set the value successfully. Remember this is within a Wix Custom action so some of the methods calls are Wix's.
This code is actually working and the log shows the correct path I intend to set, but the task doesn't get changed. What am I doing wrong?
HRESULT UpdateWorkingDirectory(TaskScheduler::ITaskFolderPtr rootFolder, BSTR taskName, BSTR installFolder)
{
HRESULT hr = S_OK;
TaskScheduler::IRegisteredTaskCollectionPtr taskCollection;
LONG numTasks = 0;
TaskScheduler::IRegisteredTaskPtr thisTask;
TaskScheduler::ITaskDefinitionPtr definition;
TaskScheduler::IActionCollectionPtr actions;
TaskScheduler::IActionPtr action;
TaskScheduler::IExecActionPtr execAction;
long actionCount;
hr = rootFolder->GetTasks(NULL, &taskCollection);
ExitOnFailure(hr, "Cannot get task collection pointer");
hr = taskCollection->get_Count(&numTasks);
ExitOnFailure(hr, "Cannot get task collection item count");
for (LONG taskIdx = 0; taskIdx < numTasks; taskIdx++) {
TaskScheduler::IRegisteredTaskPtr registeredTask;
bstr_t taskIdxName;
hr = taskCollection->get_Item(variant_t(taskIdx + 1), ®isteredTask);
ExitOnFailure(hr, "Cannot get task item %d", taskIdx + 1);
hr = registeredTask->get_Name(&taskIdxName.GetBSTR());
ExitOnFailure(hr, "Cannot get task name");
WcaLog(LOGMSG_STANDARD, " registered task name = %s", (LPCSTR)taskIdxName);
if (strcmp(bstr_t(taskName), taskIdxName) == 0) {
thisTask = registeredTask;
break;
}
}
if (thisTask == NULL) {
hr = E_FAIL;
ExitOnFailure(hr, "task {%S} not found", taskName);
}
hr = thisTask->get_Definition(&definition);
ExitOnFailure(hr, "error getting task definition for {%S}", taskName);
hr = definition->get_Actions(&actions);
ExitOnFailure(hr, "error getting actions for {%S}", taskName);
WcaLog(LOGMSG_STANDARD, " got actions from %S", taskName);
hr = actions->get_Count(&actionCount);
ExitOnFailure(hr, "error getting action count for {%S}", taskName);
WcaLog(LOGMSG_STANDARD, " got count = %d from {%S}", actionCount, taskName);
if (actionCount > 0) {
bstr_t actionId;
bstr_t arguments;
bstr_t path;
hr = actions->get_Item(1, &action);
ExitOnFailure(hr, "error getting action[1] for {%S}", taskName);
hr = action->QueryInterface(&execAction);
ExitOnFailure(hr, "error getting ExecAction for {%S}", taskName);
hr = execAction->get_Id(&actionId.GetBSTR());
ExitOnFailure(hr, "error getting Exec Action id for first exec action of {%S}", taskName);
WcaLog(LOGMSG_STANDARD, " first Exec Action Id is %s", (LPCSTR)actionId);
hr = execAction->get_Arguments(&arguments.GetBSTR());
ExitOnFailure(hr, "error getting Exec Action arguments for first exec action of {%S}", taskName);
WcaLog(LOGMSG_STANDARD, " first Exec Action arguments are %s", (LPCSTR)arguments);
hr = execAction->get_Path(&path.GetBSTR());
ExitOnFailure(hr, "error getting Exec Action path for first exec action of {%S}", taskName);
WcaLog(LOGMSG_STANDARD, " first Exec Action path is %s", (LPCSTR)path);
hr = execAction->put_WorkingDirectory(installFolder);
ExitOnFailure(hr, "error putting working directory for {%S}", taskName);
WcaLog(LOGMSG_STANDARD, " successful put working directory to %S", installFolder);
}
LExit:
return hr;
}
NOTE: All C++ samples I found used simple interface pointers instead of smart pointers, I preferred smart pointers to avoid taking care of release by myself
It seems to me that this is a good example:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa446854(v=vs.85).aspx
More specifically, it shows that you call ITask::Activate before changing things like the working directory, and that you call IPersistFile to save the changes. I can't see any of these in that posted code sample. I've not looked at these interfaces for a while, but that might be the issue.
I found the answer
I had to change my method signature to accommodate username and password
HRESULT UpdateWorkingDirectory(TaskScheduler::ITaskFolderPtr rootFolder, BSTR taskName, BSTR installFolder, variant_t username, variant_t password)
and add these five lines
thisTask = NULL;
hr = rootFolder->RegisterTaskDefinition(taskName, definition, TaskScheduler::_TASK_CREATION::TASK_UPDATE, username, password, TaskScheduler::_TASK_LOGON_TYPE::TASK_LOGON_PASSWORD, variant_t(), &thisTask);
ExitOnFailure(hr, "error updating task regisration for {%S}", taskName);
WcaLog(LOGMSG_STANDARD, " successful update of task regisration to %S", taskName);
after executing put_WorkingDirectory.
This updates the registration of the task definition with the updated value, that was what I wanted to do, and it worked!!!!
Related
I am using Media Foundation to create a webcam viewer.
Critical to this application is that the webcam stream is horizontally mirrored.
I am using the Video Processor MFT
to achieve this.
Here's the relevant code to add the MFT:
void tryMirror(IMFPMediaPlayer* pPlayer) {
IMFTransform* pMFT = NULL;
IMFVideoProcessorControl* pVPC = NULL;
HRESULT hr = CoCreateInstance(CLSID_VideoProcessorMFT, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pMFT));
if (FAILED(hr)) {
ShowHResultErrorMessage(L"CoCreateInstance(CLSID_VideoProcessorMFT, ...) failed", hr);
goto done;
}
hr = pMFT->QueryInterface(&pVPC);
if (FAILED(hr)) {
ShowHResultErrorMessage(L"pMFT->QueryInterface(&pVPC) failed", hr);
goto done;
}
hr = pVPC->SetMirror(MIRROR_HORIZONTAL);
if (FAILED(hr)) {
ShowHResultErrorMessage(L"pVPC->SetMirror(MIRROR_HORIZONTAL) failed", hr);
goto done;
}
hr = pPlayer->InsertEffect(pMFT, FALSE); // Not optional - critical functionality
if (FAILED(hr)) {
ShowHResultErrorMessage(L"m_pPlayer->InsertEffect(CLSID_VideoProcessorMFT) failed", hr);
goto done;
}
done:
SafeRelease(&pMFT);
SafeRelease(&pVPC);
}
// class CPreview implements IMFPMediaPlayerCallback as follows
void STDMETHODCALLTYPE CPreview::OnMediaPlayerEvent(MFP_EVENT_HEADER* pEventHeader) {
switch (pEventHeader->eEventType)
{
//...
case MFP_EVENT_TYPE_MEDIAITEM_SET:
OnMediaItemSet(MFP_GET_MEDIAITEM_SET_EVENT(pEventHeader));
break;
case MFP_EVENT_TYPE_PLAY:
OnMfpPlay(MFP_GET_PLAY_EVENT(pEventHeader));
break;
}
}
// Called after I set the webcam media source
void CPreview::OnMediaItemSet(MFP_MEDIAITEM_SET_EVENT* /*pEvent*/)
{
HRESULT hr = m_pPlayer->Play();
if (FAILED(hr)) {
ShowHResultErrorMessage(L"m_pPlayer->Play() failed", hr);
}
}
void CPreview::OnMfpPlay(MFP_PLAY_EVENT* pEvent) {
if (FAILED(pEvent->header.hrEvent)) {
ShowHResultErrorMessage(L"OnMfpPlay failed", pEvent->header.hrEvent);
WCHAR msg[1000];
HRESULT hr = StringCbPrintf(msg, sizeof(msg), L"Event type: 0x%X", pEvent->header.eEventType);
ShowErrorMessage(msg);
return;
}
}
void ShowHResultErrorMessage(PCWSTR errContext, HRESULT hrErr) {
_com_error err(hrErr);
LPCTSTR hrErrMsg = err.ErrorMessage();
WCHAR msg[1000];
HRESULT hr = StringCbPrintf(msg, sizeof(msg), L"%s (HRESULT=0x%X, %s)", errContext, hrErr, hrErrMsg);
if (SUCCEEDED(hr)) {
MessageBox(NULL, msg, L"Error", MB_ICONERROR);
}
}
On my development machine, this program runs without error, exactly as desired.
However, on a different user machine,
it fails with this error:
OnMfpPlay failed (HRESULT=0xC00D36B2, The request is invalid in the current state.)
That is,
this error comes through on the OnMediaPlayerEvent callback
of the IMFPMediaPlayerCallback object.
I do know a few things about the machine that this fails on:
The user has also run a modified version,
with the MFT set to optional, like so:
pPlayer->InsertEffect(pMFT, TRUE).
In this case, the program runs,
but the mirroring MFT has no effect.
The error is definitely caused by this MFT.
This user is running Windows 10, version 1909.
The Video Processor MFT is clearly available.
Its API claims to work - all HRESULTs are successful.
This error, "The request is invalid in the current state", could mean anything, and
I can't find any way to get more observability.
What does 'The request is invalid in the current state' mean?
Why is it generated by adding a Video Processor MFT, only on some machines?
How can I debug this with a more specific error?
I am trying to connect to an SQL Server 2008 instance in a different machine in my same domain, through C++ and ADO. I am able to connect successfully through the SQL Server Management Studio. But by passing the same credentials, in my C++ program, I am getting error: "Login failed for user 'DOMAIN\MyUserID'." Here is the code that I was trying with:
HRESULT hr = -1;
CoInitialize(NULL);
try
{
_ConnectionPtr pConn1("ADODB.Connection");
_CommandPtr pCmd1("ADODB.Command");
hr = pConn1.CreateInstance("ADODB.Connection");
pConn1->Provider = "sqloledb";
hr = pConn1->Open("Data Source='DATAXXX';Initial Catalog='DATA123';Integrated Security=SSPI", "XX", "xyz123", adConnectUnspecified);
//hr value returned is : S_OK
}
catch (_com_error& ex) //ex value is: DB_SEC_E_AUTH_FAILED Authentication failed
{
CString err = ex.Description();
AfxMessageBox(err); //err = Login failed for user 'DOMAIN\\MyUserID'
}
if (hr != S_OK)
{
AfxMessageBox("ERROR: Opening Database connection");
return;
}
pCmd1->ActiveConnection = pConn1;
Please let me know what I am doing wrong here. My OS is Windows 8, and I am using VS2013
I'm using IApplicationActivationManager::ActivateApplication() from MSDN ActivateApplication API
to write a console app EXE (VC++) which launches a "Photos metro app & displays a PNG image". Here is teh code snippet. It's activating the "Photos metro application" but not able to display the image using the "Photos app".
CoInitializeEx(NULL, COINIT_MULTITHREADED);
LPCWSTR appId = L"Microsoft.Windows.Photos_8wekyb3d8bbwe!App";
LPCWSTR imageArg = L" C:\\data\\Users\\Public\\Pictures\\image123.png";
IApplicationActivationManager* paam = NULL;
HRESULT hr = E_FAIL;
__try
{
hr = CoCreateInstance(CLSID_ApplicationActivationManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&paam));
if (FAILED(hr))
{
cout << "Error creating CoCreateINstance & HR is" <<hr<< endl;
return 0;
}
DWORD pid = 0;
hr = paam->ActivateApplication(appId, imageArg, AO_NONE, &pid);
if (FAILED(hr))
{
cout << "Error in ActivateApplication call & HR is " <<hr<< endl;
return 0;
}
cout << hex << hr;
if (hr == 0)
wprintf(L"Activated %s with pid %d\r\n", appId, pid);
}
__finally
{
if (paam) paam->Release();
}
CoUninitialize();
I'm sure the error is in the 2nd argument of the "ActivateApplication()" function, where I'm giving the argument. I tried different ways of giving arguments like :
LPCWSTR imageArg = L"
C:\data\Users\Public\Pictures\image123.png"; OR
LPCWSTR imageArg = L"-
C:\data\Users\Public\Pictures\image123.png";
LPCWSTR
imageArg = L"C:\data\Users\Public\Pictures\image123.png";
To perform a file activation use the IApplicationActivationManager::ActivateForFile method.
You can create a ShellItem from a file path with SHCreateItemFromParsingName (the path is a parsing name) and can create a ShellItemArray from that with
SHCreateShellItemArrayFromShellItem
The Photos app won't listen for a file name on ActivateApplication's activation argument, and it wouldn't have access to the file by path if it did. The ActivateForFile method will convert the passed in ShellItems to StorageFiles which carry permissions to allow the app to open them.
I schedule my application in the task scheduler using the ITask class incorporated with the winapi.
I have this piece of code: dwTask->SetAccountInformation(L"", NULL);
The first parameter requires the username of the user to run the task.
By setting it to blank it runs the task as a NTAUTHORITY/SYSTEM service and therefore I run into spikes in the system where when I run commands in that task it doesnt appear on the screen (I want to fix this, as I'd like to keep NTAUTHORITY/SYSTEM privileges with my application -> see my last question: Task Scheduler WorkItem Not Running)
The question for this post is; How can I set the first parameter to the current user that is logged in? I tried to use GetUserName but the first parameter is a WIDE CHAR array data type. I tried to use GetUserNameW but the program still failed to compile due to unknown errors. Has anyone been able to set their scheduled task as the current user? Thanks!
EDIT
Okay! I added the GetUserNameW as suggested into my code. Then I linked it to the first parameter of SetAccountInformation. There are no errors compile time. After I run the program by double clicking it, it doesnt add the process as a task. Clearly it didn't work. Here is the code that I am using...
static bool AddProcessAsTask()
{
LPWSTR uname=L"";
DWORD size = 1024;
ITask *pITask;
HRESULT hr = S_OK;
LPCWSTR pwszTaskName;
ITaskScheduler *pITS;
LPCWSTR pwszApplicationName;
hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance
(
CLSID_CTaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskScheduler,
(void **) &pITS
);
if (FAILED(hr))
{
CoUninitialize();
return FALSE;
}
}
else
{
return FALSE;
}
pwszTaskName = L"AV_Updater Watchdog";
hr = pITS->NewWorkItem
(
pwszTaskName,
CLSID_CTask,
IID_ITask,
(IUnknown**)&pITask
);
if (FAILED(hr))
{
CoUninitialize();
return FALSE;
}
pwszApplicationName = L"C:\\Windows\\watchdog.exe";
hr = pITask->SetApplicationName(pwszApplicationName);
if (FAILED(hr))
{
pITS->Release();
pITask->Release();
CoUninitialize();
return FALSE;
}
GetUserNameW(uname, &size);
pITask->SetAccountInformation(uname, NULL);
pITask->SetFlags(TASK_FLAG_RUN_ONLY_IF_LOGGED_ON);
pITS->AddWorkItem(pwszTaskName, pITask);
pITS->Release();
hr = pITask->Run();
if (FAILED(hr))
{
pITask->Release();
CoUninitialize();
return FALSE;
}
pITask->Release();
CoUninitialize();
return TRUE;
}
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?
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;
}
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.