[CmdletBinding(DefaultParameterSetName='Generate')] param( # File(s) or wildcard pattern to compute checksum for [Parameter(Position = 0, ParameterSetName = 'Generate', ValueFromPipeline = $true)] [Parameter(Position = 0, ParameterSetName = 'Match', ValueFromPipeline = $true)] [string[]]$FilePath, # Output file to save generated checksums (-o mode) [Parameter(Mandatory = $false, ParameterSetName = 'Generate')] [Alias('o')] [string]$OutFile, # File containing checksums to verify (-c mode) [Parameter(Mandatory = $false, ParameterSetName = 'Verify')] [Alias('c')] [string]$Check, # Direct checksum hash string to match against (-m mode) [Parameter(Mandatory = $true, ParameterSetName = 'Match')] [Alias('m')] [string]$Match, # Hashing algorithm to use (Defaults to SHA256) [Parameter(Mandatory = $false)] [Alias('a')] [ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512', 'MD5')] [string]$Algorithm, # Help flag (-h, -Help) [Parameter(Mandatory = $false)] [Alias('h')] [switch]$Help ) begin { function Show-Help { @' NAME checksum - Compute and verify message digests / file checksums SYNOPSIS .\checksum.ps1 [-FilePath] [-OutFile ] [-Algorithm ] .\checksum.ps1 [-FilePath] -Match [-Algorithm ] .\checksum.ps1 -Check [-Algorithm ] .\checksum.ps1 -Help DESCRIPTION Computes or verifies checksums using various cryptographic algorithms. Outputs hash values in standard Linux utility format ( ). PARAMETERS -FilePath Specifies the path to one or more files to hash. Accepts wildcards (*). -o, -OutFile Specifies an output file to save the checksum results to, while still displaying the output on screen. -c, -Check Read checksums from the specified file and verify them against files on disk. -m, -Match Compare the computed hash of the target file(s) against a specific hash string. -a, -Algorithm Specifies the cryptographic hash algorithm to use. Supported values: SHA1, SHA256 (default), SHA384, SHA512, MD5. If omitted in -c or -m mode, the algorithm is auto-detected by hash length. -h, -Help Display this help message. EXAMPLES 1. Compute SHA256 checksum for a single file: .\checksum.ps1 myfile.iso 2. Compute checksums for all files and write to a file while showing output: .\checksum.ps1 * -o checksums.sha256 3. Verify a file against a explicit hash string: .\checksum.ps1 myfile.iso -m "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 4. Verify checksums from a verification file (auto-detects algorithm): .\checksum.ps1 -c checksums.sha256 '@ } # Helper function to auto-detect hash algorithm based on string character length function Get-AlgorithmFromHashLength { param([string]$HashString) switch ($HashString.Trim().Length) { 32 { return 'MD5' } 40 { return 'SHA1' } 64 { return 'SHA256' } 96 { return 'SHA384' } 128 { return 'SHA512' } default { Write-Error "checksum: Could not auto-detect algorithm for hash length $($HashString.Length). Please specify -Algorithm." return $null } } } } process { # Match help arguments passed either as switch (-h, -Help) or positional string ('help', 'h', '--help', etc.) $isHelpRequested = $Help.IsPresent -or ( $FilePath -and $FilePath.Count -eq 1 -and ( $FilePath[0] -match '^(?i)[-/]{0,2}(h|help)$' ) ) if ($isHelpRequested) { Show-Help return } # Normalize explicit algorithm input if provided if (-not [string]::IsNullOrWhiteSpace($Algorithm)) { $Algorithm = $Algorithm.ToUpper() } # --- DIRECT MATCH MODE (-m / -Match) --- if ($PSCmdlet.ParameterSetName -eq 'Match') { if (-not $FilePath -or $FilePath.Count -eq 0) { Write-Error "checksum: Missing file path parameter for matching. Use -Help for usage." return } # Auto-detect algorithm from provided -Match hash string if not explicitly given if ([string]::IsNullOrWhiteSpace($Algorithm)) { $Algorithm = Get-AlgorithmFromHashLength -HashString $Match if (-not $Algorithm) { return } Write-Verbose "Auto-detected algorithm: $Algorithm" } $expectedHash = $Match.Trim().ToLower() foreach ($pathEntry in $FilePath) { $resolvedItems = Get-Item -Path $pathEntry -ErrorAction SilentlyContinue if (-not $resolvedItems) { Write-Error "checksum: $pathEntry`: No such file or directory" continue } foreach ($item in $resolvedItems) { if ($item.PSIsContainer) { Write-Error "checksum: $($item.Name)`: Is a directory" } else { $actualHash = (Get-FileHash -Path $item.FullName -Algorithm $Algorithm).Hash.ToLower() $targetFile = Resolve-Path -Path $item.FullName -Relative if ($targetFile.StartsWith(".\")) { $targetFile = $targetFile.Substring(2) } if ($actualHash -eq $expectedHash) { Write-Host "${targetFile}: " -NoNewline Write-Host "OK" -ForegroundColor Green } else { Write-Host "${targetFile}: " -NoNewline Write-Host "FAILED" -ForegroundColor Red } } } } } # --- VERIFICATION MODE (-c / -Check) --- elseif ($PSCmdlet.ParameterSetName -eq 'Verify') { if (-not (Test-Path -Path $Check -PathType Leaf)) { Write-Error "checksum: $Check`: No such file or directory" return } $lines = Get-Content -Path $Check # Auto-detect algorithm based on hash length if not explicitly provided if ([string]::IsNullOrWhiteSpace($Algorithm)) { foreach ($line in $lines) { if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith("#")) { continue } if ($line -match '^([a-fA-F0-9]+)\s+') { $Algorithm = Get-AlgorithmFromHashLength -HashString $Matches[1] if (-not $Algorithm) { return } Write-Verbose "Auto-detected algorithm: $Algorithm" break } } # Fallback if file contained no parsable hashes if ([string]::IsNullOrWhiteSpace($Algorithm)) { $Algorithm = 'SHA256' } } foreach ($line in $lines) { # Skip empty lines or comments if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith("#")) { continue } # Match standard checksum output format: " [* ]" if ($line -match '^([a-fA-F0-9]+)\s+[* ]?(.*)$') { $expectedHash = $Matches[1].ToLower() # Strip leading whitespace and any leftover leading asterisk $targetFile = $Matches[2].Trim().TrimStart('*') if (Test-Path -Path $targetFile -PathType Leaf) { $actualHash = (Get-FileHash -Path $targetFile -Algorithm $Algorithm).Hash.ToLower() if ($actualHash -eq $expectedHash) { Write-Host "${targetFile}: " -NoNewline Write-Host "OK" -ForegroundColor Green } else { Write-Host "${targetFile}: " -NoNewline Write-Host "FAILED" -ForegroundColor Red } } else { Write-Host "${targetFile}: " -NoNewline Write-Host "FAILED open or read" -ForegroundColor Red } } else { Write-Warning "checksum: improperly formatted line: $line" } } } # --- GENERATION MODE --- else { # Default to SHA256 if no algorithm was specified in generation mode if ([string]::IsNullOrWhiteSpace($Algorithm)) { $Algorithm = 'SHA256' } if (-not $FilePath -or $FilePath.Count -eq 0) { Write-Error "checksum: Missing file path parameter. Use -Help for usage." return } # Clear/initialize output file if specified before writing hashes if (-not [string]::IsNullOrWhiteSpace($OutFile)) { $null = New-Item -Path $OutFile -ItemType File -Force } foreach ($pathEntry in $FilePath) { # Resolve wildcards and single file inputs $resolvedItems = Get-Item -Path $pathEntry -ErrorAction SilentlyContinue if (-not $resolvedItems) { Write-Error "checksum: $pathEntry`: No such file or directory" continue } foreach ($item in $resolvedItems) { # Handle directories like Linux sha256sum does if ($item.PSIsContainer) { Write-Error "checksum: $($item.Name)`: Is a directory" } else { $hash = (Get-FileHash -Path $item.FullName -Algorithm $Algorithm).Hash.ToLower() # Format standard relative path (stripping leading '.\') $relativePath = Resolve-Path -Path $item.FullName -Relative if ($relativePath.StartsWith(".\")) { $relativePath = $relativePath.Substring(2) } $outputLine = "$hash $relativePath" # If -OutFile is provided, write line to file if (-not [string]::IsNullOrWhiteSpace($OutFile)) { Add-Content -Path $OutFile -Value $outputLine } # Output to stdout Write-Output $outputLine } } } } } end {}