Private/Set-MonthlyDayOfWeekTrigger.ps1
|
function Set-MonthlyDayOfWeekTrigger { <# .SYNOPSIS Converts a registered task's trigger to "the Nth <weekday> of every month", by rewriting its XML. .DESCRIPTION Register-ScheduledTask has no way to accept a monthly day-of-week trigger. New-ScheduledTaskTrigger offers Once, Daily, Weekly, Startup and Logon and nothing else, and a client-only MSFT_TaskMonthlyDOWTrigger built with New-CimInstance is refused: it carries no CimInstance#MSFT_TaskTrigger type name, and inserting one by hand gets past parameter binding only to fail a layer deeper with "Type mismatch". So the task is registered with an ordinary weekly trigger first, and its exported XML is rewritten and registered again. Task Scheduler accepts from XML what the cmdlets cannot express. The namespace is declared on the fragment rather than left to the parent. Elements assigned through InnerXml do not inherit the document's default namespace, and Task Scheduler rejects the result with "The task XML contains an element or attribute from an unexpected namespace". .PARAMETER Occurrence Which one in the month: 1 to 4, or 5 for the last. .EXAMPLE Set-MonthlyDayOfWeekTrigger -TaskName Update-Everything -TaskPath '\' ` -Start (Get-Date '03:00') -DayOfWeek Wednesday -Occurrence 3 #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Called only by Register-UpdateEverythingTask, immediately after it registered the task and inside its own ShouldProcess. A second confirmation for finishing one registration would be noise.')] [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string] $TaskName, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string] $TaskPath, [Parameter(Mandatory)][datetime] $Start, [Parameter(Mandatory)][System.DayOfWeek] $DayOfWeek, [Parameter(Mandatory)][ValidateRange(1, 5)][int] $Occurrence, [ValidateRange(0, 1440)][int] $RandomDelayMinutes = 0 ) $xml = [xml](Export-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath) $uri = $xml.DocumentElement.NamespaceURI $manager = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) $manager.AddNamespace('t', $uri) $triggers = $xml.SelectSingleNode('//t:Triggers', $manager) if (-not $triggers) { throw "The exported XML for '$TaskName' has no Triggers element." } $months = (@('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December') | ForEach-Object { "<$_ />" }) -join '' # ISO 8601, the same form the CIM class wants. A [TimeSpan] rendered as # "00:15:00" is rejected as incorrectly formatted. $delay = if ($RandomDelayMinutes -gt 0) { "<RandomDelay>PT${RandomDelayMinutes}M</RandomDelay>" } else { '' } $triggers.InnerXml = "<CalendarTrigger xmlns='$uri'>" + "<StartBoundary>$($Start.ToString('s'))</StartBoundary>" + '<Enabled>true</Enabled>' + '<ScheduleByMonthDayOfWeek>' + "<Weeks><Week>$Occurrence</Week></Weeks>" + "<DaysOfWeek><$DayOfWeek /></DaysOfWeek>" + "<Months>$months</Months>" + '</ScheduleByMonthDayOfWeek>' + $delay + '</CalendarTrigger>' $null = Register-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath ` -Xml $xml.OuterXml -Force -ErrorAction Stop } |