Skip to content

Instantly share code, notes, and snippets.

@iOnline247
Created December 8, 2025 15:43
Show Gist options
  • Select an option

  • Save iOnline247/d1a69bfaa7d07a2d5a55d0e8620ce483 to your computer and use it in GitHub Desktop.

Select an option

Save iOnline247/d1a69bfaa7d07a2d5a55d0e8620ce483 to your computer and use it in GitHub Desktop.
Checksum the checksums!
[CmdletBinding(DefaultParameterSetName = "Generate")]
param (
[Parameter(Mandatory = $true, ParameterSetName = "Generate")]
[Parameter(Mandatory = $true, ParameterSetName = "Verify")]
[string]$Path,
[Parameter(Mandatory = $true, ParameterSetName = "Verify")]
[string]$CompareCsv,
[Parameter(Mandatory = $false, ParameterSetName = "Verify")]
[ValidateSet("HashOnly", "DiffOnly", "Hybrid")]
[string]$VerifyMode = "Hybrid",
[Parameter(Mandatory = $false)]
[ValidateRange(1, 64)]
[int]$ThrottleLimit = 8,
[Parameter(Mandatory = $false)]
[ValidateSet("SHA256", "SHA1", "MD5", "SHA384", "SHA512")]
[string]$Algorithm = "SHA256"
)
if (-not (Test-Path $Path)) {
throw "Path not found: $Path"
}
if ($PSCmdlet.ParameterSetName -eq "Verify" -and -not (Test-Path $CompareCsv)) {
throw "Compare CSV not found: $CompareCsv"
}
$sep = [System.IO.Path]::DirectorySeparatorChar
$CmdletName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)
$TempRoot = Join-Path ([System.IO.Path]::GetTempPath()) $CmdletName
if (-not (Test-Path $TempRoot)) {
New-Item -Path $TempRoot -ItemType Directory | Out-Null
}
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$Guid = [guid]::NewGuid().ToString()
$OutputCsv = Join-Path $TempRoot "checksums_${Algorithm}_${Timestamp}--${Guid}.csv"
$BasePath = (Resolve-Path $Path).Path.TrimEnd($sep)
function Get-ChecksumData {
param (
[Parameter(Mandatory)]
[string]$BaseDirectory,
[Parameter(Mandatory)]
[int]$Throttle,
[Parameter(Mandatory)]
[string]$HashAlgorithm
)
$files = Get-ChildItem -Path $BaseDirectory -Recurse -File
$total = $files.Count
$counter = [ref] 0
$files | ForEach-Object -Parallel {
$hash = Get-FileHash -Path $_.FullName -Algorithm $using:HashAlgorithm
[System.Threading.Interlocked]::Increment([ref]$using:counter.Value) | Out-Null
$percent = [Math]::Floor(($using:counter.Value / $using:total) * 100)
Write-Progress -Activity "Hashing files" -Status "$($using:counter.Value)/$using:total" -PercentComplete $percent
# Cross-platform RelativePath
$sep = [System.IO.Path]::DirectorySeparatorChar
$relPath = $_.FullName.Substring($using:BaseDirectory.Length).TrimStart($sep)
[pscustomobject]@{
RelativePath = $relPath
FileName = $_.Name
Algorithm = $using:HashAlgorithm
Hash = $hash.Hash
}
} -ThrottleLimit $Throttle
}
# ---------- GENERATE MODE ----------
if ($PSCmdlet.ParameterSetName -eq "Generate") {
Write-Host "πŸ“ Scanning directory (PARALLEL=$ThrottleLimit, Algorithm=$Algorithm): $BasePath"
Get-ChecksumData -BaseDirectory $BasePath -Throttle $ThrottleLimit -HashAlgorithm $Algorithm |
Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8
Write-Host "βœ… Checksum CSV generated:"
Write-Host " $OutputCsv"
exit 0
}
# ---------- VERIFY MODE ----------
Write-Host "πŸ”Ž Verification Mode: $VerifyMode"
Write-Host " Directory: $BasePath"
Write-Host " Compare CSV: $CompareCsv"
Write-Host " Algorithm: $Algorithm"
Write-Host " Parallelism: $ThrottleLimit threads"
$newData = Get-ChecksumData -BaseDirectory $BasePath -Throttle $ThrottleLimit -HashAlgorithm $Algorithm
$newData | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8
if ($VerifyMode -in @("HashOnly", "Hybrid")) {
$oldHash = (Get-FileHash -Path $CompareCsv -Algorithm $Algorithm).Hash
$newHash = (Get-FileHash -Path $OutputCsv -Algorithm $Algorithm).Hash
if ($oldHash -eq $newHash) {
Write-Host "βœ… CSV HASH MATCH β€” DATA IDENTICAL" -ForegroundColor Green
if ($VerifyMode -eq "HashOnly") { exit 0 }
}
else {
Write-Host "❌ CSV HASH MISMATCH" -ForegroundColor Red
if ($VerifyMode -eq "HashOnly") { exit 1 }
Write-Host "⚠️ Falling back to deep diff..."
}
}
if ($VerifyMode -in @("DiffOnly", "Hybrid")) {
$newSorted = $newData | Sort-Object RelativePath
$oldSorted = Import-Csv $CompareCsv | Sort-Object RelativePath
$differences = Compare-Object -ReferenceObject $oldSorted -DifferenceObject $newSorted -Property RelativePath, FileName, Algorithm, Hash
if ($differences) {
Write-Host "❌ CHECKSUM MISMATCH DETECTED!" -ForegroundColor Red
$differences | Format-Table -AutoSize
$failedCsv = [System.IO.Path]::ChangeExtension($OutputCsv, ".verification_failed.csv")
$newSorted | Export-Csv -Path $failedCsv -NoTypeInformation -Encoding UTF8
Write-Host "⚠️ New scan saved to:"
Write-Host " $failedCsv"
exit 1
}
else {
Write-Host "βœ… CHECKSUM VERIFICATION PASSED!" -ForegroundColor Green
$verifiedCsv = [System.IO.Path]::ChangeExtension($OutputCsv, ".verified.csv")
$newSorted | Export-Csv -Path $verifiedCsv -NoTypeInformation -Encoding UTF8
Write-Host "πŸ“ Verified CSV saved to:"
Write-Host " $verifiedCsv"
exit 0
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment