dahall / TaskScheduler

Provides a .NET wrapper for the Windows Task Scheduler. It aggregates the multiple versions, provides an editor and allows for localization.
MIT License
1.2k stars 191 forks source link

Task Fails to run under different user account #976

Open fritznkitz opened 10 months ago

fritznkitz commented 10 months ago

Describe the bug After creating a task to run for one user (with highest privileges), it will run for that user. But if you log in as another user, it will not run, saying the user does not have permission.

To Reproduce Steps to reproduce the behavior:

  1. Create Task using RegisterTaskDefinition("TaskName", def, TaskCreation.CreateOrUpdate, runnerUserName, runnerUserPwd, TaskLogonType.Password);
  2. Log on to the server using a different account
  3. Open Task Scheduler and try to run the task created in step 1

Expected behavior I expect the task to run, however it gives a permissions error.

Environment (please complete the following information):

Additional context As per https://learn.microsoft.com/en-us/answers/questions/707600/task-scheduler-the-user-account-does-not-have-perm if one exports the task to xml, deletes the task and then imports it, it will then work for all uses. Or if I run the powershell script (there's a link to it from that page) on the task, it will work. It has to do with some permissions being now stored in the registry.

I have done done some searching but can't find a similar posting to this, but apologize if this is a duplicate or if it is handled by code somehow already.

CWIWC commented 10 months ago

I do this all the time, but I don't do it like you are, there are issues with this method when dealing with azure users so this will not work for that.

//create my task instance
TaskDefinition td = TaskService.Instance.NewTask();
//create the name of your task
td.RegistrationInfo.Description = "YOURTASKNAME";
//set the elevation level 
td.Principal.RunLevel = TaskRunLevel.Highest;
//set your username
td.Principal.UserId = userName;
//dont start on battery set to false
td.Settings.DisallowStartIfOnBatteries = false;
//stop if on battery set to false
td.Settings.StopIfGoingOnBatteries = false;

//you would also use the td object to add triggers like on boot or on logon triggers, they would be added like the fallowing

//td.Settings.RunOnlyIfLoggedOn = true;
//BootTrigger bt = new BootTrigger();
//LogonTrigger lot = new LogonTrigger();
//lot.Delay = TimeSpan.FromMinutes(1);
//bt.Delay = TimeSpan.FromMinutes(1);
//td.Triggers.Add(bt);
//td.Triggers.Add(lot);

//add our actions here, in my case the app i want to launch and its args
 td.Actions.Add(cmdPath, cmdArgs);
 //set your password, this is NOT needed if your running as system or "nt authority/system" (you can test with psexec reflecting)
 //this is the one i use and know is working. like 1000+ machines working.
 TaskService.Instance.RootFolder.RegisterTaskDefinition($"{taskName}", td);
 //OR
//this is not tested but should work, but i dont use passwords to make tasks, as i dont know the passwords for my clients login and should not know them.
TaskService.Instance.RootFolder.RegisterTaskDefinition($"{taskName}", td, TaskCreation.CreateOrUpdate, userName, PASSWORD);
  try
 {
        TaskService.Instance.FindTask($"LaunchAsUser-{userTag}").Run();
    }
 catch (Exception ex)
 {
        Console.WriteLine($"{ex.Message}")
        if (ex.InnerException != null)
        {
        Console.WriteLine($"{ex.InnerException.Message}")
     }
 }

It should also let you create a task for yourself without a password but if you want to be your user, and to make a task for any other user as a user, even as admin, you will need the password.

This may not help you, but I hope it solves your issue or at least gives you a path to what you want.

CWIWC commented 10 months ago

with azure users, the user can make the task for himself, the system can not make tasks for the azure user.

I have not tried to get an azure user to make a task for an azure user.

fritznkitz commented 10 months ago

Thank you for your response. The only significant differences in our approaches were that you set td.Principal.UserId = userName; and that you don't set the username & password explicitly. We see it it as security issue and need to set the username & password. We have been using this library for over 10 years on regular Windows Domain accounts (not Azure) and on Windows Server 2008 up to 2019 now. It is as we are changing some of our other practices that this issue has become illuminated. The case is that we wish to create tasks to run on a schedule under a particular account, and yet allow another user admin account to run the task on from the task scheduler ad hoc.

I have changed it to set the td.Principal.UserId, as well as the option to set the username & password in the RegisterTaskDefinition (as we were doing before), and it still does not work to try to run the task from the task scheduler when logged in as another admin user. However, if we do as that link I provided in my original post suggests and and export, delete and reimport the task, then it will run as desired.

