Thursday, November 30, 2006 1:00 AM bart

PowerShell - A file hasher cmdlet and the Extended Type System explained

Introduction

Today (11/29/06) I received a mail from one of my blog readers:

Hi Bart,

in the following Block you mention an powershell commandlet for creating an SHA hash:

http://community.bartdesmet.net/blogs/bart/archive/2006/06/23/4106.aspx

Could you please publish or mail me some code snipes? My own code wont work proper.

THX for sharing so much knowledge about .NET.

Regards,

Thomas

Apparently I promised so time in the past to upload a cmdlet for file hashing but it never made it to my blog. So here it is today.

 

A file hasher cmdlet

Let's create a cmdlet for file hashing, called get-hash. It should take two parameters: one with the algorithm desired (SHA1, MD5, SHA256, SHA384, SHA512) and one with the file. The latter one can either be passed from the command line (e.g. dir *.cs | get-hash sha1 should work fine) or using some aliases specifying the name of the file as a string. Taking all these requirements together, we end up with the following:

1 using System; 2 using System.ComponentModel; 3 using System.IO; 4 using System.Management.Automation; 5 using System.Security.Cryptography; 6 using System.Text; 7 8 [Cmdlet("get", "hash")] 9 public class HashCmdlet : PSCmdlet 10 { 11 private string algorithm; 12 13 [Parameter(Position = 0, Mandatory = true)] 14 public string Algorithm 15 { 16 get { return algorithm; } 17 set { algorithm = value; } 18 } 19 20 private string file; 21 22 [Alias("File", "Name")] 23 [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true)] 24 public string FullName 25 { 26 get { return file; } 27 set { file = value; } 28 } 29 30 protected override void ProcessRecord() 31 { 32 HashAlgorithm algo = HashAlgorithm.Create(algorithm); 33 if (algo != null) 34 { 35 StringBuilder sb = new StringBuilder(); 36 using (FileStream fs = new FileStream(file, FileMode.Open)) 37 foreach(byte b in algo.ComputeHash(fs)) 38 sb.Append(b.ToString("x2")); 39 WriteObject(sb.ToString()); 40 } 41 else 42 { 43 string s = String.Format("Algorithm {0} not found.", algorithm); 44 ErrorRecord err = new ErrorRecord(new ArgumentException(s), s, ErrorCategory.InvalidArgument, null); 45 WriteError(err); 46 } 47 } 48 } 49 50 [RunInstaller(true)] 51 public class HashSnapin : PSSnapIn 52 { 53 public override string Name { get { return "FileHasher"; } } 54 public override string Vendor { get { return "Bart"; } } 55 public override string Description { get { return "Computes file hashes."; } } 56 }

A few remarks:

  • The second parameter, FullName, can be taken from the pipeline (ValueFromPipelineByPropertyName set to true). The reason for the chosen name "FullName" is the fact that a System.IO.FileInfo object has such a property name, and we want that property name to match our property name.
  • Using aliases, the second parameter can be supplied using File or Name too.
  • Line 39 write the string representation of the hash to the pipeline using WriteObject. Alternatively, one might also output the byte array retrieved from ComputeHash (line 37) but that would be less user-friendly in my opinion.
  • In case an unknown algorithm is specified, we construct an ErrorRecord object that's passed on to the shell using WriteError.
  • Stuff on lines 50 to 56 creates a simple snap-in that's used to make the cmdlet available.

 

Compilation and installation instructions

Download the code and execute the following steps on a VS2005 command line:

  • Make sure the PATH environment variable has csc.exe and installutil.exe on it (set PATH=%PATH%;%windir%\Microsoft.NET\Framework\v2.0.50727)
  • Save the file as hashcmdlet.cs
  • Copy System.Management.Automation from the GAC (%windir%\assembly\GAC_MSIL\System.Management.Automation\<version_pktoken>\System.Management.Automation.dll) to the current folder with the hashcmdlet.cs code file
  • Execute csc /t:library /r:System.Management.Automation.dll hashcmdlet.cs
  • Install the snap-in using installutil -i hashcmdlet.dll

 

Demo

Below you can see a sample of our get-hash cmdlet:

1 PS C:\temp> add-pssnapin filehasher 2 3 PS C:\temp> type extfi.ps1xml 4 <Types> 5 <Type> 6 <Name>System.IO.FileInfo</Name> 7 <Members> 8 <ScriptProperty> 9 <Name>MD5</Name> 10 <GetScriptBlock> 11 get-hash md5 $this 12 </GetScriptBlock> 13 </ScriptProperty> 14 <ScriptProperty> 15 <Name>SHA1</Name> 16 <GetScriptBlock> 17 get-hash sha1 $this 18 </GetScriptBlock> 19 </ScriptProperty> 20 </Members> 21 </Type> 22 </Types> 23 24 PS C:\temp> update-typedata extfi.ps1xml 25 26 PS C:\temp> dir *.cs | gm -type P* 27 28 TypeName: System.IO.FileInfo 29 30 Name MemberType Definition 31 ---- ---------- ---------- 32 PSChildName NoteProperty System.String PSChildName=bar.cs 33 PSDrive NoteProperty System.Management.Automation.PSDriveInfo PS... 34 PSIsContainer NoteProperty System.Boolean PSIsContainer=False 35 PSParentPath NoteProperty System.String PSParentPath=Microsoft.PowerS... 36 PSPath NoteProperty System.String PSPath=Microsoft.PowerShell.C... 37 PSProvider NoteProperty System.Management.Automation.ProviderInfo P... 38 Attributes Property System.IO.FileAttributes Attributes {get;set;} 39 CreationTime Property System.DateTime CreationTime {get;set;} 40 CreationTimeUtc Property System.DateTime CreationTimeUtc {get;set;} 41 Directory Property System.IO.DirectoryInfo Directory {get;} 42 DirectoryName Property System.String DirectoryName {get;} 43 Exists Property System.Boolean Exists {get;} 44 Extension Property System.String Extension {get;} 45 FullName Property System.String FullName {get;} 46 IsReadOnly Property System.Boolean IsReadOnly {get;set;} 47 LastAccessTime Property System.DateTime LastAccessTime {get;set;} 48 LastAccessTimeUtc Property System.DateTime LastAccessTimeUtc {get;set;} 49 LastWriteTime Property System.DateTime LastWriteTime {get;set;} 50 LastWriteTimeUtc Property System.DateTime LastWriteTimeUtc {get;set;} 51 Length Property System.Int64 Length {get;} 52 Name Property System.String Name {get;} 53 MD5 ScriptProperty System.Object MD5 {get=get-hash md5 $this;} 54 Mode ScriptProperty System.Object Mode {get=$catr = "";... 55 SHA1 ScriptProperty System.Object SHA1 {get=get-hash sha1 $this;} 56 57 PS C:\temp> dir *.cs | format-table Name,MD5,SHA1 58 59 Name MD5 SHA1 60 ---- --- ---- 61 bar.cs d541e9719077844ba1fa136... 8662e86f3302578a59da5e... 62 downloadfilecmdlet.cs 0c74a0c905f3b1cd6e22d52... ab3c4dcee4f9e3c48daded... 63 hashcmdlet.cs 41b01139d6168df3f3cec13... dd478c60f77b19b64fa0d7... 64 test.cs 477405d2be4a8f327d39a01... c632fe67a71baa0f333675... 65 66 PS C:\temp> dir *.cs | format-list Name,MD5,SHA1 67 68 Name : bar.cs 69 MD5 : d541e9719077844ba1fa13626f5122cb 70 SHA1 : 8662e86f3302578a59da5e9c936b69ab0d4ff9aa 71 72 Name : downloadfilecmdlet.cs 73 MD5 : 0c74a0c905f3b1cd6e22d52831b92b31 74 SHA1 : ab3c4dcee4f9e3c48daded97f01ee01e8c572a2a 75 76 Name : hashcmdlet.cs 77 MD5 : 41b01139d6168df3f3cec13b9663e633 78 SHA1 : dd478c60f77b19b64fa0d7c62944ec1b948419c9 79 80 Name : test.cs 81 MD5 : 477405d2be4a8f327d39a015db255fdf 82 SHA1 : c632fe67a71baa0f333675f5cdc16fc547772c33 83 84 PS C:\temp> get-hash MD5 bar.cs 85 d541e9719077844ba1fa13626f5122cb 86 87 PS C:\temp> get-hash SHA1 bar.cs 88 8662e86f3302578a59da5e9c936b69ab0d4ff9aa 89 90 PS C:\temp> get-hash SHA256 bar.cs 91 a1e6764cf77d02804e909427aff62ade6b9894924a69284f3d83fd0d2904548b 92 93 PS C:\temp> get-hash SHA384 bar.cs 94 875bde9e789f88e76aa9fe18f82adc8a8beb920cdf1d50692a0b9473ecc296a750e888844a184d7 95 6e610d434a3bec3a5 96 97 PS C:\temp> get-hash SHA512 bar.cs 98 f88b40bd4618dcb99e17af14c7f2368b00ea55a9b7d6d71e73d519e197ca3d6c3847fd46834fb1e 99 c4acb9c45729441ac76611de2f7f86b032b59e1a3b7384a3a 100 101 PS C:\temp> get-hash bla bar.cs 102 get-hash : Algorithm bla not found. 103 At line:1 char:9 104 + get-hash <<<< bla bar.cs

This sample is available in the download as well. Let's explain it in a bit more detail:

  • One line 1, the snap-in is loaded (which should be installed using installutil -i hashcmdlet.dll in the previous paragraph) using add-pssnapin.
  • Next, we leverage the power of the ETS (Extended Type System) to add two script properties MD5 and SHA1 to the FileInfo object. To do this, you should write an xml file which contains the line 4 to 22 and save it as a .ps1xml file. This file is loaded using update-typedata in line 24. This file is available in the download as well.
  • Now notice that FileInfo is extended with the MD5 and SHA1 ScriptProperty members using a get-member invocation on the output for get-childitem *.cs (assuming you have .cs files in your c:\temp folder). Lines 53 and 55 contain the newly added properties.
  • To use these script properties, observe the commands invoked in lines 57 and 66. These use format-table and format-list to visualize the additional properties.
  • Of course you can invoke get-hash directly too, as shown on lines 84, 87, 90, 93, 97 and 101. All supported algorithms are illustrated.

One drawback of our cmdlet is that it can't report progress when a hash operation takes a bit of time, especially for larger files. So, use it with care and rely on an explicit call to get-hash when you need to calculate a hash.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: PowerShell - A file hasher cmdlet and the Extended Type System explained

Thursday, November 30, 2006 5:07 AM by Thomas

THX, my missing link was .ToString("x2") here is a bread crumb to create .sha1 files in a directory ############################################################################################### # version of http://blogs.msdn.com/powershell/archive/2006/04/25/583225.aspx # # functions ############################################################################################### function getSHA1([System.IO.FileInfo] $file = $(throw 'Usage: getSHA1 [System.IO.FileInfo]')) { $stream = $null $hashAlgorithm = new-object System.Security.Cryptography.SHA1CryptoServiceProvider $stream = $file.OpenRead() $hashByteArray = $hashAlgorithm.ComputeHash($stream) $stream.Close() trap { if ($stream -ne $null) { $stream.Close() } break } foreach($hashItem in $hashByteArray){ $returnString += $hashItem.ToString("X2") } return $returnString } function createSHA1File([System.IO.FileInfo] $file = $(throw 'Usage: getSHA1 [System.IO.FileInfo]')) { getSHA1($file) > $("{0}.sha1"-f $file.fullname) } ############################################################################################### # script ############################################################################################### dir *.txt | foreach { createSHA1File $_ }

# Windows PowerShell 2.0 Feature Focus - Script cmdlets

Saturday, March 22, 2008 9:09 AM by B# .NET Blog

Two weeks ago I did a little tour through Europe spreading the word on a couple of our technologies including

# System.Management.Automation.dll missing? | wehuberconsultingllc.com

Pingback from  System.Management.Automation.dll missing? | wehuberconsultingllc.com

# Using Powershell for MD5 Checksums | Brian Hartsock's Blog

Saturday, December 13, 2008 2:57 PM by Using Powershell for MD5 Checksums | Brian Hartsock's Blog

Pingback from  Using Powershell for MD5 Checksums | Brian Hartsock's Blog