-
Notifications
You must be signed in to change notification settings - Fork 0
/
Measure-UsnConsumption.ps1
337 lines (282 loc) · 15.7 KB
/
Measure-UsnConsumption.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
<#
Author: Ryan Ries, 2016, [email protected], [email protected]
Concept By Todd Maxey, [email protected]
Queries the NTFS USN jounral of whatever volume(s) you specify. This does not use
the newer USN record versions in order to maintain backwards compatibility with older versions.
#>
Filter Query-UsnJournal
{
Param([Parameter(Mandatory=$False, ValueFromPipeline=$True)][String[]]$DriveLetter = $Env:SystemDrive)
If (-Not((New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)))
{
Write-Error "This cmdlet requires administrative privileges and UAC elevation."
Return
}
$NativeAPI = @'
using System;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;
public class NativeAPI
{
public const Int64 INVALID_HANDLE_VALUE = -1;
public const int FSCTL_QUERY_USN_JOURNAL = 0x000900f4;
[StructLayout(LayoutKind.Sequential)]
public struct USN_RECORD
{
public UInt32 RecordLength;
public UInt16 MajorVersion;
public UInt16 MinorVersion;
public UInt64 FileReferenceNumber;
public UInt64 ParentFileReferenceNumber;
public Int64 Usn;
public Int64 TimeStamp; // strictly, this is a LARGE_INTEGER in C
public UInt32 Reason;
public UInt32 SourceInfo;
public UInt32 SecurityId;
public UInt32 FileAttributes;
public UInt16 FileNameLength;
public UInt16 FileNameOffset; // immediately after the FileNameOffset comes an array of WCHARs containing the FileName
}
[StructLayout(LayoutKind.Sequential)]
public struct USN_JOURNAL_DATA
{
public long UsnJournalID;
public long FirstUsn;
public long NextUsn;
public long LowestValidUsn;
public long MaxUsn;
public long MaximumSize;
public long AllocationDelta;
}
[StructLayout(LayoutKind.Sequential)]
public struct READ_USN_JOURNAL_DATA
{
public Int64 StartUsn;
public UInt32 ReasonMask;
public UInt32 ReturnOnlyOnClose;
public UInt64 Timeout;
public UInt64 BytesToWaitFor;
public UInt64 UsnJournalID;
}
[System.Flags]
public enum USN_REASON : uint
{
DATA_OVERWRITE = 0x00000001,
DATA_EXTEND = 0x00000002,
DATA_TRUNCATION = 0x00000004,
NAMED_DATA_OVERWRITE = 0x00000010,
NAMED_DATA_EXTEND = 0x00000020,
NAMED_DATA_TRUNCATION = 0x00000040,
FILE_CREATE = 0x00000100,
FILE_DELETE = 0x00000200,
EA_CHANGE = 0x00000400,
SECURITY_CHANGE = 0x00000800,
RENAME_OLD_NAME = 0x00001000,
RENAME_NEW_NAME = 0x00002000,
INDEXABLE_CHANGE = 0x00004000,
BASIC_INFO_CHANGE = 0x00008000,
HARD_LINK_CHANGE = 0x00010000,
COMPRESSION_CHANGE = 0x00020000,
ENCRYPTION_CHANGE = 0x00040000,
OBJECT_ID_CHANGE = 0x00080000,
REPARSE_POINT_CHANGE = 0x00100000,
STREAM_CHANGE = 0x00200000,
CLOSE = 0x80000000
}
[System.Flags]
public enum USN_SOURCE : uint
{
DATA_MANAGEMENT = 0x00000001,
AUXILIARY_DATA = 0x00000002,
REPLICATION_MANAGEMENT = 0x00000004
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
[DllImport("kernel32.dll", SetLastError=true)]
//[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
//[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("Kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool DeviceIoControl(
IntPtr hDevice,
uint dwIoControlCode,
ref long InBuffer,
int nInBufferSize,
ref USN_JOURNAL_DATA OutBuffer,
int nOutBufferSize,
ref int pBytesReturned,
IntPtr UnUsed);
}
'@
Add-Type -TypeDefinition $NativeAPI
:NextDrive Foreach ($Drive In $DriveLetter)
{
If (-Not($Drive.EndsWith(':')))
{
$Drive = $Drive + ':'
}
[IntPtr]$DriveHandle = [NativeAPI]::CreateFile("\\.\$Drive", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [IntPtr]::Zero, [System.IO.FileMode]::Open, 0, [IntPtr]::Zero)
If ($DriveHandle.ToInt32() -EQ [NativeAPI]::INVALID_HANDLE_VALUE)
{
Write-Error "Unable to open drive $Drive."
Continue NextDrive
}
$JournalData = New-Object NativeAPI+USN_JOURNAL_DATA
$ReadData = New-Object NativeAPI+READ_USN_JOURNAL_DATA
$UsnRecord = New-Object NativeAPI+USN_RECORD
[Long]$BytesRead = 0
If (([NativeAPI]::DeviceIOControl(
$DriveHandle,
[NativeAPI]::FSCTL_QUERY_USN_JOURNAL,
[ref]$Null,
0,
[ref]$JournalData,
[System.Runtime.InteropServices.Marshal]::SizeOf($JournalData),
[ref]$BytesRead,
[IntPtr]::Zero)) -EQ 0)
{
Write-Error "DeviceIoControl failed during USN Journal query on drive $Drive with error code $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
If ($DriveHandle.ToInt32() -GT 0)
{
If ([NativeAPI]::CloseHandle($DriveHandle) -EQ 0)
{
Write-Warning "Could not close handle to drive $Drive."
}
}
Continue NextDrive
}
If ($DriveHandle.ToInt32() -GT 0)
{
If ([NativeAPI]::CloseHandle($DriveHandle) -EQ 0)
{
Write-Warning "Could not close handle to drive $Drive."
}
}
$UsnJournalInfo = New-Object PSObject
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'Drive Letter' -Value $Drive
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'Timestamp' -Value (Get-Date)
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'USN Journal ID' -Value ("0x{0:x}" -f $JournalData.UsnJournalID)
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'First USN' -Value ("0x{0:x}" -f $JournalData.FirstUsn)
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'Next USN' -Value ("0x{0:x}" -f $JournalData.NextUsn)
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'Lowest Valid USN' -Value ("0x{0:x}" -f $JournalData.LowestValidUsn)
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'Max USN' -Value ("0x{0:x}" -f $JournalData.MaxUsn)
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'Maximum Size' -Value ("0x{0:x}" -f $JournalData.MaximumSize)
$UsnJournalInfo | Add-Member -MemberType NoteProperty -Name 'Allocation Delta' -Value ("0x{0:x}" -f $JournalData.AllocationDelta)
Write-Output $UsnJournalInfo
}
}
<#
Author: Ryan Ries, 2016, [email protected], [email protected]
Concept By Todd Maxey, [email protected]
Utilizes Query-UsnJournal to measure USN Journal consumption over time. USN consumption
at an extremely high rate can predict failures in services that rely on the USN journal (e.g. FRS, DFSR, etc.)
#>
Filter Measure-UsnConsumption
{
Param([Parameter()][ValidateRange(5,([Uint16]::MaxValue))][UInt16]$DurationInSeconds = 60,
[Parameter()][UInt32]$UsnConsumptionThreshold = 1000000,
[Parameter()][String[]]$DriveLetter = $Env:SystemDrive,
[Parameter()][Bool]$LogEvent = $True)
If (-Not((New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)))
{
Write-Error "This cmdlet requires administrative privileges and UAC elevation."
Return
}
$UsnMeasurementBeforeCollection = @()
# Collect a USN Journal measurement for each drive specified and add it to the collection.
Foreach ($Drive In $DriveLetter)
{
$UsnMeasurementBeforeCollection += Query-UsnJournal -DriveLetter $Drive
}
Start-Sleep -Seconds $DurationInSeconds
$UsnMeasurementAfterCollection = @()
Foreach ($Drive In $DriveLetter)
{
$UsnMeasurementAfterCollection += Query-UsnJournal -DriveLetter $Drive
}
Foreach ($Drive In $DriveLetter)
{
$Drive = $Drive.Trim(':').Trim()
$Before = $UsnMeasurementBeforeCollection | Where { ($_.'Drive Letter').StartsWith($Drive) }
$After = $UsnMeasurementAfterCollection | Where { ($_.'Drive Letter').StartsWith($Drive) }
$UsnMeasurementObject = New-Object PSObject
$UsnMeasurementObject | Add-Member -MemberType NoteProperty -Name 'Drive Letter' -Value $Before.'Drive Letter'
$UsnMeasurementObject | Add-Member -MemberType NoteProperty -Name 'Journal ID' -Value $After.'USN Journal ID'
$UsnMeasurementObject | Add-Member -MemberType NoteProperty -Name 'DurationInSeconds' -Value $DurationInSeconds
$UsnMeasurementObject | Add-Member -MemberType NoteProperty -Name 'USNs Consumed' -Value (($After.'Next USN') - ($Before.'Next USN'))
$UsnMeasurementObject | Add-Member -MemberType NoteProperty -Name 'Threshold' -Value $UsnConsumptionThreshold
If ($UsnMeasurementObject.'USNs Consumed' -GT $UsnConsumptionThreshold)
{
$UsnMeasurementObject | Add-Member -MemberType NoteProperty -Name 'Threshold Crossed' -Value $True
}
Else
{
$UsnMeasurementObject | Add-Member -MemberType NoteProperty -Name 'Threshold Crossed' -Value $False
}
Write-Output $UsnMeasurementObject
If ($LogEvent)
{
# Register the event log source... squelch the error if the event log source has already been registered.
Try
{
New-EventLog -LogName System -Source 'Measure-UsnConsumption' -ErrorAction Stop
}
Catch
{
If ($_.Exception.Message -NotLike "*already registered*")
{
Write-Error $_
}
}
If ($UsnMeasurementObject.'Threshold Crossed')
{
Write-EventLog -LogName System -Source 'Measure-UsnConsumption' -EntryType Error -EventId 2288 -Message "USN journal consumption on drive $Drive is high. This can be a predictor of failure in services that utilize the USN journal such as FRS and DFSR. Investigate applications and services causing high disk activity using Resource Monitor.`n`nDrive Letter: $($UsnMeasurementObject.'Drive Letter')`nJournal ID: $($UsnMeasurementObject.'Journal ID')`nMeasurement Duration: $($UsnMeasurementObject.'DurationInSeconds') seconds`nUSNs Consumed: $($UsnMeasurementObject.'USNs Consumed')`nThreshold: $($UsnMeasurementObject.'Threshold')`nThreshold Crossed: $($UsnMeasurementObject.'Threshold Crossed')"
}
Else
{
Write-EventLog -LogName System -Source 'Measure-UsnConsumption' -EntryType Information -EventId 2287 -Message "This informational event details the rate of USN consumption on the $Drive drive. It is currently below the configured threshold.`n`nDrive Letter: $($UsnMeasurementObject.'Drive Letter')`nJournal ID: $($UsnMeasurementObject.'Journal ID')`nMeasurement Duration: $($UsnMeasurementObject.'DurationInSeconds') seconds`nUSNs Consumed: $($UsnMeasurementObject.'USNs Consumed')`nThreshold: $($UsnMeasurementObject.'Threshold')`nThreshold Crossed: $($UsnMeasurementObject.'Threshold Crossed')"
}
If (-Not(Test-Path -Path HKLM:\SOFTWARE\Measure-UsnJournal\ -PathType Container))
{
New-Item HKLM:\SOFTWARE\Measure-UsnJournal\ | Out-Null
}
[String]$RegistryValue = [String]::Empty
Try
{
$RegistryValue = (Get-ItemProperty HKLM:\SOFTWARE\Measure-UsnJournal $Drive -ErrorAction Stop).$Drive
}
Catch
{
}
If ($RegistryValue.Length -GT 0)
{
If ($RegistryValue -NotLike ($UsnMeasurementObject).'Journal ID')
{
Write-EventLog -LogName System -Source 'Measure-UsnConsumption' -EntryType Error -EventId 2289 -Message "USN journal ID change detected since the last time this script ran! This can be a predictor of failure in services that utilize the USN journal such as FRS and DFSR. Examine FRS or DFSR logs to ensure that the service is healthy.`n`nDrive Letter: $($UsnMeasurementObject.'Drive Letter')`nPrevious Journal ID: $RegistryValue`nNew Journal ID: $($UsnMeasurementObject.'Journal ID')`nMeasurement Duration: $($UsnMeasurementObject.'DurationInSeconds') seconds`nUSNs Consumed: $($UsnMeasurementObject.'USNs Consumed')`nThreshold: $($UsnMeasurementObject.'Threshold')`nThreshold Crossed: $($UsnMeasurementObject.'Threshold Crossed')"
}
Set-ItemProperty HKLM:\SOFTWARE\Measure-UsnJournal $Drive -Value ($UsnMeasurementObject).'Journal ID' -Force | Out-Null
}
Else
{
New-ItemProperty HKLM:\SOFTWARE\Measure-UsnJournal $Drive -Value ($UsnMeasurementObject).'Journal ID' -Force | Out-Null
}
}
}
}
Measure-UsnConsumption
#Useage:
#Measure-UsnConsumption [-DurationInSeconds][-UsnConsumptionThreshold][-DriveLetter][-DriveLetter]
# Switches:
# -DurationInSeconds Duration between the sample of NextUSN. Default is 60 seconds
# -UsnConsumptionThreshold The threshold that a event log error will be generated based on the difference between the new NextUSN samples. Default is 1,000,000
# -DriveLetter The volume to run the test against. Default is the volume of the system drive.
# -LogEvent Log results in System event log. Boolean. Default is $True