if it helps, here's a massaged version of the my code that creates the task:

using (TaskService ts = new TaskService())
{
    if (ts.FindTask(Name) != null)
    {
       // handle task already existing
        return ;
    }
    TaskDefinition def = ts.NewTask();
    def.RegistrationInfo.Description = Name;  // Name defined elsewhere
    def.Actions.Add(new ExecAction(Program, Parameters)); // Program and Parameters defined elsewhere
    def.Triggers.Add(OneTrigger); // OneTrigger defined elsewhere
    def.Settings.IdleSettings.IdleDuration = new TimeSpan(0, IdleWaitMinutes, 0);
    def.Settings.Priority = System.Diagnostics.ProcessPriorityClass.Normal;
    if (OneTrigger.EndBoundary != DateTime.MaxValue)
        def.Settings.DeleteExpiredTaskAfter = new TimeSpan(7, 0, 0, 0);

    def.Principal.RunLevel = TaskRunLevel.Highest;
    def.Principal.UserId = RunnerUser;

    try
    {
        ts.RootFolder.RegisterTaskDefinition(Name, def, TaskCreation.CreateOrUpdate, RunnerUser, RunnerPwd, TaskLogonType.Password);
    }
    catch (Exception ex)
    {
        ts.RootFolder.DeleteTask(Name); // so that there's not some 'bad' task hanging out there...
        // handle exception 
    }
}

Thank you for your comments and suggestions.

CWIWC commented 9 months ago

I would argue that having access to passwords is less secure then having access to system if you already have system, but that is irrelevant.

I took your code and made some slight changes The fallowing is tested and WORKS for me. The user was an administrator.

First I created a project and added the app manifest file and set the project to "requireAdministrator" image

This will prevent the unauthorized action error.

Here is my working code, TESTED, where I use a logon trigger for my test, this does launch notepad under the "TestUser" i created for this process and logged into 1 time before hand.


public void Run()
        {

            string Name = "ExampleTaskPerUser";
            string username = "TestUser";
            string password = "TestUser";
            TimeTrigger trigger = new TimeTrigger();
            //for my testing to verify this works
            LogonTrigger bootTrigger = new LogonTrigger();
            bootTrigger.Enabled = true;
            // example of getting a parameter. will return "FAIL" if not present. remove if not needed.
            using (TaskService ts = new TaskService())
            {
                if (ts.FindTask(Name) != null)
                {
                    // handle task already existing
                    return;
                }
                TaskDefinition def = ts.NewTask();
                def.RegistrationInfo.Description = Name;  // Name defined elsewhere
                def.Actions.Add(new ExecAction("C:\\Windows\\System32\\notepad.exe")); // Program and Parameters defined elsewhere
                def.Settings.Priority = System.Diagnostics.ProcessPriorityClass.Normal;
                //changed by me
                if (trigger.EndBoundary != DateTime.MaxValue)
                    def.Settings.DeleteExpiredTaskAfter = new TimeSpan(7, 0, 0, 0);
                //============

                //added by me
                def.Triggers.Add(trigger);
                def.Triggers.Add(bootTrigger);
                //===========

                def.Principal.RunLevel = TaskRunLevel.Highest;
                def.Principal.UserId = username;

                try
                {
                    ts.RootFolder.RegisterTaskDefinition(Name, def, TaskCreation.CreateOrUpdate, username, password, TaskLogonType.InteractiveTokenOrPassword);
                }
                catch (Exception ex)
                {       
                    Console.WriteLine(ex.Message);// handle exception 
                    if (ex.InnerException != null)
                    {
                        Console.WriteLine(ex.InnerException.Message);
                    }
                    ts.RootFolder.DeleteTask(Name);
                }
            }
        }

I hope the above helps out and that I did not respond to late.

CWIWC commented 9 months ago

We see it it as security issue and need to set the username & password. We have been using this library for over 10 years on regular Windows Domain accounts (not Azure) and on Windows Server 2008 up to 2019 now. It is as we are changing some of our other practices that this issue has become illuminated. The case is that we wish to create tasks to run on a schedule under a particular account, and yet allow another user admin account to run the task on from the task scheduler ad hoc.

I'm semi confused by the ad hoc run part of your requirement, but if the task exists for the the user in question without a trigger they could run the task from task scheduler at any time.

But i may just not understand exactly what you meant.