Scripts/ExternalSources/ScheduledTasks.ps1
<# .SYNOPSIS Pull serialized Scheduled Tasks data, add to Neo4j .DESCRIPTION Pull serialized Scheduled Tasks data, add to Neo4j * Assumes properties line up with Get-ScheduledTasks from the WFTools module in the PowerShell Gallery * Assumes data is serialized via Export-CliXml to one or more paths This is quite opinionated. We prefer this route to directly connecting to nodes. An example implementation: * A central limited access share accessible by all computers. Perhaps domain computers create, creator owner fullish * GPO creates scheduled task on all computers * Scheduled task collects scheduled tasks from the local computer, exports clixml to limited access share This is invoked by Connect-TheDots .PARAMETER Prefix Prefix to append to properties when we add them to Neo4j This helps identify properties that might come from mutiple sources, or where the source is ambiguous For example, Description becomes TSKDescription Defaults to TSK. Change at your own risk .PARAMETER Label What label do we assign the data we pull? Defaults to Task. Change at your own risk .PARAMETER Properties Properties to extract and select from scheduled task data .PARAMETER Excludes Properties to exclude (in line with transforms) .PARAMETER Transforms Properties to select again (in line with excludes) Example: '*', @{ label = 'Hostname' expression = { $_.ComputerName.ToLower() } } This would keep all properties from -Properties, and add a calculated Hostname .PARAMETER DataPath One or more paths to data holding clixml for scheduled tasks. Maps to Get-ChildItem Path (i.e. -Path $DataPath) For example: '\\Path\To\Share\task_*.xml' '\\Path\To\Share\tasks\*.xml' .FUNCTIONALITY Dots #> [cmdletbinding()] param( [string]$Prefix = 'TSK', [string]$Label = 'Task', [string[]]$Properties = @( 'ComputerName', 'Name', 'Path', 'Enabled', 'Action', 'Arguments', 'UserId', 'LastRunTime', 'NextRunTime', 'Status', 'Author', 'RunLevel', 'Description' ), [string[]]$Excludes, [object[]]$Transforms = @( '*', @{ label = 'Hostname' expression = { $_.ComputerName.ToLower() } } ), [string[]]$DataPath, [switch]$AllLower = $Script:AllLower ) $Date = Get-Date # Dot source so module import is available in this scope if($Script:TestMode) { Write-Verbose "Using mock functions from $ModuleRoot/Mock/Mocks.ps1" . "$ModuleRoot/Mock/Mocks.ps1" } $Files = Get-ChildItem $DataPath $Tasks = foreach($File in $Files){ Import-Clixml $File | Where-Object {$_.ComputerName -and $_.Path -and $_.Action} | Select-Object -Property $Properties | Select-Object -Property $Transforms -ExcludeProperty $Excludes } $Tasks = Foreach($Task in $Tasks) { $Output = Add-PropertyPrefix -Prefix $Prefix -Object $Task Add-Member -InputObject $Output -MemberType NoteProperty -Name "${Script:CMDBPrefix}${Prefix}UpdateDate" -Value $Date -Force if($AllLower) { ConvertTo-Lower -InputObject $Output } $Output } $TotalCount = $Tasks.count $Count = 0 Foreach($Task in $Tasks) { Write-Progress -Activity "Updating Neo4j" -Status "Adding task $($Task.$MergeProperty)" -PercentComplete (($Count / $TotalCount)*100) $Count++ Set-Neo4jNode -InputObject $Task -Label $Label -Hash @{ TSKHostname = $Task.TSKHostname TSKPath = $Task.TSKPath } # hostname's not unique across all qualified doman namespaces? Use different logic New-Neo4jRelationship -LeftQuery "MATCH (left:Task) WHERE left.TSKHostname = {TSKHostname} AND left.TSKPath = {TSKPath}" ` -RightQuery "MATCH (right:Server) WHERE right.${script:CMDBPrefix}Hostname STARTS WITH {Start}" ` -Parameters @{ TSKHostname = $Task.TSKHostname TSKPath = $Task.TSKPath Start = "$($Task.TSKHostname)." # Assumes host names are unique across all domains } ` -Type RunsOn } |