Hey, Scripting Guy! How Can I Best Work with Task Scheduler?

Hey, Scripting Guy! How Can I Best Work with Task Scheduler?

  • Comments 3
  • Likes

Hey, Scripting Guy! Question

Hey, Scripting Guy! We are really looking at reducing our IT costs as a group and as a result are seeking out anything that is already built into the operating system that will allow us to automate things. Our company recently had some cutbacks and we simply do not have enough people to be able to do anything more than operate in fire-fighting mode. Task Scheduler in Windows Vista and on Windows Server 2008 looks really powerful, and I have been experimenting with it, but I will need a way to automate it if it is to be of much value. After all, we have thousands of servers and workstations. I looked at the WMI script you wrote yesterday, but I do not see half of the things that I can do with the graphical version of the Task Scheduler.

- KS

SpacerHey, Scripting Guy! Answer

Hi KS,

Don't worry, be happy. Or as former president of the United States Bill Clinton once said, "I feel your pain." We were captivated by your question. So much so, in fact, that if you have been following us on Twitter, you would have seen that we have been working on today's article for more than three days. (There are actually several reasons for this, not the least of which was Ed’s hand surgery. Not a problem because Windows 7 with Office 2007 has awesome voice recognition capabilities. In fact we were writing this article using voice recognition until we came down with tonsillitis! Needless to say, it has been a rough week.) Much of the time has been spent poring over the Task Scheduler API documentation on MSDN. Then we got engrossed with playing around with it, and started coming up with way more ideas than we could possibly do in just a couple of days worth of articles. We will probably write a magazine article for TechNet Magazine, so we can go into it much more deeply. Let us tell you one thing now that is so cool. We get questions all the time from people who want to run a script when the computer is idle. Guess what? We always say you cannot do that. We lied inadvertantly. You can, using the Task Scheduler in Windows Vista and later! We tested it out, and it really works! This is just one really cool thing you can do.

This week we are talking about scheduled tasks. There are at least three different ways that you can work with scheduled tasks. This “Hey, Scripting Guy!” article provides an example of working with scheduled tasks via WMI. This script lists scheduled tasks created via WMI. We also have a variety of scheduled task scripts from the Community-Submitted Scripts Center. The Schedule.Service API is discussed in two articles. The first article talks about creating a task and setting the trigger. The second article discusses organizing tasks. This article discusses executing scheduled tasks via the Schedule.Service API. And the Task Scheduler is documented on MSDN.

KS, we decided to write a script called ScheduledTaskFolders.ps1. This script does nothing by default, but it is really powerful. We have four separate command lines, each of which is commented out at the bottom of the script. It will list task folders, do a recursive listing of task folders, create a new task folder, or delete a task folder. So this is actually four different scripts in one. It can probably do even more things than that, and in fact as you will see tomorrow, it can. Without further ado, here is the ScheduledTaskFolders.ps1 script.

Function Get-ScheduleService
{
  New-Object -ComObject schedule.service
} #end Get-ScheduleService

