Running programs on remote computers is a common administrative task, and there are a number of techniques available to accomplish it. (See the article "Common Ways to Run Programs on Remote Computers" for a list of some commonly used techniques and their advantages and disadvantages.) I've come to rely on the Windows Task Scheduler service because it's a native part of Windows OSs and hence always available under normal circumstances.
In 2005, I wrote a VBScript script, JTRun.vbs, that used the Task Scheduler service to immediately run programs on Windows XP and Windows 2000 (Win2K) remote computers. As I described in the web-exclusive article "Command-Line Task Scheduler," I used the Microsoft utility Jt.exe instead of the Schtasks command (which was introduced in XP) as the command-line interface for the Task Scheduler service for two reasons. First, Jt.exe worked on both XP and Win2K, whereas Schtasks didn't exist on Win2K. Second, Jt.exe had a "run now" feature that Schtasks lacked. (Jt.exe lets you run programs "now," meaning in the next minute.)
Now that I use Windows PowerShell as my primary command-line shell, I decided to replace JTRun.vbs with a PowerShell script for several reasons:
- JTRun.vbs requires an administrative password in clear text on the script's command line. PowerShell can use PSCredential objects, which securely store passwords.
- JTRun.vbs works with only one computer at a time. PowerShell pipelining makes working with multiple computers relatively easy.
- JTRun.vbs produces output that isn't designed for parsing. In PowerShell, you can output custom objects with whatever properties are needed.
- Jt.exe's "run now" feature can be problematic if the local and remote computers' times aren't in sync, or if the local and remote computers are in different time zones.
After I decided to replace JTRun.vbs with a PowerShell script, my first thought was to use the TaskService scripting object (which was introduced in Windows Vista and Windows Server 2008) or the Schtasks command instead of Jt.exe. However, I was annoyed to discover that, at least from Windows 7, you can't connect to remote XP systems using either the TaskService or Schtasks method. As Figure 1 shows, both methods return an Access is denied error. (The PowerShell window in Figure 1 is running under the Administrator account on the local system, and the same domain account is a member of Administrators on the remote XP system as well.) However, I found that Jt.exe works just fine on Windows 7, so I decided to stick with it.
Although the new PowerShell script, Start-Program.ps1, still uses Jt.exe to create the scheduled task, it doesn't have the old VBScript script's other problems. Before I show you how to use the new PowerShell script, however, I need to describe the Jt.exe program and how Start-Program.ps1 uses it to become a general-purpose "run a program on any computer" tool.
As I mentioned previously, Jt.exe provides a command-line interface to the Task Scheduler service. You can download this utility from ftp://ftp.microsoft.com/reskit/win2000/jt.zip. After you extract the Jt.exe file, you need to put it in a directory in your Path. You only need Jt.exe in the Path on the computer on which you run Start-Program.ps1.
Although the Jt.exe command is powerful and flexible, its syntax is rather complex and not very user-friendly. For that reason, I designed Start-Program.ps1 to create and run a Jt.exe command that contains the parameters shown in Table 1.
Jt.exe generates output strings that are informative. For example, Figure 2 shows some sample output strings, one of which contains an error message. However, the output strings make parsing difficult, especially if you need to execute a Jt.exe command for more than one computer. Start-Program.ps1 parses the output strings and manages Jt.exe errors for you, reporting errors when appropriate.
Using Start-Program.ps1 to run Jt.exe offers other several advantages as well. First, you don't need to type passwords in clear text on the command line. Second, Start-Program.ps1 hides the complexities of the Jt.exe command-line parameters shown in Table 1. Third, Start-Program.ps1 can run a program on multiple computers. Finally, it outputs objects that can be filtered, sorted, and so on.
You can download Start-Program.ps1 by clicking the 141270.zip hotlink at the top of this page. After you extract the script, you should put it in the same Path as Jt.exe. (Start-Program.ps1 and Jt.exe can be in the same or different directories in the Path.) The script's command-line syntax is as follows:
The -ComputerName parameter specifies the computers on which to run the program. You can omit the -ComputerName parameter name if you put the computer names first on the command line. You can specify a period (.) or the string localhost to refer to the current computer. This parameter also supports pipeline input (i.e., if you pipe input to the script, the script assumes the piped input is a list of computer names).
The -StartTime parameter is a DateTime object that specifies when the program should run. You can specify a date, a time, or both. If you specify only a date, the time will be midnight. If you specify only a time, the date will be the current day. Because the parameter is a DateTime object, you can specify the date and time in any valid format. Note that for remote computers, this parameter refers to the date and time on the remote computer, not the computer on which you run the script.
You use the -Program parameter to specify the program you want to run. You can also specify a shell script (i.e., a .cmd or .bat file). This parameter can include a path to a network location if the credentials you specify have the appropriate permissions. You shouldn't include the program's command-line arguments in the -Program parameter. If the program needs command-line arguments, you need to use the -Parameters parameter.
The -Parameters parameter is a string that specifies the command-line arguments for the program. The parameter needs to be enclosed in double quotes (") when it contains embedded spaces. However, the string must not contain embedded double quotes due to a limitation in Jt.exe. (Jt.exe uses double quotes for strings and doesn't provide a means to embed double quotes inside a quoted string.) If you need to run a program that uses embedded double quotes in its command line, you need to put the command line in a shell script and use Start-Program.ps1 to run the shell script.
If you want to make sure the program starts in a specific location, you must include the -WorkingDirectory parameter. Otherwise, the OS will decide the program's working directory. Note that if you're running a program on a remote computer, the working directory is relative to the remote computer, not the current computer.
If you specify the -KeepTask parameter, the scheduled task won't be marked to delete itself after it's scheduled to start. This parameter can be useful for troubleshooting.
The -TaskCredential parameter specifies a PSCredential object. This set of credentials will be used to start the scheduled task. If you don't specify this parameter, Start-Program.ps1 will prompt you for credentials. Due to the aforementioned limitation in Jt.exe, the username and password can't contain embedded double quotes.
Start-Program.ps1 supports PowerShell's -Verbose common parameter. If you specify the -Verbose parameter, the script outputs the exact Jt.exe command line (excluding the password) and the output generated by Jt.exe. You can use the verbose output as needed to diagnose problems.
Table 2 shows some sample commands to run Start-Program.ps1.
When you run it, the script creates the appropriate Jt.exe command using the information you supplied on the command line. Next, the script executes that command and captures Jt.exe's output strings, checking them for errors. Finally, the script outputs a PSObject object that contains the following properties:
- ComputerName (the computer on which the task was scheduled)
- TaskName (the scheduled task's name, which the script generates based on the program name and the date and time the task was scheduled)
- StartTime (the date and time the task will run)
- CommandLine (the program name and its arguments)
- KeepTask (True or False, based on whether the -KeepTask parameter was specified)
- Result (0 for success, non-zero hexadecimal code for error)
Figure 3 shows a sample command that uses the -Verbose parameter and the resulting output object. Note that Start-Program.ps1 removes the password from the verbose output.
Understanding the Security Implications
Because Start-Program.ps1 uses Jt.exe, it's subject to that utility's limitations, which means there are a few security implications you need to know about:
- Although you can use Start-Program.ps1 without typing passwords in clear text, the script still needs a clear-text copy of the password for the Jt.exe command it runs. This means that a clear-text copy of the password is temporarily available in memory while the script creates and executes the Jt.exe command. Thus, there's a remote possibility of password exposure if a rogue process has access to this memory or if Windows swaps the memory to disk.
- On Vista/Server 2008 or later, Jt.exe will create the scheduled task at the same integrity level on the current computer as the process that created it. In other words, if you run Start-Program.ps1 from an elevated PowerShell session, Jt.exe will create the scheduled task to run elevated. However, if you run Start-Program.ps1 from a non-elevated PowerShell session, Jt.exe won't create the scheduled task to run elevated. This caveat applies only to scheduling tasks on the current computer. If you're running a non-elevated PowerShell session but you're logged on using an account that's a member of the Administrators group on a remote computer, the task will run elevated.
- The program you run won't be visible on the remote computer, so it needs to be a program that can run silently and complete without user interaction.
Flexibility Without Complexity
Using the Task Scheduler service is a good way to run programs on remote computers, but the Jt.exe program is complex and not secure because it requires a password on its command line. Start-Program.ps1 manages the tool's complexity, minimizes the security risk, and provides an easy-to-use and flexible way to run a program on one or more computers.