Scheduled task triggers

The version 3.0 of powershell offers now a brand new module named PSScheduledJob that holds cmdlets designed to manipulate scheduled tasks. It works great but it has some limitations as well that I’ve already mentioned in my previous post ‘Working with scheduled tasks’

Let’s see first what can we do with these new cmdlets:

# Show all cmdlets of the PSScheduledJob module            
Get-Command -Module PSScheduledJob            
            
# Remove any scheduled tasks left behind as Register-ScheduledJob doesn't have a -Force parameter            
Get-ScheduledJob | Unregister-ScheduledJob            
            
# Create first a trigger object, i.e. when the job is supposed to run            
$trig = New-JobTrigger -Once -At (Get-Date).AddHours(1)            
            
# Display it            
$trig            
            
# Create some addtionnal options            
$opt = New-ScheduledJobOption            
            
# Display            
$opt            
            
# Add a scheduled task             
Register-ScheduledJob -Name "testV3" -Trigger $trig -ScheduledJobOption $opt -ScriptBlock {Write-host 'Hello'}            
# NB: ScheduledJobOption is a non mandatory parameter            
            
# Display the new scheduled task            
Get-ScheduledJob -Name 'testV3'             
            
# Show all its properties            
Get-ScheduledJob -Name 'testV3' | Format-List -Property *            
            
# Show the object type, its methods and the definition of its properties and methods            
Get-ScheduledJob -Name 'testV3' | Get-Member            
            
# 1rst way of launching the task            
(Get-ScheduledJob -Name 'testV3').Run()             
            
# 2nd way of launching the task            
(Get-ScheduledJob -Name 'testV3').StartJob() | Receive-Job -Wait -AutoRemoveJob            

When I saw this new .net object Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger, I immediatly thought that it could be nice to be able to extract the task triggers from their XML definition and cast them back into this new .net class.

We can now “CREATE NON-CUSTOM OBJECTS FROM HASH TABLES” in Powershell version 3.0. See the about_Object_Creation
Let’s see the fairly simple example from the online help:

[Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger]@{            
    Frequency="Daily";            
    At="15:00"            
}

Here’s another example:

[Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger]@{Frequency = 'Weekly';            
    At = [DateTime]::Now ;            
    DaysOfWeek = @([System.DayOfWeek]::Monday,[System.DayOfWeek]::Friday)            
}            
# Requires -Version 2.0

Function Get-DurationAsTimeSpan {
[CmdletBinding()]
param(
    [system.string]$Value 
)
Begin {
    $HT = @{}
}
Process {
    if ($Value) {
        # Validate
        if ($Value -match "^P(T?)\d{1,6}[H|M|D|S]$") {
            Write-Verbose -Message "A valid XML timespan was submitted as input"
        } else {
            Write-Warning -Message "Invalid timespan submitted: $Value"
            break
        }
        # Iterate
        ($Value -split [regex]'(?<FirstLetter>^P)(?<TimeIndicator>(T?))(?<Number>\d{1,6})(?<Units>[H|M|D|S])$',0,'ExplicitCapture') |
        ForEach-Object -Process {
            switch ($_) {
                {$_ -eq "P"} { }
                {$_ -eq "T"} { }
                {$_ -eq "H"} { $PropertyName = 'Hours' }
                {$_ -eq "M"} { $PropertyName = 'Minutes' }
                {$_ -eq "D"} { $PropertyName = 'Days' }
                {$_ -eq "S"} { $PropertyName = 'Seconds' }
                {$_ -match "\d"} { $Units = $_ }
                default {}
            }
        }
        if ($PropertyName -and $Units) {
            $HT += @{$PropertyName = $Units}
            try {    
                New-TimeSpan @HT -ErrorAction Stop
            } catch {
                Write-Warning -Message 'Failed to set timespan'
            }
        }
    }
}
End {}
}

