Scripting in Windows Vista

Task Scheduler: Part 2

Windows Vista

This is the second part in what is now a two-part series (a series that might reproduce someday and become even more parts) on the new Task Scheduler API available in Windows Vista. If you’re a newcomer to the series, you’ll probably want to read Part 1 first.

*
**
**
On This Page
Task Scheduler Part 2Task Scheduler Part 2
Keeping Things OrganizedKeeping Things Organized
Create a FolderCreate a Folder
Delete a TaskDelete a Task
Delete a FolderDelete a Folder
Delete a Folder TreeDelete a Folder Tree
Gone ForeverGone Forever

Task Scheduler Part 2

Of all the many new features introduced in Windows Vista, one of the most exciting to scripters is the brand-new Task Scheduler. The really exciting part is the scripting API that comes with it. In case you missed it, we already told you all about this in Task Schedule, Part 1. Okay, obviously we didn’t tell you all about it or we wouldn’t have called it Part 1, assuming, of course, that we would eventually bring you more parts. What we did tell you about was how to get a list of all your scheduled tasks and how to schedule a daily task. There are several other types of tasks you can schedule, but before we show you any of that, in Part 2 (this article) we’re going to show you how to delete the tasks that you’ve already created. Oh, and before that (although still here in Part 2), we’ll show you how to create a folder. That can be important, as we’ll explain in a moment.

Top of pageTop of page

Keeping Things Organized

We’ll start out by admitting that staying organized isn’t necessarily something the Scripting Guys know a lot about. (For example, one of the Scripting Guys currently has 4,852 items in her Inbox.) So in this one case we’ll just ask you to go along with the old adage “do as we say, not as we do.” (There are many cases where you should go along with the “not as we do” part, but we’d never try to tell you to do any of those things. We’re not always sure why we do them ourselves.)

When you open Task Scheduler, by default you’ll have a list of folders, kind of like this:

Task Scheduler Default Folders

In the same way that the folders in Windows Explorer help you keep your files organized, Task Scheduler folders help you keep your tasks organized. (The same Scripting Guy with the overloaded Inbox also has over 600 items in her C:\Scripts folder.) In Part 1 we showed you how to retrieve a folder and view its contents; here in Part 2 we’re going to show you how to create a new folder.

Top of pageTop of page

Create a Folder

Creating a folder in Task Scheduler is an incredibly simple process. This is all there is to it:

Set objService = CreateObject("Schedule.Service")
objService.Connect

Set objFolder = objService.GetFolder("\")

Set objNewFolder = objFolder.CreateFolder("My Tasks")

Yes, that’s it; four short lines of code. The first thing we do is connect to the Task Scheduler service. We do that by creating a reference to the Schedule.Service class, which just happens to be the Task Scheduler service class, and then calling the Connect method to connect:

Set objService = CreateObject("Schedule.Service")
objService.Connect

We then use our Task Scheduler service (objService) to retrieve the root folder in our hierarchy, represented by a single backslash (\). We do that by calling the GetFolder method:

Set objFolder = objService.GetFolder("\")

If we wanted to create the new folder somewhere other than at the root, we’d simply put in the full path to the folder we want to create the subfolder in:

Set objFolder = objService.GetFolder("\Personal Tasks")

Finally, we call the CreateFolder method on the folder we just retrieved, passing in the name we want to give to the new folder:

Set objNewFolder = objFolder.CreateFolder("My Tasks")

That’s it. Here’s our new folder (you might have to refresh Task Scheduler before you’ll be able to see the new folder):

Task Scheduler New Folder

Top of pageTop of page

Delete a Task

Before we can delete a task we need to actually have a task to delete. We’re just going to run our handy script from Part 1 to create a new daily task in our new folder. On the chance that you’re as lazy as we are, here it is again; that way you can copy it and follow along without having to actually click a link and go back to Part 1:

Const TASK_TRIGGER_DAILY = 2
Const TASK_ACTION_EXEC = 0 
Const TASK_CREATE = 2

Set objService = CreateObject("Schedule.Service")
objService.Connect

Set objFolder = objService.GetFolder("\My Tasks")

Set objTaskDefinition = objService.NewTask(0)

Set colTasks = objTaskDefinition.Triggers

Set objTrigger = colTasks.Create(TASK_TRIGGER_DAILY)

objTrigger.DaysInterval = 1
objTrigger.StartBoundary = "2006-06-27T08:00:00-00:00"


Set colActions = objTaskDefinition.Actions

Set objAction = colActions.Create(TASK_ACTION_EXEC)
objAction.ID = "Daily Task Test"
objAction.Path = "C:\Windows\System32\sdclt.exe"

Set objInfo = objTaskDefinition.RegistrationInfo

objInfo.Author = "Administrator"
objInfo.Description = "Test task that displays Windows Backup Status and Configuration tool daily."

Set objSettings = objTaskDefinition.Settings
objSettings.Enabled = True
objSettings.Hidden = False

objFolder.RegisterTaskDefinition "Test Daily Trigger", objTaskDefinition, TASK_CREATE,  , , 0

We’re obviously not going to explain this here; we already did that. And, believe us, the Scripting Guys are way too lazy to explain something more than once.

Now that we’ve created this task, let’s delete it. Here’s how we do that:

Set objService = CreateObject("Schedule.Service")
objService.Connect

Set objFolder = objService.GetFolder("\My Tasks")

objFolder.DeleteTask "Test Daily Trigger",0

This one we will explain, but only because we haven’t done that already. Although, once you start looking at the script, we really have explained most of it already, haven’t we? (Wow, this job just gets easier all the time.) As you can see, the first three lines of our four-line script are almost identical to the first three lines of the four-line script that created a folder. All we do is create a Schedule.Service object, connect to the service, and then get a reference to the folder (in this case our new My Tasks folder).

Let’s see, we have a reference to the folder that contains the task we want to delete… if you were to take a guess as to how to delete a task from that folder, what would it be? If you guessed “call a method (on the folder object) named DeleteTask” you were exactly right:

objFolder.DeleteTask "Test Daily Trigger",0

We pass the DeleteTask method two parameters. The first is the name of the task we want to delete. The second parameter is a 0. That second parameter doesn’t actually do much, it just has to be there and it has to be a 0. (You probably didn’t guess that part.)

Top of pageTop of page

Delete a Folder

Okay, so we decided we didn’t want the task we created after all, so we deleted it. And since that happened to be the only task in that folder, we’ve decided we don’t want the folder anymore, either. Here’s a script that will get rid of the folder:

Set objService = CreateObject("Schedule.Service")
objService.Connect

Set objFolder = objService.GetFolder("\")

objFolder.DeleteFolder "My Tasks", 0

Yes, there are those same first three lines again. Because the folder we’re deleting is a subfolder of the root (\), we ask for the root in our call to GetFolder:

Set objFolder = objService.GetFolder("\")

We now delete the folder in almost the exact same way we deleted a task. The only difference? We call DeleteFolder rather than DeleteTask:

objFolder.DeleteFolder "My Tasks", 0

We pass two parameters to DeleteFolder: The name of the folder we want to delete, and our good friend the 0. (My Hero, Zero).

One thing to note is that you won’t be able to delete the folder under regular Windows Vista administrator privileges; you’ll need elevated privileges for this one to work.

Note: What does it mean to have elevated privileges? It means that you need to run this script from the command prompt as an administrator. How do you do that? Simple enough: From the Start menu, select All Programs, then select Accessories. Right-click on Command Prompt and select Run As Administrator. You’ll be able to run your script from that window.

We mentioned that we deleted this folder because it was empty. You might be wondering whether we could have simply deleted the folder and saved ourselves the trouble of having to delete the task first. Well, the answer is “No.” You can’t delete a folder that has tasks in it. You also can’t delete a folder that contains subfolders, even if there are no tasks in the folder or the subfolders. In order to delete a folder that contains anything, you have to do a recursive sort of thing.

But don’t worry; we’ll show you the recursive sort of thing.

Top of pageTop of page

Delete a Folder Tree

The last thing we’re going to do is show you how to delete an entire tree; that is, given a starting folder, we will recursively delete all the tasks in that folder and all its subfolders, then delete all the subfolders and the folder itself. Notice we said “recursively.” That means we need to use something called recursion. We’re going to explain the whole script, but if you’re not familiar with recursion you’ll probably want to check out the discussion in the Windows 2000 Scripting Guide.

As you might expect, this is going to be a little longer than the four-line scripts we’ve been looking at. We’ll show you the whole thing, but don’t worry if it looks a little long and complicated; we’re going to break it down and explain everything. Here we go:

Set objService = CreateObject("Schedule.Service")
objService.Connect

Set objFolder = objService.GetFolder("\My Tasks")
Set colFolders = objFolder.GetFolders(0)

For Each objSubFolder in colFolders

    FindFolders objSubFolder
    RemoveAllTasks objSubFolder
    objFolder.DeleteFolder objSubFolder.Name, 0

Next

RemoveAllTasks objFolder

strPath = objFolder.Path
i = InStrRev(strPath, "\")

If i = 1 Then
    strParent = Left(strPath, i)
Else
    strParent = Left(strPath, i - 1)
End If

Set objParentFolder = objService.GetFolder(strParent)
objParentFolder.DeleteFolder objFolder.Name, 0


Sub FindFolders(objFolder)

    Set colFolders = objFolder.GetFolders(0)

    For Each objF in colFolders

        FindFolders objF
        RemoveAllTasks objF
        objFolder.DeleteFolder objF.Name, 0

    Next

End Sub

Sub RemoveAllTasks(objF)

    Set colTasks = objF.GetTasks(0)

    For Each objTask in colTasks
        objF.DeleteTask objTask.Name, 0
    Next

End Sub

We start out the same way we start all our Task Scheduler scripts, by connecting to the Task Scheduler service:

Set objService = CreateObject("Schedule.Service")
objService.Connect

Nothing new there. Next we retrieve a reference to the folder we want to delete; in this case, that’s the My Tasks folder:

Set objFolder = objService.GetFolder("\My Tasks")

Now we’re going to take a look at a method we haven’t seen yet. We’ve shown you how to get one individual folder; now we’re going to find out how to get all the subfolders of that folder. We do that with the GetFolders method:

Set colFolders = objFolder.GetFolders(0)

As you’ve probably figured out, the GetFolders method returns a collection of all the subfolders (but not their subfolders – we’re only looking at one level at a time) of the objFolder object (which right now represents the My Tasks folder). So now that we have a collection of all the subfolders under My Tasks what do we do with it? Well, as we mentioned ,we need to delete all the tasks from a folder before we can delete the folder. But we also need to delete all the subfolders before we can delete the folder. So what we need to do is – using a For Each loop – loop through the collection of subfolders:

For Each objSubFolder in colFolders

    FindFolders objSubFolder
    RemoveAllTasks objSubFolder
    objFolder.DeleteFolder objSubFolder.Name, 0

Next

Inside this loop the first thing we do is call a subroutine we named FindFolders, passing it the reference to the first subfolder in the collection. What does FindFolders do? Well, let’s take a look:

Sub FindFolders(objFolder)

    Set colFolders = objFolder.GetFolders(0)

    For Each objF in colFolders

        FindFolders objF
        RemoveAllTasks objF
        objFolder.DeleteFolder objF.Name, 0

    Next

End Sub

The first thing we do inside this subroutine is to once again call the GetFolders function, this time returning a collection of all the subfolders in the subfolder we passed to FindFolders. For example, if the first folder under My Tasks was named SubFolder1, this call to GetFolders would retrieve all the subfolders under SubFolder1. We then set up another For Each loop to loop through this collection of subfolders.

Here’s where things might get a little confusing. (Or a little more confusing, as the case may be.) The first thing we do inside the For Each loop is to call the FindFolders subroutine, passing it the first subfolder in this collection. But wait a minute, we’re already inside the FindFolders subroutine – what happens when we call a subroutine when we’re already inside that subroutine? Well, this is what recursion is all about, and this is how we work our way down the folder tree. When we call FindFolders here, we go back to the top of FindFolders, this time calling GetFolders to retrieve the collection of subfolders under the subfolder. When we loop through that collection we call FindFolders again, and so on until we run out of subfolders.

Now, what happens when we do run out of subfolders? Eventually we’ll retrieve a folder and our call to GetFolders won’t return anything in the collection because that particular folder doesn’t have any subfolders. That means that, when we start the For Each loop, we’ll never actually go into the loop. The For Each loop is performing the code within the loop once for each item in the collection; if there are no items in the collection then nothing in the loop is executed. Instead we simply bypass the loop and get to the end of the FindFolders subroutine.

Fine, we’re at the end of the FindFolders subroutine; what happens now? Okay, pay close attention (not that you weren’t already, of course); this is where you can really get lost. Let’s assume our My Tasks tree looks like this:

Folder Tree

We started with My Tasks and retrieved a collection of its subfolders: SubFolder 1 and SubFolder2. We called FindFolders, passing it SubFolder1. We then retrieved a collection of all the subfolders of SubFolder1: SubFolderA. We called FindFolders again, passing it SubFolderA. SubFolderA doesn’t have any subfolders, so we skipped the For Each loop and reached the end of the FindFolders subroutine.

Whew! Now where does our script go?

Well, we just got to the end of the FindFolders subroutine, so now we go to the line of code following our last call to FindFolders. If you recall, the last time we called FindFolders we passed it SubFolderA, which was inside the For Each loop, which was inside the FindFolders subroutine:

For Each objF in colFolders

        FindFolders objF
        RemoveAllTasks objF
        objFolder.DeleteFolder objF.Name, 0

    Next

At this point, objF references SubFolderA. In the line of code following the call to FindFolders we call a subroutine named RemoveAllTasks, passing it a reference to the folder we’re currently looking at (SubFolderA). And what does RemoveAllTasks do? Exactly what it sounds like it does, it removes all the tasks within a folder:

Sub RemoveAllTasks(objF)

    Set colTasks = objF.GetTasks(0)

    For Each objTask in colTasks
        objF.DeleteTask objTask.Name, 0
    Next

End Sub

Note: In order for recursion to work, the recursive code must be in a subroutine (in our case the FindFolders subroutine). The code we put within the RemoveAllTasks subroutine does not need to be in a subroutine. But as you’ll see, we use this exact same section of code in several places within this script. When you do that, it’s often easier to place the code in a subroutine and call the subroutine than it is to repeat the code over and over again throughout the script.

For more information on creating subroutines and functions, see these articles:

Functions, Subroutines, and Beyond

Bring in Da Subs, Bring in Da Funcs

Within the RemoveAllTasks subroutine the first thing we do is call the GetTasks function to retrieve a collection of all the tasks within the current folder (the folder we passed to the subroutine). We then loop through that collection and call DeleteTask on each task, using the Name property of the task to tell DeleteTask which task we’re deleting. (And of course we’d never forget the 0.)

Once we’ve deleted all the tasks in SubFolderA we go back to where we called RemoveAllTasks from:

RemoveAllTasks objF
        objFolder.DeleteFolder objF.Name, 0

We’ve removed all the tasks from SubFolderA, so now it’s time to delete the folder. We know that objF right now contains a reference to SubFolderA, so we pass the DeleteFolder method the Name of SubFolderA. Notice that we call the DeleteFolder method on the objFolder object. What’s in objFolder? Well, you delete a subfolder from the parent folder. Remember, we’re still in the For Each loop that loops through a collection of subfolders. SubFolderA is in the collection of subfolders for SubFolder1, which means that objFolder is referencing SubFolder1. In other words, we’re calling DeleteFolder on the SubFolder1 object in order to delete SubFolderA.

Got all that? We hope so, because it’s not over yet. Hang in there.

We’ve deleted all the tasks in SubFolderA, and now we’ve deleted SubFolderA itself. That means that we have once more reached the end of the FindFolders subroutine. If we work our way backwards, we realize that we made this call to FindFolders from within the For Each loop in the main body of our code:

For Each objSubFolder in colFolders

    FindFolders objSubFolder
    RemoveAllTasks objSubFolder
    objFolder.DeleteFolder objSubFolder.Name, 0

Next

We had just started looping through the collection of folders we found in the My Tasks folder, and the first folder in that collection was SubFolder1. As you can see, we do exactly the same thing here that we did inside our FindFolders subroutine: We call RemoveAllTasks, this time removing the tasks from SubFolder1, then we call DeleteFolder to delete SubFolder1.

At this point we loop around and do exactly the same thing with the next folder in the My Tasks subfolder collection, SubFolder2. We call FindFolders to look for any subfolders. In this case there aren’t any, so we simply fall out of the FindFolders subroutine, delete all the tasks from SubFolder2, then delete SubFolder2.

Okay, we’re almost done, just a little farther.

Once we’ve looped through the entire collection of subfolders under My Tasks and removed all those subfolders (and their tasks), we need to remove the tasks from the My Tasks folder itself. That’s why, as soon as we’re out of the For Each loop, we make one more call to RemoveAllTasks, passing it a reference to the My Tasks folder:

RemoveAllTasks objFolder

The last thing we need to do is remove the My Tasks folder. Remember that in order to remove a folder you need to call DeleteFolder from that folder’s parent. We happen to know that the parent of My Tasks is the Task Scheduler root, represented by a backslash (\). So we could have simply retrieved the root folder and called DeleteFolder to delete the My Tasks folder like this:

Set objParentFolder = objService.GetFolder("\")
objParentFolder.DeleteFolder "My Tasks", 0

At this point we’d be done. However, we wanted to make it easy to modify this script and put in any folder you want to delete, not just folders at the root level. To that end, we added a little bit of code to make that easier. Let’s take a look:

strPath = objFolder.Path
i = InStrRev(strPath, "\")

If i = 1 Then
    strParent = Left(strPath, i)
Else
    strParent = Left(strPath, i - 1)
End If

The objFolder variable holds a reference to the folder we want to delete (in this case My Tasks). The first thing we do is use the Path property to get the full path to our folder. This will return a path like this:

\My Tasks

If we wanted to delete SubFolder1 our path would look like this:

\My Tasks\SubFolder1

To find the parent of the folder, we need to go up one level in the path. We do that by looking for the last backslash in the path:

i = InStrRev(strPath, "\")

The InStrRev function returns the position of one string within another, starting from the right side of the string. We pass the folder path (strPath) as the search string, and the backslash as the string to search for. That means that in the path \My Tasks, the backslash will be found at position 1, which gives i the value 1. If our path were \My Tasks\SubFolder1, the first backslash from the right would be at position 10, so i would be equal to 10 (\My Tasks\SubFolder1). That brings us to this If statement:

If i = 1 Then
    strParent = Left(strPath, i)
Else
    strParent = Left(strPath, i - 1)
End If

Just for a moment, let’s go back towards the beginning of our script and look at our call to GetFolder that retrieves the My Tasks folder:

Set objFolder = objService.GetFolder("\My Tasks")

Notice that the path we supply does not end in a backslash. However, if we wanted to retrieve the Task Scheduler root folder, we need to supply a backslash:

Set objFolder = objService.GetFolder("\")

That’s why we have the If statement. If the first backslash we encounter (reading from the right) is at position 1, that means the folder we’re deleting is directly under the root. In that case the backslash is the first character in the path, or the left-most 1 character, which we retrieve by calling the Left function:

strParent = Left(strPath, i)

In this case strParent will be equal to “\”. But let’s see what would happen if our parent isn’t the root. Going back to our \My Tasks\SubFolder1 example, the first backslash is at position 10. If we were to use the same parameters with the Left function, reading the first 10 character from the left, strParent would be equal to this:

\My Tasks\

Notice we still have the backslash on the end. In this case we don’t want the backslash, so we actually need to look for the left-most i - 1 characters (or, in this case, the first 9 characters in the string):

\My Tasks

Now that we have the path to the parent folder stored in strParent, we simply retrieve that folder, then call DeleteFolder on our target folder:

Set objParentFolder = objService.GetFolder(strParent)
objParentFolder.DeleteFolder objFolder.Name, 0

Yes, finally, that’s it.

Top of pageTop of page

Gone Forever

At long last, all your tasks and folders under a particular folder are gone, never to return.

Did you think we meant the Scripting Guys were gone forever? No such luck. (Not yet anyway.) The Task Scheduler articles aren’t gone forever either, there’s plenty more where this came from. Not anywhere nearby, mind you, but there is plenty more. And we’ll get right on that, just as soon as we go through and clean out our Inbox. See you in Part 3!


Top of pageTop of page