Scripts/ExternalSources/ScheduledTasks.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<#
.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
}