Function New-TaskObject($path)
{ #returns a taskfolder object
 $taskObject = Get-ScheduleService
 $taskObject.Connect()
 if(-not $path) { $path = "\" }
 $taskObject.GetFolder($path)
} #end New-TaskObject

Function Get-TaskFolder($folder,[switch]$recurse)
{ #returns a string representing the path to a task folder
  if($recurse)
    {
     $colFolders = $folder.GetFolders(0)
      foreach($i in $colFolders)
        {
          $i.path
          $subFolder = (New-taskObject -path $i.path)
           Get-taskFolder -folder $subFolder -recurse
        }
    } #end if
  ELSE
    {
     $folder.GetFolders(0) |
      foreach-object { $_.path }
     } #end else
} #end Get-TaskFolder

Function New-TaskFolder($folder,$path)
{ 
 $folder.createFolder($path)
} #end New-TaskFolder

Function Remove-TaskFolder($folder,$path)
{
 $folder.DeleteFolder($path,$null)
} #end Remove-TaskFolder


# *** Entry point to the script ***

# Get-TaskFolder -folder (New-taskObject -path "\microsoft")
# Get-TaskFolder -folder (New-taskObject -path "\microsoft") -recurse 
# New-TaskFolder -folder (New-taskObject) -path "\mred"
# Remove-TaskFolder -folder (New-taskObject) -path "\mred"

We first need to create the Get-ScheduleService function. To do this we will use the Function keyword and give it the name of Get-ScheduleService. We open a pair of curly brackets and use the New-Object cmdlet with the -ComObject parameter to allow us to create the Schedule.Service object. We then close the curly brackets. The Schedule.Service object is the main entry point for the Task Scheduler API. We will need to use this object for anything that we want to do with the scheduled tasks. This code is seen here:

Function Get-ScheduleService
{
  New-Object -ComObject schedule.service
} #end Get-ScheduleService

Next we come to the New-TaskObject function. It returns a TaskFolder object. We use a TaskFolder object to allow us to work with folders in scheduled tasks. Scheduled task folders can be a useful way to allow you to organize your scheduled tasks. For example, if you had a number of maintenance-related activities that you would like to run on a scheduled basis, you could create a folder called “Maintenance Tasks.” While this is not required, it certainly makes things easier to find. To create the New-TaskObject function, we use the Function key word, assign a name, and create a variable to hold the path to the TaskFolder object that is returned. We then open a set of curly brackets. Inside the curly brackets, we call the Get-ScheduleService function and assign the object that is returned by that function to the $taskObject variable. We then use the $taskObject variable, which contains an instance of the schedule service object, and we call the connect method. This code is shown here:

Function New-TaskObject($path)
{ 
$taskObject = Get-ScheduleService
$taskObject.Connect()

Now we need to determine if a path was supplied to the function. If a path was not supplied to the function, we supply “\” to the $path variable. The “\” value is used to represent the root of the folder. We use the -not operator to see if a value was indeed supplied. After we have a value for the $path variable, we then use the GetFolder method from the TaskObject. This is seen here:

if(-not $path) { $path = "\" }
$taskObject.GetFolder($path)
} #end New-TaskObject

It is time to create the Get-TaskFolder function. It returns a string representing the path to a task folder. The Get-TaskFolder function will accept two parameters. The first parameter is the folder parameter and it needs a folder object. The second parameter is the recurse parameter and it is switched. When the function is run with the recurse parameter, it will list the path to all of the folders. The Function keyword function name and parameters are seen  here:

Function Get-TaskFolder($folder,[switch]$recurse)
{

Because there can be several nested folders in our task schedule hierarchy, we need the ability to recurse. A typical task folder hierarchy is shown here:

Image of a typical task hierarchy

 

If the function is called with the recurse parameter, we use the GetFolders method from the folder object. We store the returned object in the $colfolders variable:

if($recurse)
    {
     $colFolders = $folder.GetFolders(0)

Because the GetFolders method can return a collection of folders, we need to be able to walk through these folders. In order to do this, we use the ForEach statement. Inside the ForEach statement, we use the $i variable as the enumerator, which allows us to work with an individual folder from within the collection. We then return the path from the folder. This is seen here:

ForEach($i in $colFolders)
        {
          $i.path

Now we get to the tricky part. Because it is possible that there is more than one folder and more than one subfolder, we will need the ability to recurse. To do this we first create a NewFolder object that is based upon the path from the folder that is stored in the $i variable. We do this by calling the New-taskObject function and storing the results and the $subfolder variable. We can call the Get–taskFolder function and pass the $subfolder variable to it with the -recurse parameter. This is seenhere:

$subFolder = (New-taskObject -path $i.path)
           Get-taskFolder -folder $subFolder -recurse
        }
    } #end if

If the function is not run with the –recurse parameter, we need to take alternate action. To take alternate action, we use the ELSE statement. Inside the curly brackets, we first call the GetFolders method from the $folder variable. We pipeline the results to the ForEach-Object cmdlet and inside the curly brackets we return the path:

ELSE
    {
     $folder.GetFolders(0) |
      ForEach-Object { $_.path }
     } #end else
} #end Get-TaskFolder

We now need to create the New-TaskFolder function. After using the Function keyword and assigning a name, we define two input parameters. The first contains a folder object and the second contains the path to the new folder. Inside the curly brackets we call the createFolder method from the $folder variable and pass it the path. We then close the curly brackets and the function is done. This is shown here:

Function New-TaskFolder($folder,$path)
{
 $folder.createFolder($path)
} #end New-TaskFolder

When a new task folder is created, it will appear as seen here:

Image of a new task folder

 

It is time to create the Remove-TaskFolder function. We once again use the Function keyword to assign a name to the function and define our input parameters. This function also uses the $folder variable and the $path variable as input parameters. Inside the curly brackets of the function, we call the DeleteFolder method from the folder object and pass it the path to the folder that will be removed. This is seen here:

Function Remove-TaskFolder($folder,$path)
{
$folder.DeleteFolder($path,$null)
} #end Remove-TaskFolder

We have listed four possible command lines you may wish to use in the entry point to the script section. The first one lists task folders in a specific location. The second one does the same thing but in a recursive fashion. The third command line creates a new task folder, and the last one removes it. These four command lines are seen here:

# Get-TaskFolder -folder (New-taskObject -path "\microsoft")
# Get-TaskFolder -folder (New-taskObject -path "\microsoft") -recurse 
# New-TaskFolder -folder (New-taskObject) -path "\mred"
# Remove-TaskFolder -folder (New-taskObject) -path "\mred"

You may be wondering why we wrote this script in this manner. If you look at the syntax of the above commands, you will see that they kind of look like Windows PowerShell commands. This was done intentionally. Because there is no directly executable line of code in the script, you may wonder how it executes. If you wish, you can simply uncomment the line of code you wish to use and run it that way. Or you could dot source the script into your Windows PowerShell console and use the functions as you would any cmdlet. When Windows PowerShell 2.0 is released, you could turn the script into a module. We will talk about this when Windows PowerShell 2.0 is shipped.

Well, KS, I hope you enjoyed today's script and are as excited as I am about the many possibilities for the new Task Scheduler. Join us tomorrow when we create scheduled tasks with the Task Scheduler 2.0 API (that is what we were looking at today by the way). Until tomorrow be careful, and watch out for splinters.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Nice, but what I really need is to interact with the scheduler on a remote server where I can pass credentials.  I have tried numerous things but have found any way to make this work.

  • Same here.  I've been looking for a way to schedule a script that uses new-pssession without success.

  • Look at the Connect method of the COM Object:  msdn.microsoft.com/.../aa383451(v=vs.85).aspx

    You can pass host, username, password, and domain