Skip to content

Instantly share code, notes, and snippets.

@losticcino
Forked from loopyd/Get-HashEx.psm1
Created August 25, 2023 15:32
Show Gist options
  • Save losticcino/fa735b4f09d72e4630c59de1ac791a82 to your computer and use it in GitHub Desktop.
Save losticcino/fa735b4f09d72e4630c59de1ac791a82 to your computer and use it in GitHub Desktop.
[PowerShell 7] Get-HashEx - All-in-one file and folder hashing module for Windows PowerShell 7.0+
<#
.Synopsis
Hash a file or a folder via the lifestrea- er I mean, bytestream. Then return the results.
.Description
I improved Get-FileHash just a bit. Hash a file or a folder via the lifestrea- er I mean,
bytestream. Then returns the results. A lot of options, and what ever supported, hardware
accelerated CryptoStream HashAlgorithm provider you want. Also with real time progress,
which you can turn off if you want.
FEATURES:
a. Get cryptographic hash of single file with progress.
b. Custom functionality to recursively hash contents of entire folders
c. Combined hash for entire folder via recursive hashing.
d. Uses the CryptoStream and HashAlgorithm provider for speed.
.Parameter Path
The path to the file or folder to compute a hash for. It can be a relative
or an absolute path.
.Parameter Algorithm
One of System.Cryptography.HashAlgorithm options, as a string.
.Parameter BufferSize
Size of the stream buffer in bytes, can perform better depending on your disk.
Default is 1mb (1048576 bytes), which is good for most SSDs. For HDDs, you
should set this to your block size to align reads and writes correctly for
efficient streaming / protect drive brakes from wear. Keep your blocks
aligned to the cylander for best results when using System.IO.Stream asyncronous
functions.
.Parameter Combine
Optionally specify this when -Folder switch is also specified. It will produce a single
entry containing folder properties and the accumulated hash (Transformed hash)
.Parameter NoProgress
Optionally specify this flag to surpress progress output.
.Parameter Folder
Optionally specify this flag to indicate that the Path parameter specified is a folder
that should be recursively hashed.
#>
Function Get-HashEx() {
param(
[Parameter(Mandatory = $true, Position = 0)][string]$Path,
[Parameter(Mandatory = $false)][string]$Algorithm = "SHA256",
[Parameter(Mandatory = $false)][int64]$BufferSize = 1mb,
[Parameter(Mandatory = $false)][switch]$Combine = $false,
[Parameter(Mandatory = $false)][switch]$NoProgress = $false,
[Parameter(Mandatory = $false)][switch]$Folder = $false
)
begin {
if ($Combine -And $Folder) {
$AlgorithmObj = [System.Security.Cryptography.HashAlgorithm]::Create($Algorithm);
$CryptoStream = [System.Security.Cryptography.CryptoStream]::new(([System.IO.Stream]::Null), $AlgorithmObj, "Write");
} elseif (-Not $Folder) {
$AlgorithmObj = [System.Security.Cryptography.HashAlgorithm]::Create($Algorithm);
$CryptoStream = [System.Security.Cryptography.CryptoStream]::new(([System.IO.Stream]::Null), $AlgorithmObj, "Write");
};
$buffer = New-Object Byte[] $BufferSize;
if ((-Not $NoProgress) -And $Folder) {
$filemeasures = (Get-ChildItem $Path -Recurse -Attributes !Directory,!Directory+Hidden | Measure-Object -Property Length -Sum);
$filecount = $filemeasures.Count;
$filesize = $filemeasures.Sum;
$filemeasures = $null;
$processedfiles = 0;
$FT = @()
} elseif ($NoProgress -And $Folder) {
$FT = @()
};
$Error = $false;
}
process {
try {
if ($Folder) {
Get-ChildItem $Path -Recurse -Attributes !Directory,!Directory+Hidden -ErrorAction SilentlyContinue |% {
$myfileobj = $_;
# Display progress
if (-Not $NoProgress) {
$prog = ($processedfiles / $filecount);
Write-Progress -Id 1 -Activity "Get-HashEx is running..." -Status ("Progress: {0:P2}" -f $prog) -PercentComplete ($prog * 100) -CurrentOperation $myfileobj.FullName;
$processedfiles++;
};
# If we are not combining items, we need to initialize a fresh CryptoStream
# each iteration.
if (-Not $Combine) {
$AlgorithmObj = [System.Security.Cryptography.HashAlgorithm]::Create($Algorithm);
$CryptoStream = [System.Security.Cryptography.CryptoStream]::new(([System.IO.Stream]::Null), $AlgorithmObj, "Write");
}
# Stream the file to buffer and into CryptoStream.
$FileStream = [System.IO.File]::OpenRead($_.FullName);
while ( $bytesRead = $FileStream.Read($buffer, 0, $BufferSize) ){
# Display progress for Get-FileHash by tracking FileStream position.
if ((-Not $NoProgress)) {
$prog2 = ($FileStream.Position / $FileStream.Length);
Write-Progress -Id 2 -Activity "CryptoStream is running..." -Status ("Progress: {0:P2}" -f ($prog2)) -PercentComplete ($prog2 * 100) -CurrentOperation ("{0} of {1} bytes hashed" -f $FileStream.Position, $FileStream.Length);
}
# Write to the Stream from the buffer and then flush the CryptoStream block queue.
$CryptoStream.Write($buffer, 0, $bytesRead);
$CryptoStream.Flush();
}
$FileStream.Close(); $FileStream.Dispose();
# If we are not combining items, finalize the CryptoStream, store the result into
# the table, and then dispose of the CryptoStream and HashAlgorithm provider
# we used in this iteration.
if (-Not $Combine) {
$CryptoStream.FlushFinalBlock();
$myfilehash = $(($AlgorithmObj.Hash | ForEach-Object {$_.ToString("X2")}) -join '');
$myrelativename = ($myfileobj.FullName).Replace([System.IO.Path]::GetFullPath($Path), ".\")
$mylastwritetime = [int64]((New-TimeSpan -Start ([timezone]::CurrentTimeZone.ToLocalTime([datetime]'1/1/1970')) -End $myfileobj.LastWriteTime).TotalMilliseconds)
$mysize = ($myfileobj.Length)
$item = [PSCustomObject]@{
FullName = ($myfileobj.FullName)
RelativeName = $myrelativename
Size = $mysize
LastWriteTime = $mylastwritetime
Hash = $myfilehash
};
$FT += $item;
$CryptoStream.Close(); $CryptoStream.Dispose(); $AlgorithmObj.Dispose();
} else {
$CryptoStream.Flush();
}
}
# After everything has completed, if we are combining items, finalize the CryptoStream,
# and store the folder properties as a single entry in the table, then destroy the
# CryptoStream and the HashAlgorithm provider.
if ($Combine) {
$CryptoStream.FlushFinalBlock();
$myfileobj = $(Get-Item -Path $([System.IO.Path]::GetFullPath($Path)) -Force);
$myfilehash = $(($AlgorithmObj.Hash | ForEach-Object {$_.ToString("X2")}) -join '');
$myrelativename = ($myfileobj.FullName).Replace([System.IO.Path]::GetFullPath($Path), ".\")
$mylastwritetime = [int64]((New-TimeSpan -Start ([timezone]::CurrentTimeZone.ToLocalTime([datetime]'1/1/1970')) -End $myfileobj.LastWriteTime).TotalMilliseconds)
[int64]$mysize = [int64]((Get-ChildItem $Path -Recurse -Attributes !Directory,!Directory+Hidden | Measure-Object -Property Length -Sum).Sum);
$item = [PSCustomObject]@{
FullName = ($myfileobj.FullName)
RelativeName = $myrelativename
Size = $mysize
LastWriteTime = $mylastwritetime
Hash = $myfilehash
};
$FT += $item;
$CryptoStream.Close(); $CryptoStream.Dispose(); $AlgorithmObj.Dispose();
}
} else {
# Stream the file to buffer and into CryptoStream.
$FileStream = [System.IO.File]::OpenRead([System.IO.Path]::GetFullPath($Path));
while ( $bytesRead = $FileStream.Read($buffer, 0, $BufferSize) ){
# Display progress for Get-FileHash by tracking FileStream position.
if ((-Not $NoProgress)) {
$prog2 = ($FileStream.Position / $FileStream.Length);
Write-Progress -Id 2 -Activity "Get-HashEx is running..." -Status ("Progress: {0:P2}" -f ($prog2)) -PercentComplete ($prog2 * 100) -CurrentOperation ("{0} of {1} bytes hashed" -f $FileStream.Position, $FileStream.Length);
}
# Write to the Stream from the buffer and then flush the CryptoStream block queue.
$CryptoStream.Write($buffer, 0, $bytesRead);
$CryptoStream.Flush();
}
$FileStream.Close(); $FileStream.Dispose();
# Finalize the CryptoStream, store the result into the table, and then dispose of
# the CryptoStream and HashAlgorithm provider.
$CryptoStream.FlushFinalBlock();
$myfileobj = $(Get-Item -Path $([System.IO.Path]::GetFullPath($Path)) -Force)
$myfilehash = $(($AlgorithmObj.Hash | ForEach-Object {$_.ToString("X2")}) -join '');
$myrelativename = ($myfileobj.FullName).Replace([System.IO.Path]::GetFullPath($Path), ".\")
$mylastwritetime = [int64]((New-TimeSpan -Start ([timezone]::CurrentTimeZone.ToLocalTime([datetime]'1/1/1970')) -End $myfileobj.LastWriteTime).TotalMilliseconds)
$mysize = ($myfileobj.Length)
$FT = [PSCustomObject]@{
FullName = ($myfileobj.FullName)
RelativeName = $myrelativename
Size = $mysize
LastWriteTime = $mylastwritetime
Hash = $myfilehash
};
$FileStream.Close(); $FileStream.Dispose();
$CryptoStream.Close(); $CryptoStream.Dispose(); $AlgorithmObj.Dispose();
}
} catch {
Write-Error "$($_.Exception.Message)`n`nStack trace: $($_.Exception.StackTrace)";
$Error = $true;
}
}
end {
if (($Combine -And ($Error -Eq $false)) -Or ((-Not $Combine) -And ($Error -Eq $false))) {
$result = $FT;
} else {
$result = $null;
}
$result
}
}
Export-Module -Function Get-HashEx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment