In the PHP development arena the LAMP (Linux, Apache, MySQL and PHP) stack is very common, but once in a while a client will come through the door with a Microsoft background. So what do you do if your CMS or framework was built with a Linux base layer in mind? Sounds easy, but what if you have jobs loaded into your Linux crontab for processing mail outs or after hours records processing?

There are options for implementing cron jobs with a Windows server, one of which is to offload your cron jobs to a Linux server and use WGet to remotely activate the processing script on the Windows server. For more information on setting up cron jobs on a Linux box please see Introducing Cron. Whilst this does work well, what if the Linux box goes down or its internet connection is severed and your scripts on the Windows server do not run? Or perhaps you do not have a Linux box to hand or you don’t feel comfortable sending cron job requests across the web.

Well you are in luck because Windows does offer a way to run cron jobs although its termed Scheduled Tasks by Microsoft. You will also need a small Windows binary that performs all the functions of the Linux WGet programme. I use the WGet for Windows utility compiled by Christopher Lewis. I usually stick it into the Windows Programs Files directory, but you can put it anywhere you like.

You might be thinking to yourself why use WGet when the box already has Internet Explorer and Mozilla Firefox installed; surely they can open a web address? Well, yes, they can but every time the task/cron job is invoked a new instance of the browser is created. Once the cron job is complete they do not self close either so your server would be left with many useless open processes running! No good at all.

Now we have the necessary software installed lets turn our attention to the setup of our Scheduled Task and as usual Windows has a nice wizard interface that can be found by accessing Start > Programs > Accessories > System Tools > Scheduled Tasks. However if you prefer the command line I go into detail further down the article about using schtasks.exe. When you open the utility you will see a Windows Explorer window containing any pre-existing tasks and an “Add Scheduled Task” icon which is the one we want to double click.

Let us step through the wizard setup process:

  1. Click next on the introduction page.
  2. Choose the programme you would like run your PHP script. This is where the WGet binary comes into play. Use the browse button to locate it. Click next.
  3. Give your task an easily identifiable name and choose the regularity with which you want the task to run. As an example you might use once a day for a mail out script and once a week for a database backup script. Yep you guessed it; click next.
  4. You will be presented with a screen giving you more in depth scheduling options where you can choose start time and date and what days, weeks or months of the year you want the task to run on. Once again, after you are done, click next.
  5. You must now choose a user that the task will run as and enter the username and password. I always setup a new Windows user solely for running Scheduled Tasks and I recommend you do the same for securities sake. As it is outside the scope of this article I will assume you know how to setup Windows users. You get the idea by now I am sure; click next.
  6. A summary screen will then be displayed and we need to check the “Open advanced properties for this Task when I click finish” checkbox.
  7. You will arrive on the Task tab of a window that looks much a like file properties window where we need to adjust the contents of the Run field to tell WGet which script we want it to run. Don’t delete the contents of the field just add the URL to your PHP script on the end. So you should end up with something like this: “C:Program Fileswgetwget.exe http://www.yourdomain.com/your\_script.php –spider”. The ‘–spider’ switch on the WGet binary causes it to behave like a web spider effectively pinging the file to see if it is there without downloading it (don’t worry the spider request will cause your script to be run). For more information please see the WGet manual.
  8. On the Schedule tab you can change the regularity of the Task with more granularity using the Advanced button and you can have multiple schedules for the same Task.
  9. On the Settings tab I would set the Task to Stop if it runs for: 00 hour(s) 1 minute(s) unless you are running an intensive routine.
  10. Click OK

Once back viewing the Scheduled Tasks folder you can right click on the Task and choose Run to see if it works as expected.

OK so that is great if we know what tasks we want to setup, but what if your system needs make its own Tasks on the fly or you prefer setting up Tasks via the command line. Obviously you need to be exceptionally careful when you allow a web accessible script to setup Scheduled Tasks just as you would with cron jobs.

You must be running your server on a Windows system with Server 2003, Server 2008 or XP Professional otherwise you will not have the required schtasks.exe executable. To find out if you have it run schtasks on the command line. It should return a list of Scheduled Tasks currently on the system and if you see this read on otherwise you will need to investigate the windows at utility (type at /? on the command line).

The best place to start is by running schtasks /? which will bring up the help manual, but for further information on creating a Scheduled Task you should also read schtasks /Create /?. As there are lots of confusing options there I will break it down for you with a brief example.

schtasks /Create /RU username /RP password /SC MINUTE /MO 5 /TN taskname /TR "C:\Program Files\wget\wget.exe http://www.webaddress.com/script.php --spider"

In the above statement we are creating a new Scheduled Task that is to run every five minutes and it executes WGet, which in turn calls our PHP script. /RU and /RP are the username and password (respectively) of the account that will be used to run the task and /SC is the definition of the iteration interval which can be from minutes to monthly. The /MO is the modifier which determines when it runs, in our example above its every 5 minutes, /TN is predictably the unique name of your Scheduled Task and /TR is the command to be run when the Scheduled Task is activated.

As another example if we wanted a script to be run every second month we would enter something like this:

schtasks /Create /RU username /RP password /SC MONTHLY /MO 2 /TN taskname /TR "C:\Program Files\wget\wget.exe http://www.webaddress.com/script.php --spider"

So that should give you a basic idea of the command line utility and adjusting the examples with the help of the manual available by typing schtasks /? and schtasks /Create /? should get you on your way.

If you are like me you are now wondering if I am recommending security via obscurity; absolutely not! The problem with the examples I have given above is that if someone were to guess the URL of your processing php script (http://www.webaddress.com/script.php in the example above) they could access it via their web browser. Now think of the ramifications if your script is meant to send out emails once a day, but instead your script gets hit by a bot and sends your users 1,000+ emails a day!

So to lock your scripts down a bit I recommend the following:

  1. Always check the IP address and user agent of anyone trying to access your script
  2. Add an HTTP header to our request so we can pass our processing script a secret key

These checks combined should halt any bots in their tracks. So let us take a look at how this would work in real life with the following example code. Firstly the call we would issue to Scheduled Tasks and WGet:

schtasks /Create /RU username /RP password /SC MINUTE /MO 2 /TN taskname /TR "C:\Program Files\wget\wget.exe --header=TASK_KEY:my-secret-key -U my-agent http://www.webaddress.com/script.php --spider"

–header=TASK_KEY:my-secret-key tells WGet to add an HTTP header called TASK_KEY which contains the value of “my-secret-key” to its request for the processing script. We can then compare the contents of this header with the copy of the secret key in our processing script – this will make more sense when I show you the PHP side of the coding. You will also notice that I have added -U my-agent, which changes the user agent that WGet identifies itself as – in this case to “my-agent”. Obviously you would want to change the key and user agent to make them harder for a bot maker (who could be reading this) to guess. I would use a SHA1 hash for your secret key just to make it even harder to guess – it is a lot harder to luck upon a94a8fe5ccb19ba61c4c0873d391e987982fbbd3, which is the word test hashed rather than simply the word test.

The PHP side of this is also very simple:

<?php
    if('my-agent' !== $_SERVER['HTTP_USER_AGENT'] or
       $_SERVER['SERVER_ADDR'] !== $_SERVER['REMOTE_ADDR'] or
       !isset($_SERVER['HTTP_TASK_KEY']) or
       empty($_SERVER['HTTP_TASK_KEY']) or
       'my-secret-key' !== $_SERVER['HTTP_TASK_KEY']) {
        print 'You do not have permission to execute this script!';
        exit();
    }
    print 'Executing operation.';
    /insert your processing script here
?>

So again you can see here that you need to change the values of “my-agent” and “my-secret-key” to your own private values. Now to break down the code by looking at the operations in the if statement.

The first line ‘my-agent’ !== $_SERVER[‘HTTP_USER_AGENT’] checks to see whether the value of the user agent matches the specified value “my-agent”.

The second line $_SERVER[‘SERVER_ADDR’] !== $_SERVER[‘REMOTE_ADDR’] checks that the server requesting the script be processed is the same as the one the script is being executed on. Basically this means that only requests coming from the same IP address as the server will be run.

The third, fourth and fifth lines:

<?php
!isset($_SERVER['HTTP_TASK_KEY']) or
 empty($_SERVER['HTTP_TASK_KEY']) or
 'my-secret-key' !== $_SERVER['HTTP_TASK_KEY'])

Check that the HTTP header has been set then if the header contains something and finally if the header contents match our secret key; “my-secret-key” in this case.

If any of the criteria are found to be missing then the script will display the message “You do not have permission to execute this script!” and halt otherwise the code placed after the end of the if will be executed. So in this example upon a successful run the script would print out “Executing operation”.

Before I go I shall also very briefly show you how to setup a Scheduled Task from a PHP script. This can be very useful if you have a very large list of emails to send out to and the PHP script times out whilst your Scheduled Task is executing. What you can do is split it up into job lots of say 500 emails at a time and schedule the send out as multiple Scheduled Tasks dynamically from PHP. Of course this same principle would apply to large dataset changes that timeout during their operation as well.

To dynamically schedule the task we simply send it to the system via the PHP exec() function. As I said earlier you must ensure that you are very careful of what you put in an exec() call and I would advise that no user supplied data is allowed lest they maliciously insert a command like format C:! Here is the basic PHP to work from:

<?php
exec('schtasks /Delete /TN taskname /F');
exec('schtasks /Create /RU username /RP password /SC MINUTE /MO 2 /TN taskname /TR “C:\Program Files\wget\wget.exe --header=TASK_KEY:my-secret-key –U my-agent http://www.webaddress.com/script.php -r');

The only new concept here is that I always make sure I delete any Scheduled Task that may already be in place with the same name before attempting to setup the new Scheduled Task.

To summarise we have now got a Scheduled Task up and running with some security to ensure only our server is able execute the processing script. You have learnt how to setup a new Scheduled Task either via the command line or the graphical user interface wizard or via the exec() function in PHP to dynamically setup new Scheduled Tasks on the fly. Experiment further and enjoy!