Creating Windows Task with PHP

Recently I have looked at how to list windows accounts via PHP with an eye to administration, read the windows registry, and list scheduled jobs on Microsoft Windows with PHP.  This time around I want to advance the Dante PHP Job Scheduler to the next logical level:  creating scheduled tasks.

Remember that writing the most basic COM related PHP could be as simple as translating from VBS to PHP.  However, VBS+COM is very involved and threatens the simplicity that is the reason we are using PHP in the first place.  So we will be concentrating on initial implementation (VBS translated to PHP) and I will be wrapping this functionality in PHP 5 classes later.

Once more taking a look at MSDN there is an example of adding a task to run at a certain time.  Also the example has given us an outline of the steps to accomplish this:

  1. Create a TaskService Object and Get a task folder
  2. Create a task (with TaskService.NewTask)
  3. Define information about the task
  4. Create a time-based trigger
  5. Create an action for the task
  6. Register the task

Looking at the example on MSDN, the first thing that strikes me about this process is that it is really messy and complicated to code.  Here is how I want to use it to register a notepad to run in 30 seconds.

$oTask = new X_Scheduler_Task('C:\\Windows\\System32\\notepad.exe');
$oTask->schedule(time()+30); // 30 seconds from now
$oTask->register();

Create a TaskService Object and Get a Task Folder

Now lets start with a TaskService object and selecting the folder.

$oSchedule = new COM("Schedule.Service");
$oSchedule->Connect();
$oFolder = $oSchedule->GetFolder("\\");

Create a task

Then create the new Task variant (refer to previous post for TaskService explination):

$oTaskDefinition = $oSchedule->NewTask(0);

Now we have an empty Task Definition variant ready to define. What is the ‘0’ (zero) for? MSDN says for the future. That is right, no reason, you just have to pass it. One of the nice things about it being wrapped the class will just handle it and I wont see it, as it mocks my OCD.

Now the TaskDefinition object is different from the RegisteredTask object.  Take a look at what the documentation gives us:

TaskDefinition Properties:

Actions Read/write Gets or sets a collection of actions that are performed by the task.
Data Read/write Gets or sets the data that is associated with the task. This data is ignored by the Task Scheduler service, but is used by third-parties who wish to extend the task format.
Principal Read/write Gets or sets the principal for the task that provides the security credentials for the task.
RegistrationInfo Read/write Gets or sets the registration information that is used to describe a task, such as the description of the task, the author of the task, and the date the task is registered.
Settings Read/write Gets or sets the settings that define how the Task Scheduler service performs the task.
Triggers Read/write Gets or sets a collection of triggers that are used to start a task.
XmlText Read/write Gets or sets the XML-formatted definition of the task.

Define Information About The Task

TaskDefinition is simply a meta style object and does not have any methods. The Data property looks interesting, I might look at that later.   So now that we have that, we will want to add some useful information in the ‘RegistrationInfo’ which is itself a meta style object.  The MSDN documentation falls flat here, but we get an idea from the example:

$RegistrationInfo = $oTaskDefinition->RegistrationInfo;
$RegistrationInfo->Description = "Start notepad";
$RegistrationInfo->Author = "Author Name";

You can also find the Principal docs if you fish arround on your own.  Now we need to define who the task will be run as.  The example uses interactive login which is a little awkward on a webserver.  So our Task Logon Type options are:

ASK_LOGON_NONE
0
The logon method is not specified. Used for non-NT credentials.
TASK_LOGON_PASSWORD
1
Use a password for logging on the user. The password must be supplied at registration time.
TASK_LOGON_S4U
2
Use an existing interactive token to run a task. The user must log on using a service for user (S4U) logon. When an S4U logon is used, no password is stored by the system and there is no access to either the network or encrypted files.
TASK_LOGON_INTERACTIVE_TOKEN
3
User must already be logged on. The task will be run only in an existing interactive session.
TASK_LOGON_GROUP
4
Group activation. The userId field specifies the group.
TASK_LOGON_SERVICE_ACCOUNT
5
Indicates that a Local System, Local Service, or Network Service account is being used as a security context to run the task.
TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD
6
First use the interactive token. If the user is not logged on (no interactive token is available), then the password is used. The password must be specified when a task is registered. This flag is not recommended for new tasks because it is less reliable than TASK_LOGON_PASSWORD.

The easiest to use is TASK_LOGON_SERVICE_ACCOUNT since we are running the task under IIS. Note this registers and executes the task under the ‘IUSR’ user’s permissions or whatever account IIS is running under.  You can use TASK_LOGOON_PASSWORD to register the task to run under a different account.  This would be the best practice since you will want to track this user seperate from IIS and you may want to elevate the rights of this user.  If you use this method, you will need to supply valid user credentials when registering the task later.

define('TASK_SERVICE_ACCOUNT', 5);
$oPrincipal = $oTaskDefinition->Principal;
$oPrincipal ->LogonType = TASK_SERVICE_ACCOUNT;

Now there are some basic settings we need to set:

$oSettings = $oTaskDefinition->Settings
$oSettings->Enabled = true;
$oSettings->StartWhenAvailable = true;
$oSettings->Hidden = false;

Here is what TaskSettings are available:

AllowDemandStart Read/write Gets or sets a Boolean value that indicates that the task can be started by using either the Run command or the Context menu.
AllowHardTerminate Read/write Gets or sets a Boolean value that indicates that the task may be terminated by using TerminateProcess.
Compatibility Read/write Gets or sets an integer value that indicates which version of Task Scheduler a task is compatible with.
DeleteExpiredTaskAfter Read/write Gets or sets the amount of time that the Task Scheduler will wait before deleting the task after it expires.
DisallowStartIfOnBatteries Read/write Gets or sets a Boolean value that indicates that the task will not be started if the computer is running on battery power.
Enabled Read/write Gets or sets a Boolean value that indicates that the task is enabled. The task can be performed only when this setting is True.
ExecutionTimeLimit Read/write Gets or sets the amount of time allowed to complete the task.
Hidden Read/write Gets or sets a Boolean value that indicates that the task will not be visible in the UI. However, administrators can override this setting through the use of a “master switch” that makes all tasks visible in the UI.
IdleSettings Read/write Gets or sets the information that specifies how the Task Scheduler performs tasks when the computer is in an idle state.
MultipleInstances Read/write Gets or sets the policy that defines how the Task Scheduler deals with multiple instances of the task.
NetworkSettings Read/write Gets or sets the network settings object that contains a network profile identifier and name. If the RunOnlyIfNetworkAvailable property of TaskSettings is True and a network propfile is specified in the NetworkSettings property, then the task will run only if the specified network profile is available.
Priority Read/write Gets or sets the priority level of the task.
RestartCount Read/write Gets or sets the number of times that the Task Scheduler will attempt to restart the task.
RestartInterval Read/write Gets or sets a value that specifies how long the Task Scheduler will attempt to restart the task.
RunOnlyIfIdle Read/write Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only if the computer is in an idle state.
RunOnlyIfNetworkAvailable Read/write Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only when a network is available.
StartWhenAvailable Read/write Gets or sets a Boolean value that indicates that the Task Scheduler can start the task at any time after its scheduled time has passed.
StopIfGoingOnBatteries Read/write Gets or sets a Boolean value that indicates that the task will be stopped if the computer begins to run on battery power.
WakeToRun Read/write Gets or sets a Boolean value that indicates that the Task Scheduler will wake the computer when it is time to run the task.
XmlText Read/write Gets or sets an XML-formatted definition of the task settings.

Several of these are more than scalar values so if you decide to use others check the documentation.  With the settings above we are explicitly saying that the task is enabled, not hidden, and that it can be run any time after the scheduled time if something would delay the execution.   One property that I know will come in handy later is the XmlText.

Create a time-based trigger

Tasks can be triggered in various ways other than being triggered at a certain time.  This really makes Windows Task Scheduler 2 stand out from previous versions and various others like cron.  We will take a look at these later.

To create a trigger we need to use the trigger collection from the newly created task.  This is because the factory for triggers is kept on the collection to simultaneously register the trigger as it constructs it. Before we take a look at the code, lets take a look at the TriggerCollection methods and properties:

TriggerCollection Methods:

Clear Clears all triggers from the collection.
Create Creates a new trigger for the task.
Remove Removes the specified trigger from the collection of triggers used by the task.

TriggerCollection Properties:

Count Read-only Gets the number of triggers in the collection.
Item Read-only Gets the specified trigger from the collection.

So here is how we would translate the example code to PHP:

define('TRIGGER_TYPE_TIME', 1);
$oTrigger = $oTaskDefinition->Triggers->Create(TRIGGER_TYPE_TIME);

The various trigger types are as follows:

TASK_TRIGGER_EVENT
0
Triggers the task when a specific event occurs.
TASK_TRIGGER_TIME
1
Triggers the task at a specific time of day.
TASK_TRIGGER_DAILY
2
Triggers the task on a daily schedule. For example, the task starts at a specific time every day, every-other day, every third day, and so on.
TASK_TRIGGER_WEEKLY
3
Triggers the task on a weekly schedule. For example, the task starts at 8:00 AM on a specific day every week or other week.
TASK_TRIGGER_MONTHLY
4
Triggers the task on a monthly schedule. For example, the task starts on specific days of specific months.
TASK_TRIGGER_MONTHLYDOW
5
Triggers the task on a monthly day-of-week schedule. For example, the task starts on a specific days of the week, weeks of the month, and months of the year.
TASK_TRIGGER_IDLE
6
Triggers the task when the computer goes into an idle state.
TASK_TRIGGER_REGISTRATION
7
Triggers the task when the task is registered.
TASK_TRIGGER_BOOT
8
Triggers the task when the computer boots.
TASK_TRIGGER_LOGON
9
Triggers the task when a specific user logs on.
TASK_TRIGGER_SESSION_STATE_CHANGE
11
Triggers the task when a specific session state changes.

Each trigger type yields a trigger object with properties specific to its type.  For more information on the trigger types, see the Trigger Types entry on MSDN.

Since we are creating a time trigger, we need to make sure the trigger time string is in the correct format.  In the example VBS has a XmlTime() function.  You should use your favorite date class, but for clarity we will just use the date function.
$iTime = time()+30;
$sStartTime = date('Y-m-d', $iTime).'T'.date('H:i:s', $iTime);
$iTime += (60*5);
$sEndTime = date('Y-m-d', $iTime).'T'.date('H:i:s', $iTime);

This will give us a start time 30seconds in the future and an end time five minutes later.  You can see that the time string will end up being something like "2009-05-30T14:12:46".

Now we can set these times on the trigger we created:

$oTrigger->StartBoundary = $sStartTime;
$oTrigger->EndBoundary = $sEndTime;
$oTrigger->ExecutionTimeLimit = "PT5M";   //Five minutes;
$oTrigger->Id = "TimeTriggerId";
$oTrigger->Enabled = true;

Above we set a five minute execution time limit.  According to MSDN this string is format is "PnYnMnDTnHnMnS" so P1M4DT2H5M specifies one month, four days, two hours, and five minutes.  Yes, we will be happy to not worry about that string later.  You will also notice the Id property which is a string that is used for logging, and then we explicitly enable this trigger with "Enabled = true".

Create an action for the task

For the purposes of this demonstration we have selected to run notepad.  However the 'IUSR' account is not allowed to interact with the logged in user.  So we will fire the task, but you will not get Notepad to popup on the server.  So this makes our example a informal demonstration of windows security.  Here is the code:

define('ACTION_TYPE_EXEC', 0);
$Action = $oTaskDefinition->Actions->Create(ACTION_TYPE_EXEC);
$Action->Path = "C:\\Windows\\System32\\notepad.exe";

Again the ActionCollection object holds the factory for the ActionObject.  The types of action objects that can be passed to the factory are as follows:

TASK_ACTION_EXEC
0
This action performs a command-line operation.
TASK_ACTION_COM_HANDLER
5
This action fires a handler.
TASK_ACTION_SEND_EMAIL
6
This action sends an e-mail.
TASK_ACTION_SHOW_MESSAGE
7
This action shows a message box.

After the action is created we set the path to the executable.  You can also specify the working directory as seen in the ExecAction Object Properties:

Arguments Read/write Gets or sets the arguments associated with the command-line operation.
Id Read/write Gets or sets the identifier of the action.
Path Read/write Gets or sets the path to an executable file.
Type Read-only Gets the type of the action.
WorkingDirectory Read/write Gets or sets the directory that contains either the executable file or the files that are used by the executable file.

Register The Task

As the final action we will register the task.  We will use a unique identifier for the task and set it to update or create new so that we keep things clean. As mentioned before we are going to simply let the task run under the 'IUSR' group account in this example so we will leave the username and password fields blank.  For some reason 'null' is not always recieved well by COM methods. To work arround this we make an empty Variant:

$empty = new VARIANT();

Then we use this in the registering of our task:

define('TASK_LOGON_GROUP', 4);
$oFolder->RegisterTaskDefinition('NotePad', $oTaskDefinition, TASK_CREATE_OR_UPDATE, $empty, $empty, TASK_LOGON_GROUP);

The Task Creation Flags can be any of the following:

TASK_VALIDATE_ONLY
1
The Task Scheduler checks the syntax of the XML that describes the task but does not register the task. This constant cannot be combined with the TASK_CREATE, TASK_UPDATE, or TASK_CREATE_OR_UPDATE values.
TASK_CREATE
2
The Task Scheduler registers the task as a new task.
TASK_UPDATE
4
The Task Scheduler registers the task as an updated version of an existing task. When a task with a registration trigger is updated, the task will execute after the update occurs.
TASK_CREATE_OR_UPDATE
6
The Task Scheduler either registers the task as a new task or as an updated version if the task already exists. Equivalent to TASK_CREATE | TASK_UPDATE.
TASK_DISABLE
8
The Task Scheduler disables the existing task.
TASK_DONT_ADD_PRINCIPAL_ACE
10
The Task Scheduler is prevented from adding the allow access-control entry (ACE) for the context principal. When the TaskFolder.RegisterTaskDefinition function is called with this flag to update a task, the Task Scheduler service does not add the ACE for the new context principal and does not remove the ACE from the old context principal.
TASK_IGNORE_REGISTRATION_TRIGGERS
20
The Task Scheduler creates the task, but ignores the registration triggers in the task. By ignoring the registration triggers, the task will not execute when it is registered unless a time-based trigger causes it to execute on registration.

The Login Type parameters can be any of the following:

TASK_LOGON_NONE
0
The logon method is not specified. Used for non-NT credentials.
TASK_LOGON_PASSWORD
1
Use a password for logging on the user. The password must be supplied at registration time.
TASK_LOGON_S4U
2
Use an existing interactive token to run a task. The user must log on using a service for user (S4U) logon. When an S4U logon is used, no password is stored by the system and there is no access to either the network or to encrypted files.
TASK_LOGON_INTERACTIVE_TOKEN
3
User must already be logged on. The task will be run only in an existing interactive session.
TASK_LOGON_GROUP
4
Group activation. The groupId field specifies the group.
TASK_LOGON_SERVICE_ACCOUNT
5
Indicates that a Local System, Local Service, or Network Service account is being used as a security context to run the task.
TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD
6
First use the interactive token. If the user is not logged on (no interactive token is available), then the password is used. The password must be specified when a task is registered. This flag is not recommended for new tasks because it is less reliable than TASK_LOGON_PASSWORD.

Run it and check your task scheduler for the listing. You should see something like Figure 1.

Figure 1
Figure 1

Conclusion

Hopefully through this tutorial you have seen that COM components expose some very useful Windows components.  MSDN is the authoritative resource for what is available by default through COM.  Many of the VBS examples can be translated directly into PHP using the COM classes. As I perfect Dante and the Crossley Framework Scheduler and Com wrappers I will post tutorials for using them, so keep watching here.

Advertisements
Creating Windows Task with PHP

7 thoughts on “Creating Windows Task with PHP

  1. Oliver Wright says:

    Hi, when I run this code, I do see the task in the task scheduler. However notepad never actually opens. When I create a task manually, it opens and I can see it. Seems strange that the task is created and in the task scheduler but doesn’t run. Am I missing something?

    1. drydenmaker says:

      Task Scheduler is really particular by what it lets run and acts different between Windows versions. There are many things to take into consideration, such as what user is the script running as, and what is the tasked made under. What permissions does the task user have? Also take note that there is a difference in how batch files are run between 2k8 and newer versions of Windows. I had to hack at the user and permissions thing for a while before I got it to work.

  2. Oliver Wright says:

    After playing around with this some more I set Principal.RunLevel = TASK_RUNLEVEL_HIGHEST giving it admin rights and it worked great. This is a very useful post, thanks.

  3. Julius says:

    Hi,

    It seems really interesting what you have done, trying to run your code, I get Internal Server Erros, I guess it is because

    new X_Scheduler_Win_Service();

    Is not recognized, do I need certain library or any configuration I must change?

    I am testing with WAMP as a local host and I will be using on a server with IIS, I would appreciate some help.

    Thanks!

  4. jose says:

    hi i just found this super tutorial today, and was wondering does it works for windows 8, i tried using it copying directly mostly, but i think i got lost somewhere because the ‘X_Scheduler_Task’ i dont know if its an already made class or a class i have to make or if its a name or referencing to a windows class, i really hope you read this op

    1. drydenmaker says:

      Glad to see you found this old post. The X_Scheduler_Task is either a part of the Crossley Framework or Dante Job Scheduler I don’t remember. Be cautioned: these projects that are quite old and incomplete. However they may be of some use to give you insight on the inner workings. I am not sure of the existence of these APIs in Windows 8. I am sure there are some much better ones via WinJS. A quick search turns up the WinJS Scheduler interface if it fits your needs.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s