Function Get-TaskTrigger {
[CmdletBinding()]
param(
    [parameter(ValueFromPipeline=$false,ValueFromPipelineByPropertyName=$true,Mandatory=$false)]
    [system.string] ${ComputerName} = $env:computername,

    [parameter(ParameterSetName='ByTaskName',Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]            
    [ValidateNotNullOrEmpty()]
    [System.String[]]$Name,

    [parameter(ParameterSetName='ByTaskPath',Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]            
    [ValidatePattern('^\\')]
    [System.String[]]$Path,

    [parameter(ParameterSetName='XMLinput',Mandatory=$true,ValueFromPipeline=$false,ValueFromPipelineByPropertyName=$true)]     
    [ValidateNotNullOrEmpty()]       
    [System.String]$Xml
)
Begin {
    $HT = @{}
    if ($ComputerName) {
        $HT += @{ComputerName = $ComputerName}
    }
    $xmltaskar = @()
}
Process {
    $xmltaskar = @()
    Switch ($PSCmdlet.ParameterSetName) {
        ByTaskName {
            $Name | ForEach-Object -Process {
                $testname = $_
                Get-AllScheduledTasks @HT | Where-Object {$_.Name -eq $testname} | Foreach-Object -process {
                    $xmltaskar += ([xml]($_ | Get-Task @HT).xml)
                }
            }
            break
        }
        ByTaskPath {
            $Path | ForEach-Object -Process {
                $testpath = $_
                Get-AllScheduledTasks @HT | Where-Object {$_.Path -eq $testpath} | Foreach-Object -process { 
                    $xmltaskar += ([xml]($_ | Get-Task @HT).xml)
                }
            }
            break
        }
        XMLinput   {
            $Xml | ForEach-Object -Process {
                  $xmltaskar += ([xml]$_)
            }
            break
        }
    }
    ForEach ($xmltask in $xmltaskar) {
        if ($xmltask.Task.Triggers.HasChildNodes) {
                $triggersar = @()
                $count = -1
                $xmltask.Task.Triggers.SelectNodes("*") | ForEach-Object -Process {
                    $TriggerName  = $_.Name
                    $count++
                    Write-Verbose -Message "Dealing with trigger number $count called $TriggerName"
                    if ($_.HasChildNodes) {
                        Write-Verbose -Message "Trigger number $count called $TriggerName has childnodes"
                        switch ($TriggerName) {
                        IdleTrigger               {$Frequency = 'OnIdle'}
                        RegistrationTrigger       {$Frequency = 'At task creation/modification'}
                        TimeTrigger               {$Frequency = 'Once' }  
                        # CalendarTrigger           {$Frequency = }
                        LogonTrigger              {$Frequency = 'AtLogon'}
                        EventTrigger              {$Frequency = 'OnEvent'}
                        BootTrigger               {$Frequency = 'AtStartup'}
                        SessionStateChangeTrigger {$Frequency = 'SessionStateChange'}
                        default { $Frequency = $null}
                        }
                        $childcount = -1
                        $enabled = $at = $end = $delay = $userid = $repinterval = $repduration = $daysofweek = $subscription = $statechange = $ExecLimit = $null
                        $Interval = 1
                        $Months= $Weeks = $DaysofMonth = @() # $daysofweek = @()
                        $type = ("System.Collections.Generic.List"+'`'+"1") -as 'Type'
                        $type = $type.MakeGenericType( ("System.DayOfWeek" -as "Type"))
                        $daysofweek = [Activator]::CreateInstance($type)
                        $_.SelectNodes("*") | ForEach-Object -Process {
                            $childcount++
                            if ($_.HasChildNodes) {
                                Write-Verbose -Message "Dealing with child trigger number $childcount that has childnodes"
                                $obj = $_.CreateNavigator()
                                switch($obj.Name) {
                                    Repetition   {
                                        $RepInterval = Get-DurationAsTimeSpan -Value ([xml]$obj.OuterXML).Repetition.Interval
                                        if (([xml]$obj.OuterXML).Repetition.Duration) {
                                            $RepDuration = Get-DurationAsTimeSpan -Value ([xml]$obj.OuterXML).Repetition.Duration
                                        } else {
                                            $RepDuration = [system.timespan][int64]::MaxValue
                                        }
                                        switch (([xml]$obj.OuterXML).Repetition.StopAtDurationEnd) {
                                            false   {$StopAtDurationEnd = $false}
                                            true    {$StopAtDurationEnd = $true}
                                        }
                                    }
                                    Subscription {
                                        $Subscription = $obj.Value
                                    }
                                    StateChange {
                                        $StateChange = $obj.Value
                                    }
                                    Enabled {
                                        switch ($obj.Value) {
                                            false { $Enabled = $false}
                                            true  { $Enabled = $true }
                                        }
                                    }
                                    StartBoundary {
                                        $At = Get-Date -Date $obj.Value
                                    }
                                    EndBoundary {
                                        $End = Get-Date -Date $obj.Value
                                    }
                                    RandomDelay {
                                        $Delay = Get-DurationAsTimeSpan -Value $obj.Value
                                    }
                                    Delay {
                                        $Delay = Get-DurationAsTimeSpan -Value $obj.Value
                                    }
                                    ScheduleByDay {
                                        $Frequency = 'Daily'
                                        $Interval = [int](([xml]$obj.InnerXML).DaysInterval.'#text')
                                    }
                                    ExecutionTimeLimit {
                                        $ExecLimit = Get-DurationAsTimeSpan -Value $obj.Value
                                    }
                                    UserId {
                                        $UserID = $obj.Value
                                    }
                                    ScheduleByWeek {
                                        $Frequency = 'Weekly'
                                        ([xml]$obj.OuterXML).ScheduleByWeek.DaysOfWeek.ChildNodes | ForEach-Object { 
                                            $daysofweek.Add($_.Name)
                                        }
                                        $Interval = [int](([xml]$obj.OuterXML).ScheduleByWeek.WeeksInterval)
                                    }
                                    ScheduleByMonth {
                                        $Frequency = 'Monthly'
                                        ([xml]$obj.OuterXML).ScheduleByMonth.DaysofMonth.ChildNodes | ForEach-Object { $DaysofMonth += ($_.'#text')}
                                        ([xml]$obj.OuterXML).ScheduleByMonth.Months.ChildNodes | ForEach-Object { $Months += $_.Name}
                                    }
                                    ScheduleByMonthDayOfWeek {
                                        $Frequency = 'ByMonthDayOfWeek'
                                        ([xml]$obj.OuterXML).ScheduleByMonthDayOfWeek.Weeks.ChildNodes | ForEach-Object { $Weeks += [int]($_.'#text')}
                                        ([xml]$obj.OuterXML).ScheduleByMonthDayOfWeek.DaysOfWeek.ChildNodes | ForEach-Object { $daysofweek += $_.Name}
                                        ([xml]$obj.OuterXML).ScheduleByMonthDayOfWeek.Months.ChildNodes | ForEach-Object { $Months += $_.Name}
                                    }
                                    default {}
                                }
                            } else {
                                # Write-Verbose -Message "Dealing with child trigger number $childcount that has NO childnodes"
                            }
                        }
                        # Build the object
                        $trigobj = New-Object -TypeName PSObject -Property @{
                            Frequency = $Frequency
                            Enabled = $Enabled
                            Id = $count                            
                            Interval = $Interval
                        }
                        if ($At) { $trigobj | Add-Member  -MemberType NoteProperty -Name At -Value $At}
                        if ($daysofweek) { $trigobj | Add-Member  -MemberType NoteProperty -Name DaysOfWeek -Value @($daysofweek)}
                        if ($delay) { $trigobj | Add-Member  -MemberType NoteProperty -Name RandomDelay -Value $delay }
                        if ($RepInterval) { $trigobj | Add-Member  -MemberType NoteProperty -Name RepetitionInterval -Value $RepInterval}
                        if ($RepDuration) { $trigobj | Add-Member  -MemberType NoteProperty -Name RepetitionDuration -Value $RepDuration }
                        if ($userid) { $trigobj | Add-Member  -MemberType NoteProperty -Name User -Value $userid}
                        if ($DaysofMonth) { $trigobj | Add-Member  -MemberType NoteProperty -Name DaysofMonth -Value $DaysofMonth}

                        if ($End) { $trigobj | Add-Member  -MemberType NoteProperty -Name ExpireAt -Value $End }
                        if ($StopAtDurationEnd) { $trigobj | Add-Member  -MemberType NoteProperty -Name StopAtRepetitionDurationEnd -Value $StopAtDurationEnd }
                        if ($ExecLimit) { $trigobj | Add-Member  -MemberType NoteProperty -Name ExecTimeLimit -Value $ExecLimit }

                        # JobDefinition = $null
                        if ($Months) { $trigobj | Add-Member  -MemberType NoteProperty -Name Months -Value $Months}
                        if ($Weeks) { $trigobj | Add-Member  -MemberType NoteProperty -Name Weeks -Value $Weeks }
                        if ($statechange) { $trigobj | Add-Member  -MemberType NoteProperty -Name StateChange -Value $StateChange}
                        if ($subscription) { $trigobj | Add-Member  -MemberType NoteProperty -Name Subscription -Value $Subscription}
                        $triggersar += $trigobj
                    } else {
                        # Write-Verbose -Message "Dealing with trigger number $count called $TriggerName has no childnodes"
                    }
                }                
            }
            $triggersar
        }
    }
End {}
}

Now to test the above code, I’ve created a multi-triggers task
testing task triggers

# Get the task triggers of all tasks located at '\'            
Get-TaskTrigger -Path "\"            
            
# Get the task triggers from a task named            
Get-TaskTrigger -Name 'Adobe Flash Player Updater'            
            
# Get the tasks triggers from our multi-triggers test task            
Get-TaskTrigger -Name 'test'            
            
# Output the task name before each task triggers'            
Get-AllScheduledTasks -Verbose | Where-Object { $_.Path -eq "\"} | ForEach-Object -Process {            
    Write-Verbose -Message "TaskName: $($_.Name)" -Verbose            
    Get-TaskTrigger -Name $_.Name            
}              
            
# Same thing but with a where syntax only powershell 3 compatible            
# and use XML as input            
Get-AllScheduledTasks | Where Path -eq "\" | ForEach-Object -Process {            
     ($_ | Get-Task)             
} | Select-Object -Property XML | Get-TaskTrigger            
            
# Get all the tasks at root and cast only compatible properties with the .Net Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger class            
# Works only in powershell version 3.0            
Get-AllScheduledTasks -Verbose | Where-Object { $_.Path -eq "\"} |             
ForEach-Object -Process {            
    ($_ | Get-Task)             
} | Select-Object -Property XML | Get-TaskTrigger | ForEach-Object -Process {            
    $obj = $_            
    if ($obj.Frequency -in @('Once','Daily','Weekly','AtLogon','AtStartup')) {            
        $nonNulproperties = @()            
        ($obj | gm -MemberType NoteProperty) | Foreach -Process {            
            if ($obj.($_.Name)) {            
                # Avoid Id property as it's readonly            
                # Cannot create object of type "Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger". "Id" is a ReadOnly property.            
                if (($_.Name) -in @(            
                'DaysInterval','WeeksInterval','RandomDelay','At','User','DaysOfWeek',            
                'AtStartup','AtLogOn','Once','RepetitionInterval','RepetitionDuration','Daily','Weekly'            
                )) {            
                    $nonNulproperties += ($_.Name)            
                }            
            }            
        }            
        $HT = @{}            
        $nonNulproperties | foreach -Process {            
            $HT += @{$_ = ($obj.($_))}            
        }            
        [Microsoft.PowerShell.ScheduledJob.ScheduledJobTrigger]$HT            
    }            
} # | Format-List -Property *

While writing the above code I’ve also learned that:

  • The maximum date that you can enter with Get-Date is
  • Get-Date -Year 9999 -Month 12 -Day 31 -Hour 23 -Minute 59 -Second 59 -Millisecond 999

    To prove it, just do:

    (Get-Date -Year 9999 -Month 12 -Day 31 -Hour 23 -Minute 59 -Second 59 -Millisecond 999).AddMilliseconds(1)

    Exception calling “AddMilliseconds” with “1” argument(s): “The added or subtracted value results in an un-representable DateTime.

  • However the system.timespan .net class has other limits. You can even cast a date time with the following format into it (as there’s no -Milliseconds parameter):
  • [int64]::MaxValue -eq  ([system.timespan]'10675199.02:48:05.4775807').Ticks            
    ([system.timespan]::MaxValue).Ticks -eq  '9223372036854775807'            
    [system.timespan][int64]::MaxValue

    I’ve also found the following article http://www.rlmueller.net/PowerShellDates.htm about this topic

    Advertisements

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s