-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathGet-AuditData.ps1
471 lines (393 loc) · 18.2 KB
/
Get-AuditData.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
[Cmdletbinding()]
Param(
# The PSCredential to be used
[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[PSCredential]$PSCredential,
# The path to the node hints text file
[Parameter(Mandatory=$True)]
[ValidateScript({Test-Path $_})]
[String]$NodeHintsFile,
# The thread count for runspace pools
[Parameter(Mandatory=$False)]
[ValidateNotNullOrEmpty()]
[Int]$ThreadCount = 64
)
# Grab the start time so we can measure how long this took
$StartTime = Get-Date;
# Bring in our functions library
try {
Write-Host "Importing Audit module: " -ForegroundColor Yellow -NoNewline;
Import-Module ".\Lib\Audit\Audit-Functions.psm1" -DisableNameChecking -Force;
Write-Host "Succeeded." -ForegroundColor Green;
}
catch {
# Can't use Write-ShellMessage here
$Message = "There was a problem attempting to import the Audit module: $($_.Exception.Message)";
Write-Host $Message -ForegroundColor Red;
Exit(1);
}
# Ok now to import the node hints file
try {
# FQ the filepath for good measure and write out
$NodeHintsFilePath = (Resolve-Path $NodeHintsFile).Path;
Write-ShellMessage -Message "Importing node hints list from '$NodeHintsFilePath'" -Type Info;
# Ignore everything that doesn't start with an operand
$NodeHints = Get-Content $NodeHintsFilePath | ?{">","<" -contains $_.ToCharArray()[0]};
}
catch {
Write-ShellMessage -Message "There was a problem attempting to import the node hints file" -Type Error -ErrorRecord $Error[0];
Exit(1);
}
# Now we need to process the node hints and generate a hosts list
try {
# Write out so the user knows what we're doing
Write-ShellMessage -Message "Processing $($NodeHints.Count) node hints" -Type Info;
# Get an intermediate array to hold our nodes list
[System.Collections.ArrayList]$iNodes = @();
$NodeHints.ForEach({
# Get the node hint info from the pipeline
$Operand = $_.Substring(0,1);
$Node = $_.Substring(1,$_.Length - 1);
Write-ShellMessage -Message "Processing node '$Node' with operand '$Operand'" -Type Debug;
# Swiffy and work out whether CIDR block or single host
if ($Node.Contains("/")) {
Write-ShellMessage -Message "Node '$Node is a CIDR block, expanding address space" -Type Debug;
# Split out the IP/CIDR
$IPAddress = $Node.Split("/")[0];
$CIDR = $Node.Split("/")[1];
# Create new subnet with the hint details
$Subnet = Get-IPv4Subnet -IPv4Address $IPAddress -CIDR $CIDR;
# Get subnet boundaries
$StartIPv4Address = $Subnet.NetworkID;
$EndIPv4Address = $Subnet.Broadcast;
# Convert boundaries to Int64
$StartIPv4Address_Int64 = (Convert-IPv4Address -IPv4Address $StartIPv4Address.ToString()).Int64;
$EndIPv4Address_Int64 = (Convert-IPv4Address -IPv4Address $EndIPv4Address.ToString()).Int64;
# Create an array to hold our IPs
[System.Collections.ArrayList]$IPAddresses = @();
# Add nodes for each IP in the range
For ($I = $StartIPv4Address_Int64; $I -le $EndIPv4Address_Int64; $I++)
{
# Gather the IP first so we can write out let the user know what's happening
$IP = ((Convert-IPv4Address -Int64 $I).IPv4Address).IPAddressToString;
# Write-progress as this might be huge
Write-Progress `
-Activity "Expanding CIDR block '$CIDR'" `
-Status "Adding IP address '$IP' to nodes list" `
-PercentComplete $(($I/$EndIPv4Address_Int64)*100);
# Add to the iNodes collection
[Void]($iNodes.Add($([PSCustomObject]@{
Operand = $Operand;
Node = $IP;
})));
}
}
else {
# Ok just ordinary host, add to the iNodes collection
[Void]($iNodes.Add($([PSCustomObject]@{
Operand = $Operand;
Node = $Node;
})));
}
});
# Process node exclusions
Write-ShellMessage -Message "Processing node exclusions" -Type Debug;
$iNodes.Where({$_.Operand -eq "<"}).ForEach({
# Ok get the node
$Node = $_;
# Let's see if any dupes exist from expanded cidr blocks
$iNodes.Where({$_.Node -eq $Node.Node}).ForEach({
# And remove them
[Void]($iNodes.Remove($_));
});
});
# Build the full nodes list
Write-ShellMessage -Message "Building node runtime collection" -Type Debug;
[System.Collections.ArrayList]$NodeCollection = @();
$iNodes.ForEach({
# Grab the node from the pipeline
$Node = $_;
# Regex switch to work out if we're adding a hostname or ip
Switch -Regex ($Node.Node) {
'^\d+.\d+.\d+.\d+' {
# IP - create the node and add to the collection
[Void]($NodeCollection.Add($([PSCustomObject]@{
ID = [Guid]::NewGuid().Guid;
Status = "Unprocessed";
IPAddress = $Node.Node;
Hostname = $Null;
ICMPStatus = $Null;
MACAddress = $Null;
BufferSize = $Null;
ResponseTime = $Null;
TTL = $Null;
WinRMStatus = $Null;
Audited = $False;
AuditErrors = $Null;
Completed = $False;
})));
}
default {
# Hostname - create the node and add to the collection
[Void]($NodeCollection.Add($([PSCustomObject]@{
ID = [Guid]::NewGuid().Guid;
Status = "Unprocessed";
IPAddress = $Null;
Hostname = $Node.Node;
ICMPStatus = $Null;
MACAddress = $Null;
BufferSize = $Null;
ResponseTime = $Null;
TTL = $Null;
WinRMStatus = $Null;
Audited = $False;
AuditErrors = $Null;
Completed = $False;
})));
}
}
});
# Write inital node list to disk
$NodeCSVFilePath = ".\Audit-Results.csv";
$NodeCollection | Export-CSV -Path $NodeCSVFilePath -Force -NoTypeInformation;
# And success
Write-ShellMessage -Message "Successfully parsed $($iNodes.Count) nodes from hints file" -Type Success;
}
catch {
Write-ShellMessage -Message "There was a problem attempting to process the node hints file" -Type Error -ErrorRecord $Error[0];
Exit(1);
}
# Get our scriptblocks in for runspace jobs
try {
# Network probe
$ProbeScriptPath = (Resolve-Path ".\Lib\Audit\Probe-Machine.ps1").Path;
Write-ShellMessage -Message "Importing machine probe script from '$ProbeScriptPath'" -Type Debug;
$ProbeScriptContent = Get-Content $ProbeScriptPath | Out-String;
$ProbeScriptBlock = [ScriptBlock]::Create($ProbeScriptContent);
# Audit scan
$AuditScriptPath = (Resolve-Path ".\Lib\Audit\Audit-Machine.ps1").Path;
Write-ShellMessage -Message "Importing machine audit script from '$AuditScriptPath'" -Type Debug;
$AuditScriptContent = Get-Content $AuditScriptPath | Out-String;
$AuditScriptBlock = [ScriptBlock]::Create($AuditScriptContent);
}
catch {
Write-ShellMessage -Message "There was a problem importing runspace job scripts" -Type Error -ErrorRecord $Error[0];
Exit(1);
}
# Create our runspace pools and job lists
try {
# Network probe
Write-ShellMessage -Message "Creating network probe runspace pool and job collection" -Type Debug;
$ProbeRunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $ThreadCount, $Host);
$ProbeRunspacePool.Open();
[System.Collections.ArrayList]$ProbeJobs = @();
# Audit
Write-ShellMessage -Message "Creating audit runspace pool and job collection" -Type Debug;
$AuditRunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $ThreadCount, $Host);
$AuditRunspacePool.Open();
[System.Collections.ArrayList]$AuditJobs = @();
}
catch {
Write-ShellMessage -Message "There was a problem creating the runspace pools" -Type Error -ErrorRecord $Error[0];
Exit(1);
}
# Add all the network probe jobs to the runspace pool
try {
Write-ShellMessage -Message "Creating runspace jobs for network probe" -Type Info;
# Enumerate the nodes list, create the jobs and add them to the queue
$NodeCollection.ForEach({
# Grab the node from the pipeline
$Node = $_;
# Write debug
$Message = "Creating probe job for node with ID '$($Node.ID)' ($(. ({"$($Node.IPAddress)"},{"$($Node.Hostname)"})[$Node.IPAddress -eq $Null]))";
Write-ShellMessage -Message $Message -Type Debug;
# Create hashtable to pass parameters to the probe job
$ProbeJobParams = @{
Node = $Node;
PSCredential = $PSCredential;
};
# Create new job
$ProbeJob = [System.Management.Automation.PowerShell]::Create().AddScript($ProbeScriptBlock).AddParameters($ProbeJobParams);
$ProbeJob.RunspacePool = $ProbeRunspacePool;
# Add it to the job queue and spark it up
[Void]$ProbeJobs.Add($([PSCustomObject]@{
ID = $Node.ID;
Pipeline = $ProbeJob;
Result = $ProbeJob.BeginInvoke();
}));
});
}
catch {
Write-ShellMessage -Message "There was a problem creating the runspace jobs for the network probe" -Type Error -ErrorRecord $Error[0];
Exit(1);
}
# Job queue processing
try {
# Job Exit
$JobExitCode = 0;
# Grab the total number of probe jobs as we remove them when they are done
$ProbeJobCount = $ProbeJobs.Count;
$ProbeJobCompletedCount = 0;
# Init a counter for the Audit jobs
$AuditCompletedJobCount = 0;
$AuditScanTotalJobCount = 0;
# Loop to process all queues until empty
While ($ProbeJobs.Count -gt 0 -or $AuditJobs.Count -gt 0) {
# If there are any probe jobs completed, process them
$CompletedProbeJobs = @($ProbeJobs | ?{$_.Result.IsCompleted});
# Add to the totals
$ProbeJobCompletedCount += $CompletedProbeJobs.Count;
# Enumerate the completed jobs
$CompletedProbeJobs.ForEach({
# Get the job from the pipeline
$CompletedJob = $_;
# Now we need to trap here as the result may be an unwrapped ErrorRecord
try {
# Grab the result
$Result = $CompletedJob.Pipeline.EndInvoke($CompletedJob.Result);
# Check the error stream and update the node object
if ($CompletedJob.Pipeline.HadErrors) {
# Enumerate the errors
$CompletedJob.Pipeline.Streams.Error | %{
# Update the node with these
$NodeCollection.Where({$_.ID -eq $CompletedJob.ID}).ForEach({
$_.Status += "[$(Get-Date -f "dd/MM/yy HH:mm:ss")][Network Probe Runspace Error]: $($Error[0].Exception.Message)`r`n";
});
}
}
# Update the node in the collection
$NodeCollection.Where({$_.ID -eq $CompletedJob.ID},'First').ForEach({
$_.IPAddress = $Result.IPAddress;
$_.Hostname = $Result.HostName;
$_.ICMPStatus = $Result.ICMPStatus;
$_.MACAddress = $Result.MACAddress;
$_.BufferSize = $Result.BufferSize;
$_.ResponseTime = $Result.ResponseTime;
$_.TTL = $Result.TTL;
$_.WinRMStatus = $Result.WinRMStatus;
});
# Now, if it's accessible over WinRM add a job to the audit queue
if ($Result.WinRMStatus -eq "OK") {
# Create hashtable to pass parameters to the Audit job
$AuditParams = @{
PSCredential = $PSCredential;
Node = ($NodeCollection.Where({$_.ID -eq $CompletedJob.ID},'First'))[0]; # Indexed to escape enumerable
WorkingDirectory = $PSScriptRoot;
};
# Create new Audit job
$AuditJob = [System.Management.Automation.PowerShell]::Create().AddScript($AuditScriptBlock).AddParameters($AuditParams);
$AuditJob.RunspacePool = $AuditRunspacePool;
# And add it to the collection
[Void]($AuditJobs.Add($([PSCustomObject]@{
ID = $CompletedJob.ID;
Pipeline = $AuditJob;
Result = $AuditJob.BeginInvoke();
})));
# Increment the Audit Scan total count
$AuditScanTotalJobCount++;
}
else { # Otherwise generate an error so we know this didn't go ok
$NodeCollection.Where({$_.ID -eq $CompletedJob.ID}).ForEach({
$_.Status += "[$(Get-Date -f "dd/MM/yy HH:mm:ss")][Access Exception]: $($_.WinRMStatus)`r`n";
});
}
}
catch {
# Update the node to say it's broken instead
$NodeCollection.Where({$_.ID -eq $CompletedJob.ID}).ForEach({
$_.Status += "[$(Get-Date -f "dd/MM/yy HH:mm:ss")][Network Probe Runspace Error]: $($Error[0].Exception.Message)`r`n";
});
}
# Dispose of the pipeline
$CompletedJob.Pipeline.Dispose();
# Remove job from the collection
$ProbeJobs.Remove($CompletedJob);
});
# If there are any audit jobs completed, process them
$CompletedAuditJobs = @($AuditJobs | ?{$_.Result.IsCompleted});
# Add to the job totals
$AuditCompletedJobCount += $CompletedAuditJobs.Count;
# Enumerate the completed ones
$CompletedAuditJobs.ForEach({
# Get the completed job from the pipeline
$CompletedAuditJob = $_;
# Now we need to trap here as the result may be an unwrapped ErrorRecord
try {
$AuditResult = $CompletedAuditJob.Pipeline.EndInvoke($CompletedAuditJob.Result);
# Check the error stream and update the node object
if ($CompletedJob.Pipeline.HadErrors) {
# Enumerate the errors
$CompletedJob.Pipeline.Streams.Error | %{
# Update the node with these
$NodeCollection.Where({$_.ID -eq $CompletedJob.ID}).ForEach({
$_.Status += "[$(Get-Date -f "dd/MM/yy HH:mm:ss")][Audit Runspace Error]: $($Error[0].Exception.Message)`r`n";
});
}
}
# Update the collection with the new info
$NodeCollection.Where({$_.ID -eq $CompletedAuditJob.ID}).ForEach({
$_.Audited = $AuditResult.Audited;
$_.Completed = $AuditResult.Completed;
$_.AuditErrors += $AuditResult.AuditErrors;
});
}
catch {
# Update the collection to say the audit broke instead
$NodeCollection.Where({$_.ID -eq $CompletedAuditJob.ID}).ForEach({
$_.Audited = $True;
$_.AuditErrors += "[$(Get-Date -f "dd/MM/yy HH:mm:ss")][Audit Runspace Error]: $($Error[0].Exception.Message)`r`n";
$_.Completed = $False;
});
}
# Dispose of the pipeline
$CompletedAuditJob.Pipeline.Dispose();
# Remove job from collection
$AuditJobs.Remove($CompletedAuditJob);
});
# Write out Network Scan progress (percent complete caught for dividebyzero)
try {$NetworkProbeQueuePercent = ($ProbeJobCompletedCount / $ProbeJobCount) * 100}
catch {$NetworkProbeQueuePercent = 0}
Write-Progress `
-ID 1 `
-Activity "Network Probe Queue" `
-Status "Processed $ProbeJobCompletedCount nodes out of a possible $ProbeJobCount nodes" `
-PercentComplete $NetworkProbeQueuePercent;
# Write out Audit status (percent complete caught for dividebyzero)
try {$AuditPercent = ($AuditCompletedJobCount / $AuditScanTotalJobCount) * 100}
catch {$AuditPercent = 0}
Write-Progress `
-ID 2 `
-Activity "Audit Queue" `
-Status "Out of $AuditScanTotalJobCount nodes to audit, $AuditCompletedJobCount have completed" `
-PercentComplete $AuditPercent;
# Quick sleep to avoid excessive loop burn
Start-Sleep -Milliseconds 500;
}
}
catch {
Write-ShellMessage -Message "There was a problem with the runspace queues" -Type Error -ErrorRecord $Error[0];
$JobExitCode = 1;
}
finally {
# Make sure to dispose of the pools prior to exit
$AuditRunspacePool.Close();
$AuditRunspacePool.Dispose();
$ProbeRunspacePool.Close();
$ProbeRunspacePool.Dispose();
# Export the node collection so we don't lose any data
$NodeCollection | Export-CSV -Path $NodeCSVFilePath -Force -NoTypeInformation;
}
# Write out to say we're done
$EndTime = Get-Date;
$TS = New-TimeSpan $StartTime $EndTime;
# Status message
if ($JobExitCode -eq 0) {
Write-ShellMessage -Message "Network Audit completed" -Type SUCCESS;
}
else {
Write-ShellMessage -Message "Network Audit completed with errors" -Type WARNING;
}
Write-FinalStatus $NodeCollection $TS $ProbeJobCount;
# Fin
Exit;