Thursday, December 28, 2006

UPDATE NUMBER 2:

Kevin commented about a flaw in my script wherein it would keep files open until the PowerShell process was closed - a scenario I should've tested but, in all of the excitement (ha), I missed. You won't believe what the problem was, either. I forgot parentheses on my $inStream.Close() method (it looked like $inStream.Close instead of $inStream.Close() ). The reason the trap { } script block didn't catch it is because the statement was still valid... it would just display the MethodInfo like below:

MemberType          : Method
OverloadDefinitions : {System.Void Close()}
TypeNameOfValue     : System.Management.Automation.PSMethod
Value               : System.Void Close()
Name                : Close
IsInstance          : True

The [void] statement before it prevented the output from displaying, hence me not catching the bug. The script has now been fixed (hopefully). Thanks for the catch Kevin!

UPDATE:

Ignore my script. Go download the PowerShell Community Extensions instead. It has a great Get-Hash script that does everything that my script does and more. I wish I had downloaded it sooner :-)

 

Jeffrey Snover posted a suggestion on the PowerShell blog recently to post automation scripts people have written in PowerShell that they use. Well, here is a script I wrote that I also submitted for the PowerShell Scripting Contest a few weeks back. The script is quite basic and is based on other code I found, but I added a little bit to it to handle some of my own needs. It calculates file hashes based on a specified hash algorithm (i.e. SHA1, MD5, etc). I like to use it to determine if a large file I've downloaded (like an ISO from MSDN) is a good file or if it was corrupted during the download.

Here is Calc-Hash.ps1:

param (
	[string] $inFile = $(throw "Usage: Calc-Hash.ps1 file.txt [sha1|md5] "),
	[string] $hashType = "sha1"
)

function Main
{
	if ($hashType -eq "")
	{
		throw "Usage: Calc-Hash.ps1 file.txt [sha1|md5] "
	}
	
	if ($hashType -eq "sha1")
	{
		$provider = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
	}
	elseif ($hashType -eq "md5")
	{
		$provider = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
	}
	else
	{
		throw "Unsupported hash type $hashType"
	}
		
	$inFileInfo = New-Object System.IO.FileInfo($inFile)
	if (-not $inFileInfo.Exists)
	{
		# If the file can't be found, try looking for it in the current directory.
		$inFileInfo = New-Object System.IO.FileInfo("$pwd\$inFile")
		if (-not $inFileInfo.Exists)
		{
			throw "Can't find $inFileInfo"
		}
	}

	$inStream = $inFileInfo.OpenRead()
	$hashBytes = $provider.ComputeHash($inStream)
	[void] $inStream.Close()
	
	trap
	{
		if ($inStream -ne $null)
		{
			[void] $inStream.Close()
		}
		break
	}
	
	foreach ($byte in $hashBytes)
	{
		Write-Host -NoNewLine $byte.ToString("X2")
	}
	
	Write-Host
}

. Main
Tuesday, January 23, 2007 4:24:24 PM (Central Standard Time, UTC-06:00)
Did you get my last comment? I'm not sure your comment feature is working.

I use your script to hash a file "working.html". It produces the hash as expected. Howerver the file becomes locked and can not be saved after editing unless I close my powershell session after hashing the file.

Is there some way to release this lock without closing my powershell session?

- Kevin Criss
Kevin Criss
Tuesday, January 23, 2007 7:02:13 PM (Central Standard Time, UTC-06:00)
Kevin,

Wow, I completely missed that. I've updated the post to correct the problem, but the basic fix is to make sure the $inStream.Close() method has those closing parentheses.

Thanks for the heads up!

David
Wednesday, January 24, 2007 1:54:36 PM (Central Standard Time, UTC-06:00)
Thanks, I'll check it out. Here's an excerpt from my 1,500 line Uptime2HTML.PS1 script. It does more than just covert uptime.exe output to HTML.

