Application Security Testing: An Integral Part of DevOps
Getting Scripts to Run on a Schedule
by John Peterson
Let's assume for a minute that you've already built the perfect little online store for your business and you're even starting to get a couple of orders. Now let's say that instead of logging into a web page every day to check your sales, you want daily reports sent to you everyday at midnight (noon, 5pm, whatever...). How do you tell your site to do it? It's easy to get an email sent whenever an order is placed because you have a triggering mechanism in the user clicking the "Place Order" button, but how do you get a script to fire all on it's own? Well, like most things in life, there's more than one approach to this problem. In this article I'll walk you through the two most common ones, outline the benefits and drawbacks of each, and provide basic sample code that you can use to set up your own scheduled events.
Method 1: Faking It
Another use of this type of thing is for caching data. I do something similar to this approach on our news page so we only go out and get the headlines once a day instead of with every hit.
Without any further delay here's the code:
<html> <head> <title>Scheduling - Method 1</title> </head> <body> <p> This script will print out "Do Something!" at the first request and then at the first request after midnight every day. </p> <% ' The way it stands, the "Do Something" line will run the first ' time it's hit after the web server starts and then again every ' day at the first hit after midnight. If no one hits it for a ' day it won't run until hit. This can be troublesome when ' you're doing a calculation which expects a day to have elapsed. ' Along the same lines, to make it not run upon starting, ' uncomment this section: 'If Application("LastScheduledRun") = 0 Then ' ' Set the "LastScheduledRun" variable to something to ' ' satisfy the condition checked below. I'm setting it ' ' to the current time, hence making the computer think ' ' it just got done running it so there's no need to do ' ' so again. ' Application.Lock ' Application("LastScheduledRun") = Now() ' Application.UnLock 'End If ' Useful Debugging Lines: 'Response.Write Application("LastScheduledRun") & "<br />" & vbCrLf 'Response.Write Now() & "<br />" & vbCrLf 'Response.Write DateDiff("d", Application("LastScheduledRun"), Now()) _ ' & "<br />" & vbCrLf ' DateDiff works a little differently than you might expect. If ' you ask for the difference in days, it does not check to see ' how many days worth of time have elapsed between two dates, ' but simply takes the dates and subtracts them. Hence... from ' Jan 1, 2002, 11:59PM to Jan 2, 2002, 12:01AM returns 1 day even ' though in reality it's only been a couple seconds. This makes ' it ideal for triggering stuff at midnight like we're doing here. ' ' If days between "LastScheduledRun" and the current time is > 0, ' this must be the first hit after midnight so we run our daily ' routine. This condition is the key to what type of schedule is ' kept. It can be changed to hourly, every 10 minutes, or even ' the first hit after 5pm... whatever you want, but it won't ever ' run unless someone has hit the page containing it. If DateDiff("d", Application("LastScheduledRun"), Now()) > 0 Then ' This is where you put the commands you want to run on the ' schedule set up by the above condition. Response.Write "Do Something!" ' Reset our "LastScheduledRun" variable to the current date ' and time indicating we just did it. Application.Lock Application("LastScheduledRun") = Now() Application.UnLock End If %> </body> </html>
Method 2: True Scheduling
While this approach is generally the better approach in terms of true scheduling, it does require a little more work to set up. The first step is to write the script that does what you want to do. Unfortunately the script needs to be written as a WSH (Windows Scripting Host) script so it can run independently of the web server. Basically this entails removing calls to the ASP objects (Request, Response, Server, etc...) and saving the file with the appropriate extension (.vbs for VBScript or .js for JScript). Here's a simple sample of a script to send an email:DoSomething.vbs
Dim objCDO Set objCDO = CreateObject("CDO.Message") With objCDO .To = "Enter Your Email Address Here" .From = "Enter Your Email Address Here" .Subject = "Scheduling Test" .TextBody = "Scheduling Test Body" .Send End With Set objCDO = Nothing
Once you've written your script you can test it by double clicking it from Windows Explorer. If all works well you probably won't see anything happen, but whatever you told it to do should be done. In our case, the email should have been sent. At this point check to make sure it did whatever it was supposed to... there's no sense in scheduling to run a script that doesn't work.
Place the script somewhere outside of your web server's root so people can't request it and get the source code. Then go to the Windows Scheduled Tasks folder on your web server. On Win2000 you can get to this by going to the "Control Panel" and opening "Scheduled Tasks." You should see an item that says "Add Scheduled Task." If you double click it it should launch the "Scheduled Task Wizard." The first screen explains what the wizard does:
Figure 1: Scheduled Task Wizard - Intro Screen
Click "Next >" and you should see a dialog box to select the program to run. Select "Browse..." Then locate and select your script:
Figure 2: Scheduled Task Wizard - Select Program Screen
The next screen lets you assign this task a name. Pick something descriptive so you (and others) will know what it is later on. On this same screen you can select what type of schedule you want to set up:
Figure 3: Scheduled Task Wizard - Name and Schedule Type Screen
The next screen lets you pick the days and times at which the script should run.
Figure 4: Scheduled Task Wizard - Date / Time Select Screen
Next comes security... since the script can't run in the security context of the person who requests it, you need to give it a user to run as. It's often a good idea to set up a user specifically for this purpose and give it only the rights it needs to accomplish it's task.
Figure 5: Scheduled Task Wizard - User Select Screen
After a final summary screen, the task is scheduled and should run on the schedule you just set up. If you need to modify the task or access it's advanced properties, you can always open it by double clicking on it from the Scheduled Tasks folder.
Pros and Cons
While I've used both of these appoaches and they both work fine, each has its own benefits and drawbacks.
Method 1 Pros
- Easy to implement - no direct access to web server or scheduler needed.
- Keeps web site logic with the site.
- Only runs when called - can save processing if no one needs the info the script is getting or preparing.
Method 1 Cons
- Not very precise - things will run at the first request after the scheduled time. How quicly that is depends on traffic.
- Still needs a triggering mechanism - someone needs to request it.
- Can cause problems if you're using App vars and the server is reset - can be worked around with logic or storing timestamp on the file system, but both can slow things down.
Method 2 Pros
- Scripts run on the schedule you set.
- No need for a page to be requested.
Method 2 Cons
- Can be tricky to set up - Requires access to web server scheduler.
- Need to test scripts under WSH... it close, but it's not really ASP code.
- Scripts and logic get seperated from site and make for one more thing to deal with when the site need to be modified or moved to a new server.
Get the Code
As usual the code can be obtained in zip file format by clicking the zip file below. I've zipped the files preserving directories so be sure to extract them preserving directories using whatever zip tool you prefer. Method 1 should work "out of the box" so to speak as long as you place it into an appropriate web directory. Method 2 needs a little more setup as was discussed above.
When determining which method to use, I tend to look at what it is I'm trying to accomplish. If it has something to do with what the visitors to the site see or the content on a particular page, I'd probably go with Method 1. On the other hand, if it's behind the scenes stuff like activity reports or stuff not directly related to a specific page, Method 2 might be more appropriate. But regardless of those, perhaps the most important factor to consider is how important is it that it occur on schedule. If it needs to be done at a certain time regardless of whatever else is going on, Method 2 is probably the better choice if you can use it.
After I published this article, I immediately got a bunch of email all asking the same basic question: Why not just associate the ASP file with a browser and schedule it to run directly? At first glance, this appears to be a reasonable option and would let you schedule any existing ASP page without converting it to WSH. While it can be done, here's why it's not a great idea and an alternative that accomplishes the same thing without all the side effects. So let's call this...
Method 3 - The Problems
The first problem is that simply associating an ASP file with a browser doesn't get the job done. Try it and you'll see what I mean... actually don't... it'll mix up your associations if you've got an ASP editor installed and you'll have to go back and fix them by hand, but trust me it goes something like this:
- You associate ASP files with a browser
- You double-click one to test it (hopefully you did this and didn't schedule it directly!)
- The browser fires
- It loads the page
- But wait... it loaded it from the file system and not the web server
- No processing got done at all
That's problem one... luckily it's easily fixable. You don't have to associate ASP files with a browser. You can specify the URL to open as a parameter on the command line like this:
"C:\Program Files\Internet Explorer\IExplore.exe" http://localhost/ScheduleMe.asp
Note: I'm using IE to illustrate since that's what most of our visitors use, but to the best of my knowledge, most browsers should allow you to do this in some shape or form. If you're having problems, check the browser's documentation to try and find the appropriate syntax.
You'll need to go to the advanced properties to add the parameter after doing the basic scheduling, but it can be done.
Next problem... try and schedule something like the above and then log into the computer and launch Task Manager (I'm assuming it's a WinNT or Win2000 box). Check out that copy of IE still loaded in memory! You can also see this by going to the Task Scheduler and checking on the task's status. It'll probably still say "Running"! This is not a good thing and certainly not an efficient use of server resources. You can tell the task scheduler to automatically end the task after a given time period, but is all this really necessary? Along the same lines... if the desired effect is to do processing on the server side, do we really need to load all the code for IE's UI, Toolbars, Favorites, etc...?
All those shortcomings aside, you're still left with one more big one. What happens if IE can't resolve the server name, can't find the file (404), or just plain crashes? You've got no handle on the result and can't even do any error handling. Sure your ASP script might have every conceivable error handled, but if the browser never calls it, what good does that do you? Even worse, if you go back and check the task scheduler it'll probably tell you that the task ran fine since from it's point of view (did the browser launch), it probably did run fine. The only way you'll know differently is that whatever you scheduled it to do won't get done!
Method 3 - Done The Right Way
So how can we get the ease of use of being able to call an existing ASP page without the plethora of problems? I'm glad you asked because it's really quite simple... we build our own script-based browser. It sounds more complicated than it is... really... the code's been posted as a sample on our site for a long time - it's basically just a modified version of our http request sample.
The main modifications in the code listing below are the fact that I've added the framework for some error handling (I'm usually too lazy to do that in our samples) and have ported it over to WSH format so it'll run outside of IIS / ASP. You'll want to place this file outside of your web folders so that visitors can't request it directly. Then schedule it just like you did in Method 2. I've also included the stupid little testing script I wrote... it just writes to the included text file. It's an easy way to check to see that everything's working. Just make sure you set the NTFS permissions on the text file to allow writing if you use the script at all.HttpRequester.vbs
Option Explicit On Error Resume Next ' Declare our vars Dim objWinHttp, strURL ' Request URL from 1st Command Line Argument. This is ' a nice option so you can use the same file to ' schedule any number of differnet scripts just by ' changing the command line parameter. strURL = WScript.Arguments(0) ' Could also hard code if you want: 'strURL = "http://localhost/ScheduleMe.asp" ' For more WinHTTP v5.0 info, including where to get ' the component, see our HTTP sample: ' http://www.asp101.com/samples/winhttp5.asp Set objWinHttp = CreateObject("WinHttp.WinHttpRequest.5") objWinHttp.Open "GET", strURL objWinHttp.Send ' Get the Status and compare it to the expected 200 ' which is the code for a successful HTTP request: ' http://www.asp101.com/resources/httpcodes.asp If objWinHttp.Status <> 200 Then ' If it's not 200 we throw an error... we'll ' check for it and others later. Err.Raise 1, "HttpRequester", "Invalid HTTP Response Code" End If ' Since in this example I could really care less about ' what's returned, I never even check it, but in ' general checking for some expected text or some sort ' of status result from the ASP script would be a good ' idea. Use objWinHttp.ResponseText Set objWinHttp = Nothing If Err.Number <> 0 Then ' Something has gone wrong... do whatever is ' appropriate for your given situation... I'm ' emailing someone: Dim objMessage Set objMessage = Server.CreateObject("CDO.Message") objMessage.To = "Your Name <user@some domain.com>" objMessage.From = "Your Name <user@some domain.com>" objMessage.Subject = "An Error Has Occurred in a " _ & "Scheduled Task" objMessage.TextBody = "Error #: " & Err.Number & vbCrLf _ & "From: " & Err.Source & vbCrLf _ & "Desc: " & Err.Description & vbCrLf _ & "Time: " & Now() objMessage.Send Set objMessage = Nothing End If
As it stands, the script takes one command line parameter which is the complete URL you want to request. It then makes the request and if it gets an HTTP 200 response it assumes all is well. You might want to check the returned document for error messages, but I didn't go into this since it would all depend on what your ASP script was trying to do.
Setting this up is the same as method 2 except you schedule the HttpRequester.vbs file and then you'll need to go into the advanced properties and add the URL as a parameter. So the whole command would look something like this:
FYI: You can also just type this in from the command prompt to test it if you want.
You can get the files (WSH file above and the sample ASP and text files I was testing with) for Method 3 from here.
Method 3 - Pros and Cons
It's a little more complex, but if you can handle the scheduling with the parameters (or don't mind hard coding the URLs), Method 3 allows you to schedule your standard ASP script to run unmodified on whatever schedule you choose and even handle any errors that may occur.
Thanks go out to Tod DeBoice for being the first one to inquire about this approach and mentioning some of it's limitations at the same time. His email was the one most directly responsible for getting me off my butt to more fully research this approach and write this update.
Update: Yet Another Alternative
I've gotten a number of questions about how to schedule something if your site is being hosted with a hosting company. While Method 1 above will work in many situations, it does have its problems. So here's another alternative that recently came to my attention: http://www.cronservice.co.uk/. It's a site that offers a service that you can subscribe to that will let you schedule tasks. Here's the description from their site:
Welcometo CronService.co.uk, the simple script scheduling solution for ASP, .NET, Coldfusion, PHP or any programming language for that matter. If you have a script that you need to run at a set time each hour, day or month but don't have access to Cron or Task Scheduler then this is the perfect solution for you.
Anyway, I haven't used their service beyond the free trial so I can't really tell you how reliable it is, but, for those of you who need to schedule something, it's at least another alternative to consider.
Thanks to Steve Fleming at CronService for letting me know about their services.