Use this command to install FileHasher with WinGet:
winget install --id=FSPProductions.FileHasher -e
FileHasher is a cryptographic hashing utility designed to compute file hashes, write sidecar verification files, verify existing sidecars, and export results in CSV format. It supports individual files or entire folder trees, with optional MSI package analysis for hash extraction of inner components.
Key Features:
Computes cryptographic hashes using MD5, SHA1, SHA256, and SHA512 algorithms.
Writes .sha256-style sidecar files for file integrity verification.
Verifies existing sidecar files to detect tampered or corrupted data.
Exports hash results to CSV with optional metadata (file size, last modified time).
Handles MSI packages as read-only containers to hash inner components without installation.
Ideal for IT professionals, security teams, and developers requiring reliable file integrity checks. The self-contained executable eliminates dependency on external runtimes, ensuring seamless deployment via winget.
README
FileHasher
A utility to hash files and folders, write sidecar hash files, and export results to CSV. The original implementation is a PowerShell script; the compiled Windows GUI application started from that script's design but has been iterated on extensively since and now offers significantly more functionality than the script.
Copy FileHasher.exe anywhere you like — it has no external dependencies.
Reproducible builds
This section is for anyone who wants to independently verify that the published binaries match the source in this repository — useful for security review, downstream packaging, or peace of mind.
The CI pipeline passes -p:Deterministic=true -p:ContinuousIntegrationBuild=true to dotnet publish so the unsigned executable content can be reproduced byte-for-byte from a clean checkout with matching tools. The trailing Authenticode signature on the released .exe is appended by the maintainer's code-signing step using a hardware token, so the signed region of the file will always differ between a third-party rebuild and an official release; what you can verify byte-for-byte is the underlying executable.
How to reproduce a release locally
Check out the tag you want to verify:
git fetch --tags
git checkout vX.Y.Z
Install the .NET SDK version pinned by the repo's global.json at the root, for example:
rollForward: "disable" means dotnet will refuse to build if that exact patch isn't installed, so you can't accidentally use a different SDK. Each release tag carries the global.json content the CI used at that time; checking out the tag and installing the SDK it names is all the version-matching you need.
The two flags beyond the regular Building command are what make the output stable across machines:
Deterministic=true — replaces build timestamps and random GUIDs in PE/PDB metadata with content-derived values.
ContinuousIntegrationBuild=true — normalizes embedded source paths so the PDB content doesn't depend on your build machine's directory layout.
The output at FileHasherApp\bin\Release\net8.0-windows\win-x64\publish\FileHasher.exe should be byte-identical to:
the unsignedFileHasher.exe artifact uploaded by the CI's build stage (available on the pipeline's job page for 30 days after the release), or
the signed release .exe with its trailing Authenticode certificate table stripped — for example via osslsigncode remove-signature signed.exe unsigned.exe.
Compare with any SHA-256 utility. A match means the published bytes came from this source at that tag.
Caveats
Match the SDK first. Patch-level SDK drift is the single biggest source of size and byte differences; even one patch apart will diverge by tens or hundreds of KB after compression. The SDK version the CI used to produce a given release is recorded in that pipeline's build job log.
Applies only to releases built after the CI adopted deterministic flags. Binaries from tags cut before .gitlab-ci.yml started passing -p:Deterministic=true -p:ContinuousIntegrationBuild=true (initial release v0.1.1 predates the change) cannot be reproduced byte-for-byte regardless of the local flags used.
The signature itself can't be reproduced without access to the same code-signing certificate and HSM token. Verification of the signature, separate from binary reproduction, is done with standard tools like signtool verify or osslsigncode verify against the signed release .exe.
Usage
Selecting a target
Button
Behaviour
Browse File…
Opens a file picker. The selected file is hashed regardless of its extension.
Browse Folder…
Opens a folder picker. The folder is scanned recursively.
You can also type or paste a path directly into the path box, or drag and drop a file or folder onto it. Dropping a folder automatically enables the Scan all file types checkbox; dropping a file disables it (matching the Browse buttons' behaviour).
Scan all file types — when scanning a folder, this checkbox controls which files are included:
Unchecked (default) — only .exe and .msi files are hashed, matching the behaviour of the PowerShell script.
Checked — every file in the folder tree is hashed.
This option is not applicable when a single file is selected.
Choosing a hash algorithm
Select one of the four algorithms using the radio buttons in the Hash Algorithm group. SHA256 is selected by default.
Algorithm
Notes
MD5
Fast; not collision-resistant — avoid for security-critical use
SHA1
Legacy; deprecated for most security purposes
SHA256
Default. Recommended for general use.
SHA512
Strongest option; produces a longer hash
The column header in the results list and the CSV export header update automatically to reflect the selected algorithm.
Options
Include file metadata
When checked, two additional columns are populated in the results list and CSV export: Size (bytes) and Modified (UTC).
Write sidecar hash files
Writes a small text file next to each hashed file. For example, hashing setup.exe produces setup.exe.sha256 alongside it.
Extension — the suffix appended to the original filename. The suggested value follows the selected hash algorithm automatically (.md5, .sha1, .sha256, .sha512); switching algorithms updates the box only while it still holds one of those four standard values, so a custom extension you typed is never overwritten.
Format — controls the content of the sidecar file:
{algo}sum format (default) — HASH *filename, compatible with the standard md5sum/sha1sum/sha256sum/sha512sum tools. The radio button's label follows the selected algorithm (e.g. sha256sum format when SHA256 is selected).
Hash only — the raw hash string with no filename, useful for simple verification scripts.
Extended — HASH *filename *lastModified *sizeBytes, where the last-modified timestamp is ISO-8601 UTC (e.g. 2026-08-09T14:33:05Z), matching the CSV export's LastWriteUtc format.
Writing sidecars to protected locations (e.g. C:\Program Files) requires Administrator rights — see UAC elevation.
Sidecar conflict handling
When a sidecar file already exists for a target file, FileHasher pauses before hashing begins and shows a per-file conflict dialog. The dialog displays:
File — full path of the source file
Size — file size in bytes
Modified — last-modified timestamp (UTC)
Existing sidecar — name of the sidecar that is already on disk
Sidecar written — timestamp when the existing sidecar was last written
Four buttons let you choose what to do:
Button
Behaviour
Overwrite
Replace the existing sidecar for this file only.
Overwrite All
Replace existing sidecars for all remaining conflicts without further prompts.
Skip
Leave the existing sidecar for this file; the file is excluded from hashing.
Skip All
Leave existing sidecars for all remaining conflicts; those files are excluded from hashing.
All conflict decisions are collected before any file is hashed, so no file is touched until every dialog has been answered.
The completion summary (see Running and stopping) includes Sidecars skipped and Sidecars overwritten counts when at least one conflict was resolved.
Sidecar files are never themselves treated as hash targets — FileHasher automatically excludes any file whose path ends with the configured sidecar extension, preventing .sha256.sha256 chains on repeated runs.
Export results to CSV
When checked, a CSV file is written after all files have been hashed. Click Browse… to choose the output path, or type one directly.
Encoding: UTF-8 with BOM (opens correctly in Microsoft Excel without an import wizard).
Only successfully hashed files are included; errors are omitted.
Rows are sorted by file path.
Columns: Path, ``, and (if metadata is enabled) LengthBytes, LastWriteUtc.
Hash files inside MSI installers (experimental)
When checked, every .msi file the worker encounters is also opened read-only via the Windows Installer database API, and the files contained inside it are hashed individually in addition to the MSI itself. Each inner file appears as its own results row prefixed with [parent.msi], colored steel-blue, with the MSI's internal install path (e.g. Program Files\msi-test\foo.exe) in the File Path column and the raw MSI Directory-table identifier (e.g. PFiles64, INSTALLDIR) in a new MSI Dir column on the right. The CSV export gains optional Container and MsiDirectoryId columns when this option is on.
The Scan all file types checkbox above also drives the inner-MSI filter: when unchecked, only .exe and .msi inner files are surfaced; when checked, every inner file regardless of extension. The checkbox stays enabled whenever the target is a folder OR an .msi file with this option on.
The MSI is treated as data — the database is opened read-only and the contents extracted to a sandboxed per-MSI temp directory that's deleted as soon as hashing finishes. The installer is not executed.
For the full security model (file-size / total-size / file-count / disk-headroom caps, path-traversal and reparse-point rejection, sibling-escape cleanup), the test-coverage layout across all three tiers, and the known limitations, see README-msi-inner-scan.md.
Running and stopping
Control
Behaviour
▶ Run
Starts enumeration then hashing. Most controls are disabled while a run is in progress.
Verify Sidecars
Verifies previously written sidecar hash files against the current state of the files — see Verifying sidecars.
Stop
Cancels the current run cleanly. Files already hashed (or verified) are retained in the results list.
Clear Results
Clears all rows from the results list, resets the progress bar, and returns the status to "Ready." Available before and after a run.
The progress bar shows indeterminate (marquee) progress during folder enumeration, then switches to a percentage bar during hashing. When hashing completes successfully the bar turns blue.
A completion dialog is shown at the end of every run summarising:
Files hashed — count of successfully hashed files
Errors — count of files that failed
Sidecars skipped — shown only when at least one existing sidecar was left in place
Sidecars overwritten — shown only when at least one existing sidecar was replaced
Log — path to the current log file
Verifying sidecars
Verify Sidecars re-hashes files and compares the result against their existing sidecar files, using the current Target path and the Extension configured under the sidecar options (the Write sidecar hash files checkbox does not need to be checked). For a folder target the scan is recursive; targeting a single file verifies that file's sidecar, and targeting a sidecar file directly verifies it against its base file.
The hash algorithm is auto-detected per sidecar from the length of the stored hash (32 hex characters = MD5, 40 = SHA1, 64 = SHA256, 128 = SHA512), so the algorithm radio selection is ignored during verification and a folder with mixed-algorithm sidecars verifies correctly in one pass. All three sidecar formats are recognized: bare hash, HASH *filename, and the extended HASH *filename *lastModified *sizeBytes.
Each verified item appears as one results row (the hash column header becomes Verification):
Verdict
Color
Meaning
OK (ALGO)
green
Re-computed hash matches the sidecar.
MISMATCH (ALGO)
red
Hash differs — the row shows both the expected and the computed value.
MISSING FILE
red
A sidecar exists but the file it attests to is gone.
NO SIDECAR
orange
The file matches the current scan filter (.exe/.msi, or everything when Scan all file types is checked) but has no sidecar — a completeness audit.
PARSE ERROR
red
The sidecar's content is not recognized as any supported format.
READ ERROR
red
The file or its sidecar could not be read.
The hash alone decides pass/fail. For extended-format sidecars, a differing embedded filename, modified date, or size on an otherwise-matching row is appended as an informational note — a file's modified date often changes legitimately on copy or restore.
Verification runs are logged like hashing runs and end with the same style of summary dialog (per-verdict counts). The Export results to CSV option does not apply to verification runs.
Results
The results list shows one row per file:
Column
Content
File Path
Full absolute path
``
Uppercase hex hash, or an error message in red if hashing failed
Size (bytes)
Populated when Include file metadata is checked
Modified (UTC)
Populated when Include file metadata is checked
Warnings (e.g. inaccessible subdirectories) appear as orange rows.
Right-clicking a result row opens a context menu:
Open in File Explorer — opens the containing folder with the file pre-selected (explorer /select). If the file has been deleted since it was hashed, the folder is opened plainly instead.
Open PowerShell here — opens a Windows PowerShell window in the containing folder.
Open Command Prompt here — opens a cmd window in the containing folder.
Copy Hash — copies the row's hash to the clipboard (disabled on error rows and on verification rows where no hash was computed).
Copy File Path — copies the row's full on-disk path to the clipboard.
Double-clicking a row (or pressing Enter on it) performs the Explorer action directly.
For inner-MSI rows (experimental MSI scan), the location-based actions target the containing .msi file — the extracted temp copies are already deleted by the time results are browsable — and Copy File Path copies that .msi path. Warning rows have no payload, so no menu appears for them. The three Open items are greyed out when the row's folder no longer exists; the copy items keep working.
Logs
Every run is automatically logged. Log files are written to:
Each log entry records the timestamp (UTC), algorithm, result (OK or ERROR), hash, file path, and (when metadata is enabled) file size and last modified date. A session header and footer are written at the start and end of each run.
Click the path shown in the status bar at the bottom of the window to open the log folder in Explorer.
UAC elevation
FileHasher starts as a standard user by default. Elevation is needed in two situations:
Reading files in protected locations (e.g. C:\Windows\System32).
Writing sidecar files next to installers in protected locations (e.g. C:\Program Files).
To elevate:
Click Run as Administrator… before starting a run. The application will prompt for UAC confirmation and relaunch with Administrator privileges. The title bar and status bar will confirm elevated status.
If a run encounters an access-denied error mid-way, a dialog will offer to relaunch as Administrator automatically.
When already running as Administrator, the button is disabled and the status bar shows ● Administrator.
About dialog
Open Help → About FileHasher… from the menu bar to view the application version, author, and copyright information.
Automated Testing
FileHasher's GUI is covered by a UI automation test suite built on FlaUI and xUnit. The tests launch the real FileHasher.exe, interact with it through the Windows UI Automation (UIA3) accessibility tree, and assert on observable behaviour — no mocking.
Test project overview
Item
Value
Project
FileHasherApp.Tests\FileHasherApp.Tests.csproj
Target framework
net8.0-windows
Test runner
xUnit 2.8
Automation library
FlaUI.UIA3 4.0
Key packages:
Running the tests
Build FileHasherApp first (the tests locate the compiled executable automatically):
# From the solution root
dotnet build FileHasherApp\FileHasherApp.csproj -c Debug
dotnet test FileHasherApp.Tests\FileHasherApp.Tests.csproj
All tests run serially (via xUnit's [Collection("Serial")]) because they interact with real application windows on the Windows desktop.
AutomationId reference
Every interactive control in MainForm.cs is assigned a Name property, which WinForms surfaces as the UIAutomation AutomationId. FlaUI locates controls with:
{algo}sum sidecar format (default; label follows the selected algorithm)
SidecarFmtHashOnly
RadioButton
Hash-only sidecar format
SidecarFmtExtended
RadioButton
Extended sidecar format (hash, filename, modified, size)
CsvChk
CheckBox
Export results to CSV
CsvPathBox
TextBox
CSV output path
CsvBrowseBtn
Button
Opens CSV save dialog
RunAsAdminBtn
Button
Relaunches as Administrator
ClearBtn
Button
Clears results list
VerifyBtn
Button
Starts sidecar verification
StopBtn
Button
Cancels active run
RunBtn
Button
Starts hashing
StatusLabel
Label
Status text (accessible name = label text)
ResultsView
ListView
Results list
ResultsMenu
ContextMenuStrip
Right-click menu on result rows
MiOpenExplorer
MenuItem
Open in File Explorer (select the file)
MiOpenPowerShell
MenuItem
Open PowerShell at the row's location
MiOpenCmd
MenuItem
Open Command Prompt at the row's location
MiCopyHash
MenuItem
Copy the row's hash to the clipboard
MiCopyPath
MenuItem
Copy the row's file path to the clipboard
Test classes
MainFormStateTests — read-only assertions about the form's initial state. All tests share one app instance (IClassFixture) so none of them modify UI state.
Hashing a temp file produces a result row and shows the completion dialog
MainFormAlgorithmTests — algorithm selection completeness and hash-value accuracy. Hash output is verified by writing a sidecar in hash-only format and comparing it against the .NET HashAlgorithm provider for the same bytes.
Test
What it covers
AlgorithmSelection_CanSwitchToSha1
SHA1 radio button can be selected
AlgorithmSelection_OnlyOneCanBeCheckedAtATime
Switching algorithms unchecks the previous one
HashAccuracy_MatchesDotNetCrypto (MD5)
MD5 hash produced by the app matches MD5.HashData
HashAccuracy_MatchesDotNetCrypto (SHA1)
SHA1 hash matches SHA1.HashData
HashAccuracy_MatchesDotNetCrypto (SHA256)
SHA256 hash matches SHA256.HashData
HashAccuracy_MatchesDotNetCrypto (SHA512)
SHA512 hash matches SHA512.HashData
MainFormFolderTests — folder scanning, file filters, recursive subdirectories, and the Stop button.
Test
What it covers
FolderScan_DefaultFilter_OnlyHashesExeAndMsi
Mixed folder: only .exe and .msi files counted in results
FolderScan_AllTypes_HashesEveryFile
"Scan all file types" hashes every file regardless of extension
FolderScan_NoMatchingFiles_ShowsStatusMessage
Folder with no .exe/.msi shows "No matching files found" status
MainFormSidecarAlgoUiTests — the sidecar suggested extension and the {algo}sum format radio label follow the selected hash algorithm. Each test gets its own app process.
Switching away and back to SHA256 restores .sha256 and sha256sum format
AlgorithmSwitch_CustomExtension_IsNeverClobbered
A hand-typed extension survives algorithm switches while the label still follows
MainFormContextMenuTests — the right-click context menu on result rows and double-click activation. The three Open items are deliberately never invoked (they would spawn real Explorer/PowerShell/cmd processes on the test host); their presence and enabled state are asserted instead. Each test gets its own app process.
Test
What it covers
ContextMenu_OnHashedRow_ShowsAllItemsEnabled
All five menu items exist and are enabled on a successfully hashed row
ContextMenu_CopyHash_PutsHashOnClipboard
Copy Hash places the row's exact hash on the clipboard
ContextMenu_CopyPath_PutsFullPathOnClipboard
Copy File Path places the row's full on-disk path on the clipboard
On a failed-hash row (file locked exclusively), Copy Hash is disabled while Copy File Path stays enabled
RowDoubleClick_WithDeletedFolder_IsSafeNoOp
Double-clicking a row whose file and folder are gone is a safe no-op (exercises the Explorer fallback chain without spawning a window)
MainFormVerifyTests — end-to-end tests for the Verify Sidecars button through the UI: button flow, completion dialog, status-line counts, and row counts. Verdict-level precision lives in SidecarVerifierTests. Each test gets its own app process.
Test
What it covers
VerifyButton_EnabledAtStart
The Verify Sidecars button is present and enabled
VerifyWithNoPath_ShowsWarningDialog
Clicking Verify with an empty path shows a warning dialog
Targeting a sidecar file verifies the file it attests to
Verify_OrphanSidecar_ReportsProblem
A sidecar whose base file is gone counts as a problem
Verify_ThenHashRun_BothCompleteCleanly
A hash run immediately after a verify run still works end-to-end
SidecarVerifierTests — direct unit tests against the internal SidecarVerifier class (via InternalsVisibleTo, no UI, parallelizable): sidecar parsing across all three formats, hash-length algorithm auto-detection, status classification, informational metadata notes, and folder enumeration/filtering.
Mixed folder classifies every row and the summary counts match
Folder_AllTypes_AuditsNonExeFilesToo
The scan-all-types flag widens the NO SIDECAR audit
Folder_ResultsAreSortedByPath
Folder results are emitted in path order
CI / custom exe path
AppFixture locates the executable by walking up from the test assembly's output directory, trying Debug then Release configurations. To override this (e.g. in a CI pipeline that publishes to a specific location), set the FILEHASHER_EXE environment variable to the full path of FileHasher.exe:
$env:FILEHASHER_EXE = "C:\build\output\FileHasher.exe"
dotnet test FileHasherApp.Tests\FileHasherApp.Tests.csproj
PowerShell Script
filehasher.ps1 is the original script that the GUI application is based on. It recursively scans a folder for .exe and .msi files, computes SHA256 hashes, and optionally writes sidecar files and a CSV report.
Parameters
Parameter
Type
Default
Description
-RootPath
string
(required)
Root folder to scan recursively
-OutCsv
string
""
Path to write a CSV results file; omit to skip
-IncludeMetadata
switch
off
Include file size and last-modified date in output
-WriteSidecarHashes
switch
off
Write a sidecar hash file next to each installer
-SidecarExtension
string
.sha256
File extension for sidecar files
-SidecarFormat
string
sha256sum
Sidecar content format: sha256sum or hashonly
Examples
# Hash all .exe and .msi files under C:\Installers, print to console
.\filehasher.ps1 -RootPath "C:\Installers"
# Include metadata and export to CSV
.\filehasher.ps1 -RootPath "C:\Installers" -IncludeMetadata -OutCsv "C:\hashes.csv"
# Write sha256sum-compatible sidecar files next to each installer
.\filehasher.ps1 -RootPath "C:\Installers" -WriteSidecarHashes
# Write hash-only sidecar files with a custom extension
.\filehasher.ps1 -RootPath "C:\Installers" -WriteSidecarHashes -SidecarExtension ".hash" -SidecarFormat hashonly
Acknowledgements
This project was developed with assistance from Anthropic's Claude AI. Per-commit attribution is recorded in the git history via Co-Authored-By lines.