When working with Windows Task Scheduler programmatically, the Schedule.Service
COM object provides comprehensive access. Here's how to properly retrieve all required fields including those that initially appear empty:
$schedule = New-Object -ComObject("Schedule.Service")
$schedule.Connect()
$tasks = $schedule.GetFolder("\").GetTasks(0)
The key issue in your script is that some properties need special handling. Here's the corrected approach:
$taskDetails = foreach ($task in $tasks) {
[PSCustomObject]@{
Name = $task.Name
Status = $task.State
Triggers = ($task.Definition.Triggers | Out-String).Trim()
NextRunTime = $task.NextRunTime
LastRunTime = $task.LastRunTime
LastRunResult = "0x{0:X8}" -f $task.LastTaskResult
Author = $task.Definition.RegistrationInfo.Author
Created = $task.Definition.RegistrationInfo.Date
Actions = ($task.Definition.Actions | Select-Object -ExpandProperty Path) -join "n"
}
}
$taskDetails | Format-Table -AutoSize
Some properties require special conversion:
# For better trigger visualization:
function Get-TriggerDetails {
param($trigger)
switch ($trigger.Type) {
1 { "Daily" }
2 { "Weekly" }
3 { "Monthly" }
4 { "MonthlyDay" }
5 { "OnIdle" }
6 { "AtStartup" }
7 { "AtLogon" }
8 { "EventTrigger" }
9 { "RegistrationTrigger" }
10 { "SessionStateChangeTrigger" }
}
}
# Enhanced output:
$tasks | ForEach-Object {
$triggerInfo = $_.Definition.Triggers | ForEach-Object {
"{0} (Start: {1})" -f (Get-TriggerDetails $_), $_.StartBoundary
}
[PSCustomObject]@{
Name = $_.Name
Status = switch ($_.State) {
0 { "Unknown" }
1 { "Disabled" }
2 { "Queued" }
3 { "Ready" }
4 { "Running" }
}
Triggers = $triggerInfo -join "n"
NextRunTime = if ($_.NextRunTime) { $_.NextRunTime } else { "Not scheduled" }
LastRunTime = if ($_.LastRunTime) { $_.LastRunTime } else { "Never run" }
LastRunResult = "0x{0:X8}" -f $_.LastTaskResult
Author = $_.Definition.RegistrationInfo.Author
Created = $_.Definition.RegistrationInfo.Date
}
}
For better sharing and analysis:
$taskDetails | Export-Csv -Path "TaskSchedulerReport.csv" -NoTypeInformation -Encoding UTF8
To focus on particular tasks:
$filteredTasks = $tasks | Where-Object {
$_.Name -like "*Backup*" -or
$_.Definition.RegistrationInfo.Author -like "*Admin*"
}
When working with Windows Task Scheduler via PowerShell, many developers struggle to retrieve complete task information. The default COM object approach often leaves out crucial fields like Status, Triggers, and LastRunResult. Here's how to properly extract all relevant task details.
This enhanced script provides full access to all Task Scheduler properties by properly accessing the underlying XML definition of each task:
# Connect to Task Scheduler service
$schedule = New-Object -ComObject("Schedule.Service")
$schedule.Connect()
$rootFolder = $schedule.GetFolder("\")
$tasks = $rootFolder.GetTasks(0)
# Create custom object collection
$taskDetails = @()
foreach ($task in $tasks) {
$xml = [xml]$task.Xml
$taskObj = [PSCustomObject]@{
Name = $task.Name
Status = $task.State
Triggers = ($xml.Task.Triggers | Out-String).Trim()
NextRunTime = $task.NextRunTime
LastRunTime = $task.LastRunTime
LastRunResult = "0x{0:X}" -f $task.LastTaskResult
Author = $xml.Task.RegistrationInfo.Author
Created = $xml.Task.RegistrationInfo.Date
}
$taskDetails += $taskObj
}
# Output results
$taskDetails | Format-Table -AutoSize
The script now properly handles several problematic areas:
- Status: Uses the .State property instead of .Status
- Triggers: Extracts complete trigger information from the XML definition
- LastRunResult: Formats the result code in hexadecimal format (like Task Scheduler UI)
- Author/Created: Pulls from RegistrationInfo in the XML definition
For better reporting and analysis, you can export the results to CSV:
$taskDetails | Export-Csv -Path "TaskSchedulerReport.csv" -NoTypeInformation -Encoding UTF8
Some tasks may have complex trigger configurations. This enhanced version provides better trigger details:
function Get-TriggerDetails {
param([xml]$taskXml)
$triggers = @()
foreach ($trigger in $taskXml.Task.Triggers.ChildNodes) {
$triggerDetail = @{
Type = $trigger.LocalName
Enabled = $trigger.Enabled
}
# Add type-specific details
switch ($trigger.LocalName) {
"CalendarTrigger" {
$triggerDetail.Add("StartBoundary", $trigger.StartBoundary)
$triggerDetail.Add("ExecutionTimeLimit", $trigger.ExecutionTimeLimit)
}
"LogonTrigger" {
$triggerDetail.Add("UserId", $trigger.UserId)
}
}
$triggers += $triggerDetail
}
return ($triggers | ConvertTo-Json -Depth 3)
}
# Usage in main script:
$triggerDetails = Get-TriggerDetails -taskXml $xml