###################################
# Script Block $Failsafe #
###################################
#
# Calc-Hash.ps1
# From David Mohundro's web site.
# A PowerShell script to calculate file hashes
# Thursday, December 28, 2006
# http://www.mohundro.com/blog/PermaLink,guid,b3e7081f-8249-4e37-a777-9afdfd0d9b3d.aspx
#
# I adapted David's script referenced above to enhance UpTime2HTML's internal security.
# I checked his script's results agains my Perl Digesting routines:
# $md5->clone->hexdigest method
# $sha1->clone->hexdigest method
# and they both checksum to the same results except my Perl script ouptuts its alpha
# characters in lower case. We can trust his PowerShell digest methods for MD5 and SHA1.
#
# We will digest uptime.exe before each use to minimize our attack surface. I suggest
# running Uptime2HTML in two-tiers: app-server/web-server for best practice.
#
# The risk: Uptime2HTML processes a list of servers using an account with *sufficient privileges* to do so.
# Therefore Microsoft's uptime.exe executable must not become compromised. We will store known
# digest values for uptime.exe within constant script variables that are locked inside of our
# digitally signed script and then compare these against the pre-runtime values of uptime.exe
# before each server query's use of uptime.exe. The script will not function if it ever becomes
# altered after signing. The script is also programmed not to run uptime.exe if its sums do not
# check out.
#
# This is a double fail-safe feature. Uptime2HTML only touches servers within its input file
# via Microsoft's uptime.exe. Additionally web server writes of UpTime2HTML's output are
# controlled via the constant script varilabe, $SitePath, which is also protected by the script's
# digital signature.
#
# 500 MD5 and SHA1 Digests of uptime.exe only takes 00:00:05.3593750 seconds to generate
#
# Tested 01-14-2007 - Coments: I think it works! Might have some issues releasing uptime.exe from
# the Powershell environment until I close the PoweShell session.
# Scheduling this script as a .bat file should remedy this though.
#
$FailSafe =
{
# ############################
#### The MD5 Digest MEthod #
#############################
$script:UptimeMD5constant = "415EDA8D64E4B487A78218212F5DB282" # Uptime.exe
$global:MD5provider = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
# $infile = "c:\program files\scripts\working.htm"
$infile = "C:\windows\system32\uptime.exe"
$inFileInfo = New-Object System.IO.FileInfo($infile)
if (-not $inFileInfo.Exists)
{
$Script:BadUptimeCheckSum = "True"
Throw "Failsafe Script Block: Can't find $inFileInfo"
}
$global:inStream = $inFileInfo.OpenRead()
$global:MD5hashBytes = $MD5provider.ComputeHash($inStream)
[void] $inStream.Close()
trap
{
if ($instream -ne $null)
{
[void] $instream.Close()
}
break
}
$global:MD5chunk = ""
$global:MD5result = ""
foreach ($byte in $MD5hashBytes)
{
# Write-Host -NoNewLine $byte.ToString("X2")
$global:MD5chunk = $byte.ToString("X2")
$global:MD5result = $global:MD5result+$global:MD5chunk
}
# Write-Host
# "$MD5result = MD5 Digest for file $infile" | Out-host
If ($MD5result -ne $script:UptimeMD5constant)
{
$Script:BadUptimeCheckSum = "True"
Throw "Failsafe Script Block: MD5 CheckSum Failure"
}
#
##############################
#### The SHA1 Digest Method #
##############################
$script:UptimeSHA1constant = "B565A5B717497950B2B96B8A1EF809F2509F754E" # Uptime.exe
$SHA1provider = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
# $infile = "c:\program files\scripts\working.htm"
$infile = "C:\windows\system32\uptime.exe"
$SHA1provider = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
$inFileInfo = New-Object System.IO.FileInfo($infile)
if (-not $inFileInfo.Exists)
{
$Script:BadUptimeCheckSum = "True"
Throw "Failsafe Script Block: Can't find $inFileInfo"
}
$inStream = $inFileInfo.OpenRead()
$SHA1hasbytes = $SHA1provider.ComputeHash($inStream)
$inStream = $inFileInfo.OpenRead()
[void] $inStream.Close()
trap
{
if ($instream -ne $null)
{
[void] $instream.Close()
}
break
}
$global:SHA1chunk = ""
$global:SHA1result = ""
$SHA1hashBytes = $SHA1provider.ComputeHash($inStream)
foreach ($byte in $SHA1hashBytes)
{
# Write-Host -NoNewLine $byte.ToString("X2")
$global:SHA1chunk = $byte.ToString("X2")
$global:SHA1result = $global:SHA1result+$global:SHA1chunk
}
# Write-Host
# "$SHA1result = SHA1 Digest for file $infile" | Out-host
If ($SHA1result -ne $script:UptimeSHA1constant)
{
$Script:BadUptimeCheckSum = "True"
Throw "Failsafe Script Block: SHA1 CheckSum Failure"
}
}

#
###################################
# Script Block Time 500 FailSafes #
###################################
#
$FiveHundredFailSafes =
{
$DigestTImeCheck = get-date
for ($digestCntr=0; $digestCntr -lt 500; $DigestCntr++) {&$FailSafe}
$span = [TimeSpan]((get-date) - $DigestTimeCheck)
$DigestGenerationtime=$global:span.tostring()
"$digestCntr Digests of uptime.exe takes $DigestGenerationtime seconds to generate" | Out-host
}
#
Kevin Criss
Wednesday, January 24, 2007 9:26:30 PM (Central Standard Time, UTC-06:00)
Thanks for sharing the code!

I noticed in the script comments that you mentioned that your Perl script output in lowercase characters while the PowerShell one output in uppercase. It didn't sound like a big deal, but if you're interested, you can change the $byte.ToString("X2") line to instead say $byte.ToString("x2") with a lowercase x, it should be lowercase.

The only reason I coded it with uppercase output is because I was comparing the output to an existing program I had been using (HashOnClick at http://www.2brightsparks.com/freeware/freeware-hub.html).

David

Comments are closed.