Skip to content

Commit eb83a87

Browse files
authored
Update Compare-FileHash.ps1
- refactored auto-detection of algorithm based on its length to be more concise and easier to read - added a check to ensure mutual exclusivity of '-Algorithms' and '-Expected' now that auto-detection is used - added a check to remove duplicates of algorithms specified - clarified wording of some comments and error messages - minor logic improvements - rearranged some blocks for better flow - renamed some variables to improve readability - updated comment-based help
1 parent 105de4c commit eb83a87

File tree

1 file changed

+64
-61
lines changed

1 file changed

+64
-61
lines changed

Compare-FileHash.ps1

Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,32 @@
55
Compares the hashes of a list of files using various algorithms.
66
77
.DESCRIPTION
8-
The Compare-FileHash cmdlet will compare the hash values of a list of files. The files are
9-
passed to the cmdlet as a parameter, separated by commas. The cmdlet will use SHA512,
10-
or a list of user-specified algorithms, to perform the comparison. It will print the result
11-
of each hash comparison unless the -Quiet switch is passed. Finally, it will return either
12-
'MATCH' if all hash values matched or 'MISMATCH' if one of the hash values did not match.
8+
The Compare-FileHash cmdlet will compare the hash values of a list of files against each other,
9+
or an expected hash. Files are passed to the cmdlet using '-Files', separated by commas.
10+
The cmdlet will use SHA512, or a list of specified algorithms, to perform the comparison.
11+
It will return 'MATCH' if all hash values matched or 'MISMATCH' if one of the hash values did not match.
1312
1413
.PARAMETER Files
15-
The list of file paths, separated by commas, to compare the hashes of.
16-
A minimum of two paths must be supplied, however there is no upper limit.
14+
The list of file paths, separated by commas, from which to compare the hashes.
15+
A minimum of two paths must be supplied (or one path, if '-Expected' is passed), however there is no upper limit.
1716
1817
.PARAMETER Algorithms
1918
Determines which algorithm(s) are used to compute the specified files' hashes.
20-
You may pass any number of algorithms, separated by commas, which the Get-FileHash cmdlet supports.
21-
Passing "All" will run all algorithms, and if this parameter is not passed, it will default to SHA512.
19+
You may pass any number of supported algorithms, separated by commas.
20+
Passing 'All' will run all algorithms.When unspecified, SHA512 will be used.
2221
2322
.PARAMETER Expected
24-
Allows user to specify the hash they are expecting, and compares the file(s) against that,
23+
Allows you to specify the hash you are expecting, and compares the file(s) against it,
2524
rather than against each other. Passing this switch reduces the minimum '-Files' limit
26-
from 2 to 1, and limits '-Algorithms' to 1 type.
25+
from 2 to 1. '-Expected' cannot be passed with '-Algorithms'
2726
2827
.PARAMETER Quiet
2928
Suppresses the individual hash values from being printed;
3029
only the final result ('MATCH' or 'MISMATCH') will be printed.
3130
3231
.PARAMETER Fast
3332
Returns 'MATCH' if the first computed algorithm's hashes match.
34-
This skips the calculation and comparison of any subsequent algorithm's hashes if they are not needed.
33+
This skips the calculation and comparison of any subsequent algorithm's hashes.
3534
3635
.EXAMPLE
3736
Compare-FileHash -Files 'C:\file1.txt','C:\file2.txt'
@@ -49,7 +48,7 @@ and only print the final comparison result ('MATCH' or 'MISMATCH') without any f
4948
Compare-FileHash -Files 'C:\file1.txt','C:\file2.txt' -Fast -Algorithms All
5049
5150
In this example, the cmdlet will start comparing all algorithms' hashes of file1.txt and file2.txt and
52-
will return 'MATCH' immediately if the first algorithm matches, skipping the other algorithms.
51+
will return 'MATCH' immediately if the first algorithm matches, skipping the rest of the algorithms.
5352
5453
.EXAMPLE
5554
Compare-FileHash -Files 'C:\file1.txt','C:\file2.txt' -Algorithms SHA1,MD5,SHA384
@@ -58,10 +57,10 @@ In this example, the cmdlet will compare the SHA1, MD5, and SHA384 hashes of fil
5857
If any of the hashes do not match, the rest of the algorithms will not be computed, and 'MISMATCH' will be printed.
5958
6059
.EXAMPLE
61-
Compare-FileHash -Files 'C:\file1.txt' -Expected D41D8CD98F00B204E9800998ECF8427E
60+
Compare-FileHash -Files 'C:\file1.txt' -Expected DA39A3EE5E6B4B0D3255BFEF95601890AFD80709
6261
63-
In this example, the cmdlet will automatically detect that the input hash is MD5, based on its length
64-
(in this case, 32 characters), and compare file1.txt to that expected hash.
62+
In this example, the cmdlet will automatically detect that the input hash is SHA1, based on its length
63+
(in this case, 40 characters), and compare file1.txt to that expected hash.
6564
#>
6665

