Wednesday, April 30, 2008

Powershell script to report on all scheduled tasks in our domain

One of the annoying things about Windows management is that you cannot centrally manage scheduled tasks across your environment. You're either spending a lot of money on a third party system to do this for you, or your trying to script this.

I'll skip going into a rant about what I think of most of the available task management products out there (yes, package management and gui automation are nice, but I really just want to manage the native Windows Scheduled Tasks, not replace it). I had previously played with WINAT and command line scripts to utilize AT.EXE. Neither were sufficient as they both worked with the old-style NT4 tasks, which meant you couldn't see them via Scheduled Tasks nor could you run them under their own identities.

Windows 2000 introduced a method to query Scheduled Task info via WMI but there was no way to actually set the information.

By pure accident I found "schtasks.exe", the exact utility I'd been looking for. With SCHTASKS.EXE you can query, create, delete, start, stop, etc any scheduled task on any server you have administrative rights, both locally and remotely. Whats even nicer is SCHTASKS.EXE supports exporting query results in .CSV format.

Below is a script that will take a list of servers and generate a master CSV of all of their tasks info.

For more information on using SCHTASKS.EXE, just run "SCHTASKS.EXE /?" from the command line.

Pre-Requisites:
  • Windows 2003 (not tested on 2000 or 2008)
  • Powershell 1.0

What this script does:
  1. Reads in values from a .config file for where to find a CSV file of server names, where to save the report, and what to use as its temp folder
  2. Loops through the specified list of servers and calls SCHTASKS against it with the option to save a verbose .csv file of all task info
  3. After each loop interation, reads the temporary .csv file into a CSV object of all prior iterations
  4. After all servers are queried, it saves the final compiled report
Script files:
Steps:
  1. Download the code
  2. Extract it wherever you like
  3. Modify the .config file to update the values
  4. Modify the servers.csv file to have the names of the servers to query
For reference, here is the script code:
$Version="v8.4.28 Aaron Dodd"
$Description="Generate CSV of scheduled tasks in the environment"

#------------------------------------------------------------------------------
# Settings / Variables
#------------------------------------------------------------------------------
If (Test-Path "QueryScheduledTasks.config") {
$cfg=[xml](get-content "QueryScheduledTasks.config")
} Else {
Write-Host "!! ERROR !! - Config file not found"
Write-Host "A file with the same name as this script, ending in .config, must exist in the same directory as this script."
exit
}

$ServerList = Import-Csv $cfg.configuration.ServerList.name
$FinalReport=$cfg.configuration.FinalReport.name
$TempDir=$cfg.configuration.TempFolder.name
$TempReport=$TempDir + "\temp.csv"
$ErrorActionPreference=$cfg.configuration.ErrorAction.value
#------------------------------------------------------------------------------


#------------------------------------------------------------------------------
# Process tasks
#------------------------------------------------------------------------------
ForEach ($Server in $ServerList) {
schtasks /QUERY /S $Server.Name /FO CSV /V > $TempReport
$TempCsv += Import-Csv $TempReport
}
Remove-Item $TempReport
$TempCsv | Export-Csv $FinalReport -notype
#------------------------------------------------------------------------------

21 comments:

SteveC said...

I found I got a problem because the first line of the SchTasks output was a blank link and the Import-CSV error'red on it :-(

Amended the output line to ...

schtasks /QUERY /S $Server.Name /FO CSV /V | Where {$_.Length -gt 0} | Set-Content $TempReport

And it works fine

Prudencio Dimas said...

This could be REALLY helpful for me but I'm finding that the import is only displaying information for the first server on my list. Any idea why?

Aaron Dodd said...

Prudencio, not off hand but I need to revamp this script this week so I'll look into it in more detail. What OS versions are you querying and do all of your machines have scheduled tasks? I've tested this only on 2003 and, to be honest, I haven't tested to see what happens if a server has no tasks scheduled.

Cor Reinhard said...

This of course can be done in a one liner.

import-csv 'location of csvfile' | foreach-object {schtasks /query /s $_.name /FO CSV /V /U 'Username with Sufficient Rights' /P 'Password of username' | where {$_.length -gt 0} | set-content 'export-csv location'}

For the password you can cache it with $pw = read-host "Enter password" -AsSecureString only i was unable to test it in a oneliner.

You can also install the Quest Active Directory Powershell Plugin witch you can use to get the list of servers you want from AD instead of a CSV file. The oneliner would then be:

connect-QADService -service 'server.company.com' -ConnectionAccount 'company\administrator' -ConnectionPasswo
rd $pw | Get-QADComputer -sizelimit 0| where {$_.osname -like "*Server*"} | foreach-object {schtasks /query /s $_.name /FO CSV /V /U 'Username with Sufficient Rights' /P 'Password of username' | where {$_.length -gt 0} | set-content 'export-csv location'}

Have fun with it!

Grtz. Cor

ps. With the last oneliner you can easily scope your search to a specific machine or object using the where statement.

KM said...

Great script, works like a charm.

I have a separate script to email the csv report generated; see below.

I want to strip the csv of the text and make it the body of the email instead of sending the csv as an attachment?

$filename = “S:\ScheduledTasksReport.csv”
$smtpServer = “smtp.smtp.com”
$msg = new-object Net.Mail.MailMessage
$att = new-object Net.Mail.Attachment($filename)
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.From = “email@email.com”
$msg.To.Add(”email@email.com”)
$msg.Subject = “Hostname Scheduled Tasks Report”
$msg.Body = “The success / failure report for all scheduled tasks is attached.”
$msg.Attachments.Add($att)
$smtp.Send($msg)
$att.dispose()

Anonymous said...

I, too, discovered schtasks.exe after spending hours trying to find a way to get Scheduled Task details from reote servers.

However, I've found that certain servers cause schtasks.exe to thrown an "unknown software exception (0xc0000409)" when using the /V option. I suppose this has something to do with security as this is a government network, and even though I am a domain administrator, I may not have full admnistrative permissions on certain servers like upstream Exchange systems.

So, I'm still in search of some way to list Schedued Task status from remote systems...

bbeard@syncroness.com said...

The link to download the files is broken.

Aaron Dodd said...

My apologies for the missing attachments. It was due to a misconfigured .htaccess file. I've fixed it and the script should be available again.

Anonymous said...

thanks for the Script, it really helps, I have scheduled, but the problem is that it overwrites the previous one, do you know how can i can make the output file name with date and time stamp?

Aaron Dodd said...

To save with a timestamp you need to grab the output of "get-date" and format it. Do a "get-help get-date" for more information.

For me, I usually use the format of "YYYY-MM-DD_hh-mm-ss" which would be:

get-date -uformat "%Y-%m-%d_%H%M%S"

So I usually grab this somewhere in the script as:

$TimeStamp = get-date -uformat "%Y-%m-%d_%H%M%S"

Then prepend or append the $Timestamp to the file name.

Anonymous said...

thanks for your reply. I tried to put $TimeStamp here in the script

$TempCsv | Export-Csv $FinalReport -notype + $TimeStamp

still didn't work. I might be doing it totally wrong, I'm just starting to learn powershell, so could you help.
please note I have already defined $TimeStamp in the script

$ServerList = Import-Csv $cfg.configuration.ServerList.name
$FinalReport=$cfg.configuration.FinalReport.name
$TempDir=$cfg.configuration.TempFolder.name
$TempReport=$TempDir + "\temp.csv"
$TimeStamp = get-date -uformat "%Y-%m-%d_%H%M%S"
$ErrorActionPreference=$cfg.configuration.ErrorAction.value

I have also tried to play with the config file but still could not figure it out.

Aaron Dodd said...

You need to make the filename include the timestamp. I.e. assuming "$FinalReport" is the filename without extension (such as "MyReport" you can do:

$FinalReport = $FinalReport + $TimeStamp + ".csv"
$TempCsv | Export-Csv $FinalReport -notype

or:

$TempCsv | Export-Csv ($FinalReport + $TimeStamp + ".csv") -notype

Both of which will save a file of "Which will make "MyReport20091212153312.csv" for today at 3:13:12pm"

Anonymous said...

Thanks Aaron, this really helps :) it worked fine.

Fadooli

Ricksdev.com said...

Hello,

I am having an issue with the .config file. I am testing it on a local server which is a Win 2003. I am brand new to Powershell, and I hope you can help me out. Also the link to the zip file is not working.

Than you for your help.

Error:

Cannot convert value "rardilalp_ga" to type "System.Xml.XmlDocument". Error: "Data at the root level is invalid. Line 1, position 1."
At C:\Scripts\schedule-tasks\QueryScheduledTasks.ps1:8 char:11
+ $cfg=[xml] <<<< (get-content "QueryScheduledTasks.config")
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException

SteveC said...

Is your config file valid XML ?
- my quick and dirty check is try opening it in IE

Ricksdev.com said...

Hey Steve,

Thank you for the response. To answer your question, no it is not on a valid XML format, but I am not sure of what tag format or name should I used, nor the element structure that should be used. I can only think of using MyServer01, but again I am not sure.

Once again, any help is greatly appreciated.

Ricksdev.com said...

I think my xml structure was deleted by blogger, but here is what I think will be the correct structure,

""MyServer01""

SteveC said...

Err, something not right here

The config file has to be valid XML, even if only a single root element, as you're casting $cfg to [xml]

My example config file is ...








If you're just getting started with PowerShell, I highly recommend Bruce Payette's book, PowerShell In Action

SteveC said...

Oops, see what you mean about the XML disappearing

lets try replacing < with (
and > with )

(configuration)
(ServerList name="QueryScheduledTasks_Servers.csv" /)
(FinalReport name="ScheduledTasksReport.csv" /)
(TempFolder name="C:\" /)
(ErrorAction value="Continue" /)
(/configuration)

Krišjānis said...

Unable to download attachment :((

SteveC said...

I've put my copy at ...

http://cid-3ea862b14b6d8777.skydrive.live.com/self.aspx/OpenToAll/QueryScheduledTasks%5E_8.4.28.zip