# encoding: ascii
# api: powershell
# title: Test-Hash
# description: Test md5/sha1/etc file hashes.
# version: 0.1
# type: function
# author: Joel Bennett
# license: CC0
# function: Test-Hash
# x-poshcode-id: 3414
# x-archived: 2012-05-16T21:39:23
# x-published: 2012-05-14T13:01:00
#
# I’ve enhanced this script to make it easy to store the results in a csv file and then test them later. See Example 1.
# (This one fixes a missing “m” in the previous scrip)
#
function Test-Hash {
#.Synopsis
# Test the HMAC hash(es) of a file
#.Description
# Takes the HMAC hash of a file using specified algorithm, and optionally, compare it to a baseline hash
#.Example
# C:\PS>ls .\Bin\Release -recurse | Test-Hash -BasePath .\Bin\Release -Type SHA256 | Export-CSV ~\Hashes.csv
# C:\Program Files\MyProduct>Import-CSV ~\Hashes.csv | Test-Hash
#
# This example shows how to take the hash of a collection of files and store them in a csv file, and then later verify that the files in a secondary location match the originals exactly.
#
#.Example
# Test-Hash npp.5.3.1.Installer.exe -HashFile npp.5.3.1.release.md5
#
# Searches the provided hash file for a line matching the "npp.5.3.1.Installer.exe" file name
# and take the hash of the file (using the extension of the HashFile as the Type of Hash).
#
#.Example
# Test-Hash npp.5.3.1.Installer.exe 360293affe09ffc229a3f75411ffa9a1 MD5
#
# Takes the MD5 hash and compares it to the provided hash
#
#.Example
# Test-Hash npp.5.3.1.Installer.exe 5e6c2045f4ddffd459e6282f3ff1bd32b7f67517
#
# Tests all of the hashes against the provided (Sha1) hash
[CmdletBinding(DefaultParameterSetName="NoExpectation")]
PARAM(
# The path to the file to hash
[Parameter(Position=0,Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("PSPath")]
[string]$Path,
# When hashing many files in folders, use this to convert to relative paths (so you can compare again in a different location)
[Parameter(Position=2,Mandatory=$false,ParameterSetName="NoExpectation")]
[string]$BasePath,
# Supports hashing against a .md5 or .sha1 file such as frequently found on open source servers or torrent sites
[Parameter(Position=1,Mandatory=$true,ParameterSetName="FromHashFile")]
[string]$HashFileName,
# Supports passing in the expected hash (particularly useful when piping input from a previous run)
[Parameter(Position=2,Mandatory=$true,ParameterSetName="ManualHash", ValueFromPipelineByPropertyName=$true)]
[Parameter(Position=2,Mandatory=$false,ParameterSetName="FromHashFile")]
[ALias("Hash")]
[string]$ExpectedHash = $(if($HashFileName){ ((Get-Content $HashFileName) -match $Path)[0].split(" ")[0] }),
# The algorithm to use when hashing
[Parameter(Position=1,Mandatory=$true,ParameterSetName="ManualHash", ValueFromPipelineByPropertyName=$true)]
[Parameter(Position=1,Mandatory=$false,ParameterSetName="NoExpectation")]
[ValidateSet("MD5","SHA1","SHA256","SHA384","SHA512","RIPEMD160")]
[string[]]$Algorithm = $(if($HashFileName){ [IO.Path]::GetExtension((Convert-Path $HashFileName)).Substring(1) } else { "SHA256" })
)
begin {
$ofs=""
if($BasePath) {
Push-Location $BasePath
}
}
process {
if($BasePath) {
$Path = Resolve-Path $Path -Relative
}
if(Test-Path $Path -Type Container) {
# I'd like to support recursing all the files, but for now, just skip
Write-Warning "Cannot calculate hash for directories: '$Path'"
return
}
$Hashes = @(
foreach($Type in $Algorithm) {
# Take the Hash without storing the bytes (ouch)
[string]$hash = [Security.Cryptography.HashAlgorithm]::Create( $Type ).ComputeHash( [IO.File]::ReadAllBytes( (Convert-Path $Path) ) ) | ForEach { "{0:x2}" -f $_ }
# Output an object with the hash, algorithm and path
New-Object PSObject -Property @{ Path = $Path; Algorithm = $Type; Hash = $Hash }
}
)
if($ExpectedHash) {
# Check all the hashes to see if any of them match
if( $Match = $Hashes | Where { $_.Hash -eq $ExpectedHash } ) {
Write-Verbose "Matched hash:`n$($Match | Out-String)"
# Output an object with the hash, algorithm and path
New-Object PSObject -Property @{ Path = $Match.Path; Algorithm = $Match.Algorithm; Hash = $Match.Hash; Matches = $True }
} else {
Write-Verbose "Failed to match hash:`n$($PSBoundParameters | Out-String)"
# Output an object with the first hash, algorithm and path
New-Object PSObject -Property @{ Path = $Hashes[0].Path; Algorithm = $Hashes[0].Algorithm; Hash = $Hashes[0].Hash; Matches = $False }
}
} else {
Write-Output $Hashes
}
}
end {
if($BasePath) {
Pop-Location
}
}
}