-
Notifications
You must be signed in to change notification settings - Fork 1
/
Get-ADS.ps1
178 lines (143 loc) · 6.02 KB
/
Get-ADS.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
<#
.SYNOPSIS
This script searches recursively through a specified file system for alternate data streams (ADS).
.DESCRIPTION
The script can search local and UNC paths speciffied by the $path paramenter. All readable files will have the stream
attrubute inspected ignoring the default DATA and FAVICON (image file on URL files) streams. The script use Boe Prox's
amazing Get-RunspaceData function and other code to multithread the search. The default number of threads is the
number of logical cores plus one. This can be adjusted by specifiying the $threads parameter. Use with caution as
runspaces can easily chomp resources (CPU and RAM).
Once the number of file system objects (files and folders) is determined, they are split into equal groups of objects
divided by the number of threads. Then each thread has a subset of the total objects to inspect for ADS.
Author: Michael Garrison (@p0shkatz)
License: MIT
.PARAMETER path
This is a required parameter that sets the base or root path to search from, for example C:\ or \\servername\sharename
.PARAMETER output
This is an optionaal parameter that sets an output location for the results, for example C:\ads-data.log
.PARAMETER threads
This is an optional parameter that sets the number of threads to run concurrently.
.EXAMPLE
Get-ADS.ps1 -Path C:\
.EXAMPLE
Get-ADS.ps1 -Path C:\ -Threads 16
.EXAMPLE
Get-ADS.ps1 -Path \\servername\sharename -Output \\servername\sharename\ads-report.log
#>
Param
(
[parameter(Mandatory=$true,
ValueFromPipeline=$true,
HelpMessage="Supply the root path (e.g. C:\)")]
[ValidateScript({(Test-Path $_)})]
[String[]]$Path,
[parameter(Mandatory=$false,
HelpMessage="Supply the full path to an output file")]
[ValidateScript({(Test-Path $_.SubString(0,$_.LastIndexOf("\")))})]
[String[]]$Output,
[parameter(Mandatory=$false,
HelpMessage="Supply the number of threads to use")]
[int]$Threads
)
Function Get-RunspaceData {
[cmdletbinding()]
param(
[switch]$Wait
)
Do {
$more = $false
Foreach($runspace in $runspaces) {
If ($runspace.Runspace.isCompleted) {
$runspace.powershell.EndInvoke($runspace.Runspace)
$runspace.powershell.dispose()
$runspace.Runspace = $null
$runspace.powershell = $null
} ElseIf ($runspace.Runspace -ne $null) {
$more = $true
}
}
If ($more -AND $PSBoundParameters['Wait']) {
Start-Sleep -Milliseconds 100
}
# Clean out unused runspace jobs
$temphash = $runspaces.clone()
$temphash | Where {
$_.runspace -eq $Null
} | ForEach {
$Runspaces.remove($_)
}
$Remaining = ((@($runspaces | Where {$_.Runspace -ne $Null}).Count))
} while ($more -AND $PSBoundParameters['Wait'])
}
$ScriptBlock = {
Param ($group, $hash)
$i=1
foreach($item in $group.Group)
{
Write-Progress `
-Activity "Searching through group $($group.Name)" `
-PercentComplete (($i / $group.Count) * 100) `
-Status "$($group.count - $i) remaining of $($group.count)" `
-Id $($group.Name)
$streams = Get-Item $item.FullName -stream *
foreach($stream in $streams.Stream)
{
# Ignore DATA and favicon streams
if($stream -ne ':$DATA' -and $stream -ne 'favicon')
{
$streamData = Get-Content -Path $item.FullName -stream $stream
$hash[$item.FullName] = "Stream name: $stream`nStream data: $streamData"
}
}
$i++
}
}
if($threads){$threadCount = $threads}
# Number of threads defined by number of cores + 1
else{$threadCount = (Get-WmiObject -class win32_processor | select NumberOfLogicalProcessors).NumberOfLogicalProcessors + 1}
$Script:runspaces = New-Object System.Collections.ArrayList
$hash = [hashtable]::Synchronized(@{})
$sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$runspacepool = [runspacefactory]::CreateRunspacePool(1, $threadCount, $sessionstate, $Host)
$runspacepool.Open()
# Ignore read errors
$ErrorActionPreference = 'silentlycontinue'
Write-Host "$(Get-Date -F MM-dd-yyyy-HH:mm:ss)::Retrieving collection of file system objects..."
$items = Get-ChildItem $Path -recurse
$counter = [pscustomobject] @{ Value = 0 }
$groupSize = $items.Count / $threadCount
Write-Host "$(Get-Date -F MM-dd-yyyy-HH:mm:ss)::Collected $($items.count) file system objects. Splitting into $threadCount groups of $groupSize..."
$groups = $items | Group-Object -Property { [math]::Floor($counter.Value++ / $groupSize) }
Write-Host "$(Get-Date -F MM-dd-yyyy-HH:mm:ss)::Searching for alternate data streams..."
foreach ($group in $groups)
{
# Create the powershell instance and supply the scriptblock with the other parameters
$powershell = [powershell]::Create().AddScript($scriptBlock).AddArgument($group).AddArgument($hash)
# Add the runspace into the powershell instance
$powershell.RunspacePool = $runspacepool
# Create a temporary collection for each runspace
$temp = "" | Select-Object PowerShell,Runspace,Group
$Temp.Group = $group
$temp.PowerShell = $powershell
# Save the handle output when calling BeginInvoke() that will be used later to end the runspace
$temp.Runspace = $powershell.BeginInvoke()
$runspaces.Add($temp) | Out-Null
}
Get-RunspaceData -Wait
Write-Host "$(Get-Date -F MM-dd-yyyy-HH:mm:ss)::Completed"
$hash.GetEnumerator() | Format-List
if($output){
Write-Host "Writing output to $output"
$fileStream = New-Object System.IO.StreamWriter $output
$fileStream.WriteLine("Alternate Data Streams")
$hash.GetEnumerator() | foreach{
$fileStream.WriteLine("$($_.Name)`r`n$($_.Value)")
}
$fileStream.Close()
}
# Clean up
$powershell.Dispose()
$runspacepool.Close()
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
[System.GC]::Collect()