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
The first approach I'm going to cover is basically an all ASP way to fake
scheduling. The basic premise is that if you store the last time something was
done, you can check that timestamp to see if it needs to be run again.
This can be implemented in a number of ways and in any file you want. For
site-wide reports like I mention above, placing it inside Session_OnStart
or on your home page often provides enough traffic for it to run pretty close
to the scheduled time, but please note that this is all based on the assumption
that wherever you place it, it will get called. If no one hits the page
it's on then it obviously won't run.
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:
Click "Next >" and you should see a dialog box to select the program to
run. Select "Browse..." Then locate and select your
script:
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:
The next screen lets you pick the days and times at which the script
should run.
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.
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
here.
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.
Conclusion
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.
Article Update:
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:
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 link's to the newest one, but we've had several
versions over the years).
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:
Welcome to 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.