Theo's excellent answer is spot on - hashing big files is a slow operation and you may want to optimize read operations from disk. For smaller files this will be optimal, but it won't work for files larger than 2GB.
The problem is that [System.IO.File]::ReadAllBytes($_.FullName) needs to read the entire file into a byte array in memory, and arrays in .NET can only hold up to ~2.1 billion items (~2GB).
You can work around this limitation by passing a file stream to $hasher.ComputeHash() - the only caveat being that we need to "re-wind" the file stream before passing it to the next algorithm.
If only a small subset of the files are expected to have signatures, another (less significant) optimization is to selectively filter which extensions to invoke Get-AuthenticodeSignature for:
function Get-FileMultiHash
{
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.IO.FileInfo]$InputObject,
[Parameter(Mandatory = $false)]
[string[]]$SignatureExtension
)
begin {
$hashers = @(
($md5Hasher = [System.Security.Cryptography.MD5]::Create())
($sha1Hasher = [System.Security.Cryptography.SHA1]::Create())
($sha256Hasher = [System.Security.Cryptography.SHA256]::Create())
)
# Prepend any extension filter with `.` if not already present
$SignatureExtensionFilter = $SignatureExtensionFilter -replace '^(?!\.)','.'
}
process {
try{
$shouldCheckSignature = -not$PSBoundParameters.ContainsKey('SignatureExtensionFilter') -or $InputObject.Extension -in $SignatureExtensionFilter
# Open file stream, compute md5
$fs = $InputObject.OpenRead()
$md5sum = $md5Hasher.ComputeHash($fs)
# Rewind file stream, compute sha1
[void]$fs.Seek(0, [System.IO.SeekOrigin]::Begin)
$sha1sum = $sha1Hasher.ComputeHash($fs)
# Rewind file stream, compute sha256
[void]$fs.Seek(0, [System.IO.SeekOrigin]::Begin)
$sha256sum = $sha256Hasher.ComputeHash($fs)
}
finally{
# Dispose of file stream
if($fs){$fs.Dispose()}
}
# Output checksums and signature status
[pscustomobject]@{
MD5 = [BitConverter]::ToString($md5sum).Replace('-', '')
SHA1 = [BitConverter]::ToString($sha1sum).Replace('-', '')
SHA256 = [BitConverter]::ToString($sha256sum).Replace('-', '')
Signed = if($shouldCheckSignature){(Get-AuthenticodeSignature -LiteralPath $_.FullName).Status}else{'Skipped'}
Path = $_.FullName
}
}
end {
# Dispose of hash algo instances
$hashers |ForEach-Object Dispose
}
}
Then use like:
Get-ChildItem -Path C:\path\to\root\folder -Recurse -File -Force -ea SilentlyContinue -ev errs |Get-FileMultiHash
... or with an extension filter:
Get-ChildItem C:\Windows\System32 -File |Get-FileMultiHash -SignatureExtensionFilter exe,dll