Set-ScheduledTask (Uses COM object and XML text)

<pre class="lang:ps decode:true " title=“Set-ScheduledTask” >

Function Set-ScheduledTask {
<#
.SYNOPSIS
Creates or removes a scheduled task on local computer or remote computer
.DESCRIPTION
Creates a scheduled task on local computer or remote computer using COM object and XML text.
Returns $True or $False

Simply create a task in "Task Scheduler" and export it to XML. 
Then use that XML from inside a Here-String [@" "@] or from the xml file

CAVEAT: Only handles task folders ONE level deep

.PARAMETER TaskXmlContent
Contents of a Task XML file as [String], [XmlDocument] or [xml].
.PARAMETER TaskNamePath
Name of the task to be created/removed and/or TaskFolder to be created/removed
Can also contain a TaskFolder. TaskFolder is auto-created if needed.
[TODO SomeDay: Optional if TaskXmlContent Description’s Last line is @@@MyTaskFolder\MyTaskName]
.PARAMETER TaskUser
Default: $null
TaskUser can be $null if task is running as SYSTEM or a group (e.g. BUILT-IN\USERS)
.PARAMETER TaskPwd
Default: $null
TaskPwd can be $null if task is running as SYSTEM or a group (e.g. BUILT-IN\USERS)
.PARAMETER ComputerName
Name of Computer where to create the task. Default: localhost
.PARAMETER Remove
Remove the task specified by TaskNamePath [and ComputerName]
.PARAMETER RemoveTaskFolderIfEmpty
Works with -Remove parameter. Delete folder holding the targeted task.
.PARAMETER ContinueOnError
Continue if an error is encountered. Default: $false.
.EXAMPLE
[XML]$NewTaskXmlFileContent = Get-Content “C:\stuff\ExportedTask.xml”
Set-ScheduledTask -TaskNamePath $NewTaskName -TaskXmlContent $NewTaskXmlFileContent -ComputerName $Computer
Creates task in Root Task folder () from “C:\stuff\ExportedTask.xml”
.EXAMPLE
Set-ScheduledTask -TaskNamePath $NewTaskName -Remove -ComputerName $Computer
Removes task from Root Task folder ()
.EXAMPLE
[xml]$NewTaskXmlContent = @" …<xml stuff>… "@
Set-ScheduledTask -TaskNamePath “MyTaskFolder\MyTaskName” -TaskXmlContent $NewTaskXmlContent
Creates task in MyTaskFolder Task folder (\MyTaskFolder) from [xml]$NewTaskXmlContent
CAVEAT: Cannot create more than ONE level deep.
.EXAMPLE
Set-ScheduledTask -TaskNamePath “MyTaskFolder\MyTaskName” -Remove
Set-ScheduledTask -TaskNamePath “\MyTaskFolder\MyTaskName” -Remove
Removes task from MyTaskFolder Task folder (\MyTaskFolder)
CAVEAT: Cannot delete from more than ONE level deep.
.EXAMPLE
Set-ScheduledTask -TaskNamePath “MyTaskFolder\MyTaskName” -Remove -RemoveTaskFolderIfEmpty
Removes task from MyTaskFolder Task folder (\MyTaskFolder)
Removes MyTaskFolder Task folder if it then becomes empty
CAVEAT: Cannot delete from more than ONE level deep.
.EXAMPLE
Set-ScheduledTask -TaskNamePath “\MyTaskFolder*” -Remove -RemoveTaskFolderIfEmpty
Removes MyTaskFolder Task folder and its Tasks. (BE CAREFUL!!)
CAVEAT: Cannot delete more than ONE level deep.
.NOTES
Version 1.0 (22-APR-2015)
Denis St-Pierre (Ottawa, Canada)
LIMITATION: cannot handle more than one task folder deep
Based on http://psappdeploytoolkit.codeplex.com
For syntax use: Get-Help Set-ScheduledTask
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True)]
[ValidateNotNullorEmpty()]
[string]$TaskNamePath = ‘’,
[Parameter(Mandatory=$false)]
[ValidateNotNullorEmpty()]
$TaskXmlContent,
[Parameter(Mandatory=$false)]
[ValidateNotNullorEmpty()]
[Switch]$Remove,
[Parameter(Mandatory=$false)]
[ValidateNotNullorEmpty()]
[string]$ComputerName = “localhost”,
[Parameter(Mandatory=$false)]
[ValidateNotNullorEmpty()]
[string]$TaskUser = $null,
[Parameter(Mandatory=$false)]
[ValidateNotNullorEmpty()]
[string]$TaskPwd = $null,
[Parameter(Mandatory=$false)]
[ValidateNotNullOrEmpty()]
[switch]$RemoveTaskFolderIfEmpty,
[Parameter(Mandatory=$false)]
[ValidateNotNullOrEmpty()]
[bool]$ContinueOnError = $true
)

Begin {
	## Get the name of this function and write header
	[string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
	Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
	Remove-Variable TaskFolderPath -ErrorAction SilentlyContinue #Needed for debug to make sense
	
	Try {
		[System.__ComObject]$ScheduleObj = New-Object -ComObject("Schedule.Service") #Need Admin do to this!
		$ScheduleObj.connect($ComputerName)
		[System.__ComObject]$TaskRootFolderObj = $ScheduleObj.GetFolder("\")
	} Catch {
		[string]$exceptionMessage = "$($_.Exception.Message) <code>($($_.ScriptStackTrace)</code>)"
		[String]$message = "ERROR: Cannot connect to [Schedule.Service]. Are we running elevated? $exceptionMessage"
		If ($ContinueOnError) {
			Write-Log $message  -Source ${CmdletName}
			return $false #exit function
		} else { Throw $message }
	}
	#TODO SomeDay: Retrieve TaskNamePath from TaskXmlContent Description's Last line is @@@MyTaskFolder\MyTaskName
	Write-Log "TaskNamePath is [$TaskNamePath]" -Source ${CmdletName}
	
	If ( $TaskNamePath -match '\\') { #Get $TaskName and $TaskFolderPath from TaskNamePath
		[string]$TaskName = [System.IO.Path]::GetFileName($TaskNamePath)
		[string]$TaskFolderPath = [System.IO.Path]::GetDirectoryName($TaskNamePath)
	} else {
		[string]$TaskName = $TaskNamePath

[string]$TaskFolderPath = “” #RootTaskFolder

	}
}
Process {
	#Remove ALL Tasks in ONE Task Folder (Except ROOT Task Folder)
	If ( ($Remove) -and ($TaskName -eq "*") ) {
		If ($TaskRootFolderObj.Path -eq $TaskFolderObj.Path ) {
			[String]$message = "Will NOT delete all tasks in [ROOT Task Folder]. Allowing this would break too many things!"
			If ($ContinueOnError) {
				Write-log $Message  -Source ${CmdletName}
				return $false #exit function
			} else { Throw "ERROR: $Message" }
		}
		
		Try {
			[System.__ComObject]$TaskFolderObj = $ScheduleObj.GetFolder($TaskFolderPath)
		} Catch {
			[string]$exceptionMessage = "$($_.Exception.Message)] <code>($($_.ScriptStackTrace)</code>)"
			[String]$message = "Task folder [$TaskFolderPath] does not exist. `nNothing to Delete. $exceptionMessage"
			If ($ContinueOnError) {
				Write-log $message -Source ${CmdletName}
				return $True #exit function
			} else { Throw "ERROR: $message" }
		}
		
		Write-Log "Deleting Task folder [$($TaskFolderObj.Path)] regardless of the number of tasks in the folder" -Source ${CmdletName}
		Try {
			[System.__ComObject]$AllTasks = $TaskFolderObj.GetTasks(1) #1= include hidden tasks too
		} Catch {
			[string]$exceptionMessage = "$($_.Exception.Message)] <code>($($_.ScriptStackTrace)</code>)"
			[String]$message = "Unable to get Tasks in Task folder [$TaskFolderPath] $exceptionMessage"
			If ($ContinueOnError) {
				Write-log $message  -Source ${CmdletName}
				return $false #exit function
			} else {
				Throw "ERROR: $message"
			}
		}
		[Int32]$TotalNumTasks=$AllTasks.count
		Write-log "[$TaskFolderPath] has $TotalNumTasks task(s)"  -Source ${CmdletName}
		If ($TotalNumTasks -gt 0 ) {
			ForEach ($Task in $AllTasks) {
				Try {
					$Task.Stop(0)	#Just in case, should test if running first
					Start-Sleep -Seconds 1
					$TaskFolderObj.DeleteTask($Task.Name,0)
					Write-Log ("Task [$($Task.Name)] was deleted")  -Source ${CmdletName}
				} Catch {
					[String]$message = "Cannot delete task [$($Task.Name)]. Might not exist or stopped"
					If ($ContinueOnError) {
						Write-log $message  -Source ${CmdletName}
					} else { Throw "ERROR: $message" }
				}
			}
		
			#Are they all gone?
			[System.__ComObject]$AllTasks = $TaskFolderObj.GetTasks(1) #1= include hidden tasks too
			If ($($AllTasks.Count) -ne 0) { 
				[string]$ErrMess = "ERROR: Not all tasks have been deleted from the task folder."
				ForEach ($Task in $AllTasks) {
						$ErrMess = $ErrMess + "`nTask [$($Task.Name)]"
				}
				If ($ContinueOnError) {
					Write-log $ErrMess -Source ${CmdletName}
					return $false #exit function
				} else { Throw "ERROR: $ErrMess" }
			} else {
				#Write-log "INFO:No Tasks to delete in task folder[$SubFolderPath]." -Source ${CmdletName}
			}
		} else {
			Write-Log "INFO: No tasks to delete in task folder [$SubFolderPath]."  -Source ${CmdletName}
		}

		#Delete the Task Folder
		if ($RemoveTaskFolderIfEmpty) {
			#CAVEAT : you must use .DeleteFolder method with the PARENT of the task folder you want to delete
			#CAVEAT2: I didn't bother to add code for subfolders b/c I didn't need it.
			$SubFolderName=Split-Path -Path $TaskFolderObj.Path -Leaf
			$ParentFolderPath=Split-Path -Path $TaskFolderObj.Path -Parent
			Try {
				[System.__ComObject]$ParentFolderObj = $ScheduleObj.GetFolder($ParentFolderPath)
			} Catch {
				[string]$exceptionMessage = "$($_.Exception.Message) <code>($($_.ScriptStackTrace)</code>)"
				Throw "Task folder [${ParentFolderPath}] does not exist. `nNothing to Delete. $exceptionMessage"
				Return $true #exit Function
			}
			#No need to check for left-over tasks. We did this already above
			
			Try {
				$ParentFolderObj.DeleteFolder($SubFolderName,$null)
				Write-Log "Task folder [$SubFolderName] was deleted" -Source ${CmdletName}
				return $true #exit Function
			} Catch {
				$exceptionMessage = "$($_.Exception.Message) <code>($($_.ScriptStackTrace)</code>)"
				[String]$message = "Unable to delete Task folder [$SubFolderName] $exceptionMessage"
				If ($ContinueOnError) {
					Write-log $message -Source ${CmdletName}
					return $false #exit Function
				} else { Throw "ERROR: $message" }
			}
		} Else {
			#Write-log "Not attempting to delete Task Folder [$($TaskFolderObj.Path)]" -Source ${CmdletName}
			return $true #exit Function
		}
		
	}

	#Remove ONE Task
	If ( ($Remove) -and ($TaskName -ne "") ) {
		Try {
			[System.__ComObject]$TaskFolderObj = $ScheduleObj.GetFolder($TaskFolderPath)
		} Catch {
			[String]$message = "Task folder [${TaskFolderPath}] does not exist. `nNothing to Delete."
			If ($ContinueOnError) {
				Write-log $message -Source ${CmdletName}
				return $true #exit Function
			} else { Throw "ERROR: $message" }
		}
		
		Write-Log ("Task [$TaskName] will be removed")  -Source ${CmdletName}
		Try {
			[System.__ComObject]$task=$TaskFolderObj.gettask($TaskName)
			$Task.Stop(0)	#Stop the task, Just in case
			Start-Sleep -Seconds 1
			$TaskFolderObj.DeleteTask($TaskName,0)
			Write-Log ("Task [$TaskName] was deleted")  -Source ${CmdletName}
		} Catch {
			[String]$message = "INFO:Cannot delete task [$TaskName]. It might not exist."
			Write-log $message -Source ${CmdletName}
			return $True #exit Function
		}
		
		#TODO: Check if the Task is still in $TaskFolderObj or not ( use gettasks() ?)

		if ($RemoveTaskFolderIfEmpty) {
			If ($TaskRootFolderObj.Path -eq $TaskFolderObj.Path ) {
				[String]$message = "INFO: Cannot delete [ROOT task] folder."
				If ($ContinueOnError) {
					Write-log $message -Source ${CmdletName}
				} else { Throw "ERROR: $message" }
			} else {
				Try {
					#CAVEAT : you must use .DeleteFolder method with the PARENT of the task folder you want to delete
					#CAVEAT2: I didn't bother to add code for subfolders b/c I didn't need it.
					$SubFolderName=Split-Path -Path $TaskFolderObj.Path -Leaf
					$ParentFolderPath=Split-Path -Path $TaskFolderObj.Path -Parent
					
					Try {
						[System.__ComObject]$ParentFolderObj = $ScheduleObj.GetFolder($ParentFolderPath)
					} Catch {
						[string]$exceptionMessage = "$($_.Exception.Message) <code>($($_.ScriptStackTrace)</code>)"
						Throw "Cannot Get Task folder [${ParentFolderPath}] in order to delete $SubFolderName. $exceptionMessage"
						Return $false #exit Function
					}
					#Is $TaskFolder empty? 
					[System.__ComObject]$AllTasks = $TaskFolderObj.GetTasks(1) #1= include hidden tasks too
					If ($($AllTasks.Count) -eq 0) {
						$ParentFolderObj.DeleteFolder($SubFolderName,$null)
						Write-Log "Task folder [$SubFolderName] was deleted" -Source ${CmdletName}
					} Else {
						Write-Log "INFO: Cannot delete Task folder [$SubFolderName]. It still contains $($AllTasks.Count) task(s)." -Source ${CmdletName}
					}
					return $True #exit Function
				} Catch {
					$exceptionMessage = "$($_.Exception.Message) <code>($($_.ScriptStackTrace)</code>)"
					[String]$message = "Unable to delete Task folder [$SubFolderName] $exceptionMessage"
					If ($ContinueOnError) {
						Write-log $message -Source ${CmdletName}
						return $false #exit Function
					} else { Throw "ERROR: $message" }
				}
			} #Else
		} Else {
			#Write-log "Not attempting to delete Task Folder [$($TaskFolderObj.Path)]" -Source ${CmdletName}
			return $true #exit Function
		}
	} 
	
	#Create Task
	If ($TaskName -eq "*") {
		[String]$message = "Cannot create task named $TaskName"
		If ($ContinueOnError) {
			Write-log $message -Source ${CmdletName}
			return $false #exit Function
		} else { Throw "ERROR: $message" }
	}	
	
	If (-not ( $TaskXmlContent) ) {
		[String]$message = "Cannot create task without -TaskXmlContent parameter"
		If ($ContinueOnError) {
			Write-log $message -Source ${CmdletName}
			return $false #exit Function
		} else { Throw "ERROR: $message" }
	}
	
	If ($TaskFolderPath) { 	#Create TaskFolder if needed
		Write-Log "Creating Task folder [${TaskFolderPath}] If needed."  -Source ${CmdletName}
		Try { $TaskRootFolderObj.CreateFolder($TaskFolderPath) } Catch { } #ignore already exists error
		Try {
			[System.__ComObject]$TaskFolderObj =$ScheduleObj.GetFolder($TaskFolderPath)
		} Catch {
			[String]$message = "Task folder [${TaskFolderPath}] does not exist. Cannot create task in non-existing task folder."
			If ($ContinueOnError) {
				Write-Log $message -Source ${CmdletName}
				Return $false
			} Else { Throw "ERROR: $message" }
		}
	} Else {	#Task will be created in the "Root" Task Folder
			[System.__ComObject]$TaskFolderObj = $TaskRootFolderObj
	}
	
	#Creating task (In sub folder if needed)
	[System.__ComObject]$NewTask = $ScheduleObj.NewTask($null)		#Create blank task
	If ( $($TaskXmlContent.gettype().name) -eq "XmlDocument") {
		$NewTask.XmlText = $TaskXmlContent.OuterXml -as [string]	#load XmlText property
	} ElseIf ( $($TaskXmlContent.gettype().name) -eq "String") {
		$NewTask.XmlText = $TaskXmlContent		#load XmlText property
	} Else {
		[String]$message = "-TaskXmlContent as [$($TaskXmlContent.gettype().name)] is not supported. Please cast as [XmlDocument] or [string]."
		If ($ContinueOnError) {
			Write-log $message -Source ${CmdletName}
			return $false #exit Function
		} else { Throw "ERROR: $message" }			
	}
	
	#It can overwrite an existing task just fine provided that the task folder exists already
	# but just in case...
	Try {
		Write-Log "Creating Task [$TaskName]..."  -Source ${CmdletName}
		$RegistrationResult=$TaskFolderObj.RegisterTaskDefinition($TaskName, $NewTask, 6, $TaskUser, $TaskPwd, 1, $null)
		#Write-log "DEV: $RegistrationResult"  -Source ${CmdletName}
	} catch {
		[string]$exceptionMessage = "$($_.Exception.Message) <code>($($_.ScriptStackTrace)</code>)"
		[String]$message = "Unable to import task [$TaskName] $exceptionMessage"
		If ($ContinueOnError) {
			Write-log $message  -Source ${CmdletName}
			return $false
		} Else { throw "ERROR: $message" }
	}
	#$RegistrationResult is a Massive block of text but not as [string]
	#[String]$RegistrationResultString = Out-String -InputObject $RegistrationResult
	#return $RegistrationResultString
	Return $true
}
End {
	Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
}

} # Set-ScheduledTask