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: Windows PowerShell