Created
November 15, 2025 04:02
-
-
Save planetrocky/5fc09997022a745772e7410bdd269af6 to your computer and use it in GitHub Desktop.
Windows file checksums stored in Alternate Data Stream (ADS)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env pwsh -NoExit | |
| <# | |
| .SYNOPSIS | |
| Adds SHA256 checksums to files as Alternate Data Streams (ADS) and verifies existing checksums. | |
| .DESCRIPTION | |
| This script processes files in the specified directories and either: | |
| - Creates new SHA256 checksums stored in 'hash.sha256' Alternate Data Streams | |
| - Verifies existing checksums against current file content | |
| The script preserves original file timestamps and provides color-coded output | |
| indicating success or failure of checksum operations. Uses PowerShell 7+ features | |
| for improved performance and functionality. | |
| .INPUTS | |
| System.String[] | |
| One or more directory paths to process. If no paths are provided, processes the current directory. | |
| .OUTPUTS | |
| None. Writes results to the console. | |
| .EXAMPLE | |
| add_checksums_to_ads.ps1 C:\MyFiles D:\OtherFiles | |
| Processes all files in C:\MyFiles and D:\OtherFiles directories. | |
| .EXAMPLE | |
| add_checksums_to_ads.ps1 . | |
| Processes all files in the current directory. | |
| .EXAMPLE | |
| Get-ChildItem *.txt | add_checksums_to_ads.ps1 | |
| Processes only text files via pipeline input. | |
| .EXAMPLE | |
| add_checksums_to_ads.ps1 -Algorithm SHA512 | |
| Uses SHA512 algorithm instead of the default SHA256. | |
| .EXAMPLE | |
| add_checksums_to_ads.ps1 -Force | |
| Forces update of mismatched checksums when verification fails. | |
| .EXAMPLE | |
| add_checksums_to_ads.ps1 -WhatIf | |
| Shows what would happen without making any changes (dry run). | |
| .EXAMPLE | |
| add_checksums_to_ads.ps1 C:\Files -Algorithm SHA384 -Force | |
| Processes C:\Files with SHA384 algorithm and forces updates of mismatched checksums. | |
| .NOTES | |
| Author: PowerShell Script | |
| Requires: PowerShell 7.0 or later | |
| Alternate Data Streams are only supported on NTFS file systems. | |
| File: add_checksums_to_ads.ps1 | |
| #> | |
| #Requires -Version 7.0 | |
| function Add-ChecksumsToADS { | |
| [CmdletBinding()] | |
| param ( | |
| [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)] | |
| [string[]]$FilePaths = @("."), | |
| [Parameter(Mandatory=$false)] | |
| [ValidateSet('SHA256', 'SHA384', 'SHA512', 'MD5')] | |
| [string]$Algorithm = 'SHA256', | |
| [Parameter(Mandatory=$false)] | |
| [switch]$Force, | |
| [Parameter(Mandatory=$false)] | |
| [switch]$WhatIf | |
| ) | |
| begin { | |
| $processedCount = 0 | |
| $verifiedCount = 0 | |
| $failedCount = 0 | |
| $streamName = "hash.$Algorithm".ToLower() | |
| Write-Verbose "Starting checksum processing with algorithm: $Algorithm" | |
| } | |
| process { | |
| foreach ($path in $FilePaths) { | |
| if (-not (Test-Path -LiteralPath $path)) { | |
| Write-Warning "Path '$path' does not exist. Skipping." | |
| $failedCount++ | |
| continue | |
| } | |
| try { | |
| $files = Get-ChildItem -LiteralPath $path -File -Recurse -Attributes !System -ErrorAction SilentlyContinue | |
| } | |
| catch { | |
| Write-Warning "Error accessing path '$path': $($_.Exception.Message)" | |
| $failedCount++ | |
| continue | |
| } | |
| if (-not $files) { | |
| Write-Host "No files found in path: $path" -ForegroundColor Yellow | |
| continue | |
| } | |
| foreach ($file in $files) { | |
| $adsPath = "$($file.FullName):$streamName" | |
| if (Test-Path -LiteralPath $adsPath) { | |
| try { | |
| $current_checksum = Get-Content -LiteralPath $file.FullName -Stream $streamName -ErrorAction Stop | |
| $checksum = Get-FileHash -LiteralPath $file.FullName -Algorithm $Algorithm | Select-Object -ExpandProperty Hash | |
| if ($WhatIf) { | |
| Write-Host "[WHATIF] $($file.Name) would verify existing '$streamName'" -ForegroundColor Cyan | |
| continue | |
| } | |
| Write-Host -NoNewline "$($file.Name) already has a '$streamName'" | |
| if ($checksum -eq $current_checksum) { | |
| Write-Host " β $($PSStyle.Foreground.Green)Checksums match$($PSStyle.Reset)" -ForegroundColor Green | |
| $verifiedCount++ | |
| } else { | |
| Write-Host " β $($PSStyle.Foreground.Red)Current checksum and computed don't match!!!$($PSStyle.Reset)" -ForegroundColor Red | |
| if ($Force) { | |
| Write-Host " Forcing update of checksum..." -ForegroundColor Yellow | |
| Update-FileChecksum -File $file -Algorithm $Algorithm -StreamName $streamName -WhatIf:$WhatIf | |
| } | |
| $failedCount++ | |
| } | |
| } | |
| catch { | |
| Write-Host " β Error reading existing ADS for $($file.Name): $($_.Exception.Message)" -ForegroundColor Red | |
| $failedCount++ | |
| } | |
| } else { | |
| if ($WhatIf) { | |
| Write-Host "[WHATIF] $($file.Name) would create new '$streamName'" -ForegroundColor Cyan | |
| continue | |
| } | |
| Write-Host -NoNewline "$($file.Name) generating '$streamName'" | |
| Update-FileChecksum -File $file -Algorithm $Algorithm -StreamName $streamName -WhatIf:$WhatIf | |
| } | |
| $processedCount++ | |
| } | |
| } | |
| } | |
| end { | |
| if ($processedCount -gt 0) { | |
| Write-Host "`nProcessing Summary:" -ForegroundColor Magenta | |
| Write-Host " Total files processed: $processedCount" -ForegroundColor White | |
| Write-Host " Checksums verified: $verifiedCount" -ForegroundColor Green | |
| Write-Host " Checksums failed: $failedCount" -ForegroundColor Red | |
| } | |
| } | |
| } | |
| function Update-FileChecksum { | |
| param( | |
| [System.IO.FileInfo]$File, | |
| [string]$Algorithm, | |
| [string]$StreamName, | |
| [bool]$WhatIf = $false | |
| ) | |
| $lastWriteTime = $file.LastWriteTime | |
| $checksum = Get-FileHash -LiteralPath $file.FullName -Algorithm $Algorithm | Select-Object -ExpandProperty Hash | |
| if ($WhatIf) { | |
| Write-Host " [WHATIF: $checksum]" -ForegroundColor Cyan | |
| return | |
| } | |
| try { | |
| Set-Content -Force -LiteralPath $file.FullName -Stream $StreamName -Value $checksum | |
| Write-Host " $checksum" -ForegroundColor Cyan | |
| } catch { | |
| Write-Host " Failed to write ADS '$StreamName' to $($file.Name) [$($_.Exception.Message)]" -ForegroundColor Red | |
| return | |
| } | |
| try { | |
| $file.LastWriteTime = $lastWriteTime | |
| } catch { | |
| Write-Host "Failed to reset LastWriteTime on $($file.Name)" -ForegroundColor Yellow | |
| } | |
| } | |
| # Main execution block | |
| if ($MyInvocation.InvocationName -ne '.') { | |
| try { | |
| Write-Host "π Checksum Processor (PowerShell $($PSVersionTable.PSVersion))" -ForegroundColor Magenta | |
| Write-Host " Algorithm: SHA256 | Stream: hash.sha256" -ForegroundColor Gray | |
| # Handle pipeline input | |
| if ($input.MoveNext()) { | |
| $input.Reset() | |
| $paths = @($input) | |
| Write-Host "Processing pipeline input ($($paths.Count) items)..." -ForegroundColor Cyan | |
| } else { | |
| # Process command line arguments | |
| $paths = $args | |
| if ($paths.Count -eq 0) { | |
| $paths = @(".") | |
| } | |
| Write-Host "Processing paths: $($paths -join ', ')" -ForegroundColor Cyan | |
| } | |
| # Detect parameters from arguments | |
| $paramArgs = @() | |
| $remainingPaths = @() | |
| foreach ($arg in $paths) { | |
| if ($arg -match '^-') { | |
| $paramArgs += $arg | |
| } else { | |
| $remainingPaths += $arg | |
| } | |
| } | |
| if ($remainingPaths.Count -eq 0) { | |
| $remainingPaths = @(".") | |
| } | |
| # Invoke the function with detected parameters | |
| Add-ChecksumsToADS -FilePaths $remainingPaths @paramArgs | |
| Write-Host "`nβ Checksum processing completed." -ForegroundColor Green | |
| } | |
| catch { | |
| Write-Error "An error occurred during execution: $($_.Exception.Message)" | |
| exit 1 | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment