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:
- Create a TaskService Object and Get a task folder
- Create a task (with TaskService.NewTask)
- Define information about the task
- Create a time-based trigger
- Create an action for the task
- 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.
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.

0 Responses to “Creating Windows Task with PHP”