There is a quiet category of trouble that leaves no obvious trace: a file changed when it should not have been. A configuration file silently edited by malware, a critical binary swapped for a tampered copy, a document subtly corrupted by a failing disk, none of these announces itself, and a system can run for weeks with altered files before anyone notices the consequences. File integrity verification is the discipline that makes such silent changes loud, computing a fingerprint of each file and watching for any deviation from a trusted baseline so that the moment a file changes unexpectedly, it can be flagged.
The reason to automate this rather than check files by hand is that meaningful verification requires comparing the present state against a recorded past, file by file, across potentially thousands of files, on a schedule. No human does this reliably. A script that hashes the files that should never change, stores those hashes as a baseline, and re-checks them regularly turns the abstract worry of tampering into a concrete alert. Integrity, in this context, is the assurance that data remains consistent and unaltered without authorization, the guarantee that what you retrieve has not been changed since it was trusted.
How a Hash Turns a File into a Fingerprint
The foundation of integrity verification is the cryptographic hash, a function that reduces a file of any size to a short, fixed-length value that depends entirely on the file's content. Change even a single byte and the hash changes completely and unpredictably, which is exactly the property that makes it a fingerprint: two files with the same hash are, for all practical purposes, identical, and any difference in content produces a different hash. This is why a hash can detect a change that a glance at the file size or timestamp would miss.
PowerShell computes hashes through a dedicated cmdlet that takes a file and an algorithm and returns the hash value. The current standard algorithm is well suited to integrity work, producing a value robust enough that an attacker cannot feasibly craft a different file with the same fingerprint. By default this cmdlet uses that standard algorithm, returning an object whose hash property is the fingerprint, expressed as an uppercase hexadecimal string.
Get-FileHash -Path "C:\App\config.xml" -Algorithm SHA256
A practical detail matters when comparing hashes: the output is always uppercase, so comparisons should normalize case to avoid a false mismatch between an uppercase computed value and a lowercase reference. This same hashing is what lets anyone verify a downloaded installer against a vendor-published value before running it, confirming the file arrived intact and unmodified, and it is the same mechanism that underpins ongoing tamper detection across a set of files.
Establishing a Trusted Baseline
Integrity verification is meaningless without a trusted starting point, so the first phase of any such system is establishing a baseline. This means hashing every file that should be monitored while the system is known to be in a good, clean state, and recording each file's path alongside its hash in a baseline file. That recorded set becomes the standard of truth against which all future checks are compared, so the integrity of the baseline itself is paramount.
$baseline = "C:\FIM\baseline.csv"
Get-ChildItem "C:\App" -Recurse -File |
ForEach-Object {
[PSCustomObject]@{
Path = $_.FullName
Hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
}
} | Export-Csv $baseline -NoTypeInformation
This walks a target directory, hashes each file, and writes the paths and hashes to a baseline file. The crucial requirement is that the baseline be captured when the files are genuinely trustworthy, because everything afterward is judged against it, and a baseline taken from an already-compromised system would faithfully bless the very tampering it was meant to catch. Establishing the baseline at a known-good moment, such as immediately after a clean deployment, is therefore the single most important step.
The baseline file itself deserves protection, since it is the reference everything depends on. If an attacker can alter both a monitored file and its recorded hash in the baseline, the change becomes invisible to verification, so the baseline should be stored where it is harder to tamper with than the files it protects, ideally on separate or read-only storage. A baseline that an intruder can edit as easily as the files defeats the purpose of having one at all.
Comparing Against the Baseline to Detect Change
With a baseline in hand, verification becomes a matter of re-hashing the monitored files and comparing each result against its recorded fingerprint. The check loads the baseline into a lookup structure for quick comparison, then computes the current hash of each file and judges it against the stored value. A file whose hash differs has been modified, a file present now but absent from the baseline is new, and a file in the baseline but missing now has been deleted, three distinct findings the check can distinguish.
$baseline = Import-Csv "C:\FIM\baseline.csv"
$lookup = @{}
$baseline | ForEach-Object { $lookup[$_.Path] = $_.Hash }
Get-ChildItem "C:\App" -Recurse -File | ForEach-Object {
$current = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
if (-not $lookup.ContainsKey($_.FullName)) {
"NEW: $($_.FullName)"
} elseif ($lookup[$_.FullName] -ne $current) {
"CHANGED: $($_.FullName)"
}
}
Detecting deletions requires the complementary check, iterating over the baseline entries and confirming each recorded file still exists, flagging any that have vanished. Together, the modification, addition, and deletion checks give a complete picture of how the monitored set has diverged from its trusted state, which is far more useful than a simple changed-or-not verdict. Each category of change carries different implications, and distinguishing them helps an administrator judge whether a finding is benign or alarming.
A subtlety worth handling is the difference between expected and unexpected change. Some files legitimately change, through deployments, updates, or normal operation, and a verification system must account for this or it will drown the administrator in alerts about authorized modifications. The usual approach is to monitor only files that genuinely should not change outside a controlled process, configuration that should be stable, scripts, and binaries, and to deliberately update the baseline as part of any sanctioned change so that authorized modifications are absorbed rather than flagged.
Choosing Between Scheduled Checks and Real-Time Monitoring
There are two broad models for when verification runs, and each suits different needs. The scheduled model re-checks the monitored files at intervals, comparing against the baseline on a timer, which is simple, predictable, and light on resources. It catches a change at the next check after it happens, which for most configuration and binary monitoring is entirely adequate, since the goal is to discover unauthorized changes within a reasonable window rather than the instant they occur.
The real-time model instead watches the filesystem continuously, reacting the moment a file is created, modified, or deleted by using the system's file-change notification mechanism. This catches changes immediately rather than at the next scheduled pass, which matters for the most sensitive files where even a brief window of undetected tampering is unacceptable. The trade-off is greater complexity and a process that must run continuously, consuming resources and requiring care to remain reliable over long uptimes.
The choice between them follows from how quickly a change must be caught. For most purposes, monitoring configuration files, scripts, and binaries that should never change outside a deployment, a scheduled check on a sensible interval is the pragmatic choice, storing the baseline and comparing against it periodically. Reserve continuous real-time watching for the small set of truly critical files where immediate detection justifies the added complexity, and let everything else ride on the simpler scheduled model.
Choosing an Algorithm and Managing the Cost of Hashing
Not all hash algorithms are equal, and the choice affects both security and speed. The cmdlet supports several, including older ones and the current standard, and for integrity verification the algorithm should be one that an attacker cannot feasibly defeat by crafting a different file with the same hash. The older algorithms, while faster, have known weaknesses that make them unsuitable where deliberate tampering is a concern, which is why the modern standard is the right default for security-sensitive integrity work.
The trade-off is that stronger hashing costs more time, and on a large set of files that cost adds up. Hashing reads every byte of every file, so verifying thousands of files or very large ones consumes real disk and processor time, which is why the scheduled model usually runs during quiet hours and why the monitored set should be deliberately scoped rather than pointed at an entire drive. Monitoring the specific files that should be stable, rather than everything, keeps each verification pass fast enough to run regularly without becoming a burden.
For the rare case where even the older, faster algorithms are acceptable, such as detecting accidental corruption rather than deliberate tampering, the speed gain can justify the weaker algorithm, since accidental disk corruption does not craft collisions on purpose. The guiding principle is to match the algorithm to the threat: where the worry is a malicious actor altering files, use the strong standard and accept its cost; where the worry is only random corruption, a faster algorithm may suffice. Being deliberate about this choice keeps verification both meaningful and practical.
Scheduling Verification and Acting on Findings
Putting scheduled verification on autopilot uses the familiar pattern: the task scheduler runs the comparison script at intervals, with elevated rights so it can read every monitored file, and registration from PowerShell keeps it reproducible. The cadence depends on how quickly changes must surface, with a daily or hourly check suiting most configuration and binary monitoring.
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\verify-integrity.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At 4:00AM
Register-ScheduledTask -TaskName 'FileIntegrityCheck' `
-Action $action -Trigger $trigger -User 'SYSTEM' -RunLevel Highest `
-Description 'Daily file integrity verification against baseline'
Detection is only half the value; the findings must reach someone who can judge them. A verification run that discovers changes should log them with timestamps and file paths, building a record of what changed and when, and should alert an administrator when discrepancies appear, whether by email, an event-log entry, or a message to whatever channel the team watches. A log of detected changes also serves as forensic evidence, showing the sequence of modifications if an incident is later investigated.
Crucially, the system must distinguish a genuine alert from the noise of expected change, or it will be ignored. The way to keep alerts meaningful is the discipline mentioned earlier: monitor only files that should be stable, update the baseline deliberately whenever an authorized change occurs, and treat any unexplained discrepancy as worth investigating. An integrity system whose alerts are mostly false positives trains its audience to dismiss them, which is worse than no system at all because it offers false confidence.
What Automated Integrity Verification Really Guarantees
The central insight is that the most dangerous file changes are the silent ones, and the only reliable way to make them loud is to fingerprint files and watch for any deviation from a trusted record. A tampered configuration file or a swapped binary betrays nothing by appearance, but it cannot hide from a hash, which changes completely at the slightest alteration. Verification converts an invisible threat into a detectable event.
A well-built integrity system establishes its baseline at a genuinely trusted moment, protects that baseline from tampering, distinguishes modifications from additions and deletions, separates expected change from suspicious change so its alerts stay meaningful, and chooses scheduled or real-time checking to match how fast detection must happen. Each element is straightforward, yet together they provide assurance that the files which should never change have not, and prompt warning when they have.
Ultimately, automated file integrity verification is how an administrator gains confidence that a system has not been quietly altered beneath them. Instead of trusting that critical files are unchanged, there is proof, recomputed on every check and compared against a trusted standard. The modest effort of building and scheduling the verification repays itself the first time it catches a change that no one made on purpose, surfacing in a log entry what might otherwise have festered undetected until it caused visible harm.