6766
function Compare-FileHash {
@@ -72,7 +71,7 @@ function Compare-FileHash {
7271

7372
[Parameter(Mandatory=$false)]
7473
[ValidateSet("SHA512","SHA384","SHA256","SHA1","MD5","All")]
75-
[string[]]$Algorithms = "SHA512",
74+
[string[]]$Algorithms,
7675

7776
[Parameter(Mandatory=$false)]
7877
[string]$Expected,
@@ -83,60 +82,62 @@ function Compare-FileHash {
8382
[Parameter(Mandatory=$false)]
8483
[switch]$Fast = $false
8584
)
85+
8686
# Oneshot variable on script scope to ensure column headers of Get-FileHash table are only printed once
8787
$script:tableHeaders = $false
8888

89-
# Lengths of each supported hash type, to validate length of hash passed with '-Expected'. No duplicate lengths allowed, would break '-Expected' algorithm detection.
90-
$hashLengths = @{ "MD5" = 32 ; "SHA1" = 40 ; "SHA256" = 64 ; "SHA384" = 96 ; "SHA512" = 128 }
91-
92-
# Ensure at least two file paths have been provided
93-
if ($Files.Count -lt 2 -and -not ($Expected)) { Write-Error "When '-Expected' is not specified, at least two file paths must be provided." ; return }
94-
95-
# Ensure supplied paths exist, and are files, not directories
96-
foreach ($file in $Files) {
97-
98-
if (-not (Test-Path $file)) {
99-
$invalidPath = $true
100-
Write-Error "Invalid Path: $file"
101-
102-
} elseif (-not (Test-Path $file -PathType Leaf)) {
103-
$invalidPath = $true
104-
Write-Error "Path is directory, not file: $file"
105-
}
89+
# Warn and return if '-Expected' is passed with '-Algorithms'
90+
if ($Expected -and $Algorithms) {
91+
Write-Error "When '-Expected' is specified, '-Algorithms' must be omitted.`nThe algorithm of your expected hash will be automatically derived from its length."
92+
return
10693
}
10794

108-
# Allow all path issues to be printed prior to return
109-
if ($invalidPath) { return }
95+
# If '-Algorithms' is unspecified, default to SHA512 (after ensuring '-Expected' + '-Algorithms' mutual exclusivity). Else, remove duplicate objects
96+
if (-not $Algorithms) { $Algorithms = "SHA512" } else { $Algorithms = $Algorithms | Select-Object -Unique }
11097

111-
# Automatically detect '-Expected' input hash type based on length
98+
# Automatically derive algorithm of '-Expected' hash from its length
11299
if ($Expected) {
113100

114101
$Algorithms = @()
115102

116-
foreach ($key in $hashLengths.Keys) {
117-
if ($hashLengths[$key] -eq $Expected.Length) {
118-
$Algorithms = $key
119-
break
103+
$Algorithms = switch ($Expected.Length) {
104+
32 { "MD5" }
105+
40 { "SHA1" }
106+
64 { "SHA256" }
107+
96 { "SHA384" }
108+
128 { "SHA512" }
109+
default {
110+
Write-Error "Invalid length of '-Expected' hash ($($Expected.Length) characters). Supported algorithms/lengths are as follows:`n`n"
111+
Write-Host -ForegroundColor Red "MD5 - 32`nSHA1 - 40`nSHA256 - 64`nSHA384 - 96`nSHA512 - 128"
112+
return
120113
}
121114
}
122-
if (-not ($Algorithms)) {
123-
Write-Error "Invalid length of '-Expected' hash ($($Expected.Length) characters). Supported hashes/lengths are as follows:`n`n"
124-
$hashLengths.Keys | Sort-Object | ForEach-Object { "$_ - $($hashLengths[$_])" } | Write-Host -ForegroundColor Red
125-
return
126-
}
127115
}
128116

129-
# Add each file's path to a hashtable which contains the path and its hashes from specified algorithms
130-
foreach ($file in $Files) { [array]$table += @{ "Path" = $file } }
117+
# If '-Algorithms' contains 'All', run all algorithms, else run what is specified
118+
$algsToRun = if ($Algorithms -contains "All") { "SHA512","SHA384","SHA256","SHA1","MD5" } else { $Algorithms }
131119

132-
# Ensure only 1 algorithm is selected for use when -Expected is specified
133-
if($Expected -and (($Algorithms.Count -gt 1) -or ($Algorithms -contains "All"))) {
134-
Write-Error "When '-Expected' is specified, '-Algorithm' is limited to one type."
135-
return
120+
# Ensure at least two file paths have been provided, unless '-Expected' is passed
121+
if ($Files.Count -lt 2 -and -not $Expected) { Write-Error "When '-Expected' is not specified, at least two file paths must be provided for comparison." ; return }
122+
123+
# Ensure supplied paths exist, and are files, not directories
124+
foreach ($path in $Files) {
125+
126+
if (-not (Test-Path $path)) {
127+
$invalidPath = $true
128+
Write-Error "Invalid Path: $path"
129+
130+
} elseif (-not (Test-Path $path -PathType Leaf)) {
131+
$invalidPath = $true
132+
Write-Error "Path is directory, not file: $path"
133+
}
136134
}
137135

138-
# If user's algorithm selection contains "All", run all algorithms, else just run what user specifies
139-
$algorithms = if ($Algorithms -contains "All") { @("SHA512","SHA384","SHA256","SHA1","MD5") } else { $Algorithms }
136+
# Allow all path issues to be printed prior to return
137+
if ($invalidPath) { return }
138+
139+
# Add each file's path to its own hashtable within an array; its hashes from the algorithms specified will be stored here later
140+
foreach ($path in $Files) { [array]$table += @{ "Path" = $path } }
140141

141142
function Get-Hashes {
142143
param (
@@ -145,17 +146,19 @@ function Compare-FileHash {
145146
)
146147

147148
# Compute hash for supplied algorithm, store result in relevant hashtable
148-
foreach ($number in 0..($Files.Count-1)) {
149+
foreach ($number in 0..($Files.Count - 1)) {
149150

150151
Write-Progress -Activity "Computing hash:" -Status "$($table[$number]["Path"]) | $Type"
151152
$table[$number][$Type] = Get-FileHash -Path $table[$number]["Path"] -Algorithm $Type
152153

153154
# Only print table headers once, for the first hash
154-
if ((-not ($Quiet)) -and (-not ($tableHeaders))) {
155-
$table[$number][$Type] | Out-Host
156-
$script:tableHeaders = $true
155+
if (-not $Quiet) {
156+
if (-not $tableHeaders) {
157+
$table[$number][$Type] | Out-Host
158+
$script:tableHeaders = $true
157159

158-
} elseif (-not ($Quiet)) { $table[$number][$Type] | Format-Table -HideTableHeaders | Out-Host }
160+
} else { $table[$number][$Type] | Format-Table -HideTableHeaders | Out-Host }
161+
}
159162
}
160163
}
161164

@@ -176,13 +179,13 @@ function Compare-FileHash {
176179
}
177180

178181
# Run Compare-Hashes with each algorithm
179-
foreach ($hashType in $algorithms) {
182+
foreach ($hashType in $algsToRun) {
180183

181184
Get-Hashes -Type $hashType
182185
$match = Compare-Hashes -Type $hashType
183186

184187
# If a mismatch is detected, or a match is detected and '-Fast' is specified, skip to results
185-
if (-not ($match)) { break } elseif ($Fast) { break }
188+
if (-not $match) { break } elseif ($Fast) { break }
186189
}
187190

188191
# Print match results

0 commit comments

Comments
 (0)