Summary: Microsoft Scripting Guy, Ed Wilson, talks about using the Windows PowerShell PSCX Get-Hash cmdlet to get hash files in a directory.
Hey, Scripting Guy! I have a question that I hope will not require a lot of work on your part. I need to find the MD5 hash of files and folders. I use this information to determine if something has changed on a system. The problem is that everything I have seen appears to make this really complicated. Is there anything you can do that will make this easier?
—MO
Hello MO,
Microsoft Scripting Guy, Ed Wilson, is here. It is official. I am going to be doing a book signing at Microsoft TechEd 2012. I will be autographing copies of my Windows PowerShell 2.0 Best Practices book that was published by Microsoft Press. Incidentally, the autograph session will take place at the O’Reilly booth on Tuesday at 10:30 (refer to the following blog for a complete schedule: The Scripting Guys Reveal Their TechEd 2012 Schedule. You will want to get to the booth early because we will be giving away autographed copies to the first 25 people in line. If you have a copy that you want to ensure gets signed, bring it to the book signing. Or just bring it along to the Scripting Guys booth, where I will also be glad to sign books, T-shirts, hats…whatever you happen to have (not blank checks, however).
Note This is the third in a series of four Hey, Scripting Guy! blogs about using Windows PowerShell to facilitate security forensic analysis of a compromised computer system. The intent of the series is not to teach security forensics, but rather to illustrate how Windows PowerShell could be utilized to assist in such an inquiry. The first blog discussed using Windows PowerShell to capture and to analyze process and service information. The second blog talked about using Windows PowerShell to save event logs in XML format and perform offline analysis.
MD5 hashing of files
MO, actually I do not think that doing a MD5 hash is all that complicated—a bit tedious, but not overly complicated. The MD5Class is documented on MSDN, and it is not too bad. However, there is no real reason for IT Pros to have to mess with the .NET Framework classes if they do not want to do so. The reason? Well, the reason is that the Windows PowerShell Community Extensions includes a function that will get the MD5 hash for you. It works just like any other Windows PowerShell function or cmdlet, and it is extremely easy to use.
Note I have written several blogs about the PowerShell Community Extensions (PSCX). One especially well received blog, Tell Me About PowerShell Community Extensions, was written by one of the developers of the project, Windows PowerShell MVP, Keith Hill. To obtain the PSCX, download them from CodePlex, and follow the installation instructions in Keith’s blog.
After you install the PSCX, import the module by using the following command:
Import-Module pscx
The cmdlet you want to use is the Get-Hash cmdlet. It accepts piped input for the path to the file to hash, and it returns an object with the path to the file and the hash value. You can specify the type of hash to use (MD5, SHA1, SHA256, SHA384, SHA512, or RIPEMD160), but this is not a requirement because it selects an MD5 hash by default. The Get-Hash cmdlet does not hash directories, only files. Therefore, an error returns (Access is denied) when the Get-Hash cmdlet runs across directories. There are several approaches to dealing with this issue:
- Ignore the error.
- Tell the cmdlet to ignore the error by specifying an error action.
- Develop a filter (by using the Where-Object) that returns only files.
Ignore the error
The following command generates an MD5 hash for every file in the c:\fso directory:
dir c:\fso -Recurse | Get-Hash
The command and its associated output are shown here (errors appear in the output due to the presence of child directories).
Tell the cmdlet to ignore the error
One way to deal with expected errors is to tell the cmdlet to ignore the error for you. Windows PowerShell cmdlets implement a common parameter named ErrorAction. Use of the ErrorAction ubiquitous parameter permits you to control the error action preference on a command-by-command basis. You can achieve the behavior of the VBScript script On Error Resume Next functionality if you so desire, but the real power is that it is doable on a cmdlet-by-cmdlet basis. There is also a global variable named $ErrorActionPreference that enables you to set the behavior on a global basis. There are four allowable values for the ActionPreference. These values are shown here (retrieved by using the GetValues static method from the System.Enum .NET Framework class).
PS C:\> [enum]::GetValues("System.Management.Automation.ActionPreference")
SilentlyContinue
Stop
Continue
Inquire
The SilentlyContinue value tells Windows PowerShell to not report any errors, but to continue to attempt to process the next command. This is the On Error Resume Next type of setting. Stop means that Windows PowerShell will halt execution when reaching an error. Continue means that Windows PowerShell will inform you of the error, and will then continue processing if possible (this is the default behavior). Inquire means that Windows PowerShell will let you know an error occurred, and ask you if you want to continue execution or halt the command. There are numerical equivalents to the values, and therefore, SilentlyContinue is equal to 0. The ErrorAction common parameter also has a parameter alias of EA. Therefore, by using the previous information, the following command appears.
dir c:\fso -Recurse | Get-Hash -ea 0
The command to retrieve an MD5 hash value for each file in the c:\fso directory and to suppress any errors that may arise is shown here, along with the output associated with the command.
Develop a filter
To create a filter that only returns files, use the psiscontainer property with Where-Object. Because I do not want to retrieve any ps container type of objects, I use the not operator (!) to tell Windows PowerShell that I want no containers. This approach is shown here.
dir c:\fso -Recurse | Where-Object {!$_.psiscontainer } | get-hash
The command and the output from the command are shown here.
From a performance standpoint, filtering out only files was a bit faster—not much, but a little: 3.06 total seconds as opposed to 3.28 total seconds. Due to file system caching, I rebooted my computer, ran the Measure-Command cmdlet, rebooted the computer, and ran the Measure-Command cmdlet. This eliminated caching from the equation. The commands and the associated output from the commands are shown here.
PS C:\> Measure-Command { dir c:\fso -Recurse | Where-Object {!$_.psiscontainer } | g
et-hash }
Days : 0
Hours : 0
Minutes : 0
Seconds : 3
Milliseconds : 61
Ticks : 30610419
TotalDays : 3.54287256944444E-05
TotalHours : 0.000850289416666667
TotalMinutes : 0.051017365
TotalSeconds : 3.0610419
TotalMilliseconds : 3061.0419
PS C:\> Measure-Command { dir c:\fso -Recurse | Get-Hash -ea 0 }
Days : 0
Hours : 0
Minutes : 0
Seconds : 3
Milliseconds : 280
Ticks : 32803013
TotalDays : 3.79664502314815E-05
TotalHours : 0.000911194805555555
TotalMinutes : 0.0546716883333333
TotalSeconds : 3.2803013
TotalMilliseconds : 3280.3013
MO, that is all there is to using the Get-Hash cmdlet from the PSCX to obtain hash values from files in a folder. Security Week will continue tomorrow when I will talk about storing and comparing hash values to detect changes to files.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy