PowerShell Cmdlets ↔ Windows Server Backup: Verifying Restored Data Integrity
0 reputation · 13 Jan 2023, 03:47 UTC
0 reputation · 13 Jan 2023, 03:47 UTC
After restoring a backup with Restore‑WBBackupSet, the goal is to confirm that every recovered file is identical to its pre‑backup state. The cmdlet provides no automatic integrity check; users must manually compare file hashes or generate a restoration manifest.
Key constraints include: the cmdlet does not expose VSS options such as “copy‑only”; restoring to a new volume preserves timestamps but may alter NTFS ACLs unless –PreserveSecurity is used; and the –RestoreDirectory parameter overwrites existing files without prompting.
Because of these uncertainties, the following questions arise:
32775 reputation · 13 Jan 2023, 08:55 UTC
The Restore‑WBBackupSet (or Restore‑WBItem) cmdlet restores files from a Windows Server Backup but does not perform any automatic integrity check. It requires elevated privileges and access to the backup catalog.
To guarantee that every recovered file is bit‑for‑bit identical to its pre‑backup state, you must compare cryptographic hashes of the original source and the restored copy. Hash comparison detects any change in file content, while metadata such as timestamps or ACLs can be handled separately.
‑Destination.# Parameters – adjust as needed
$src = 'D:\Data' # original source
$dst = 'E:\Restore' # where Restore‑WBBackupSet wrote the files
# Get all files recursively
$srcFiles = Get-ChildItem -Path $src -File -Recurse
$dstFiles = Get-ChildItem -Path $dst -File -Recurse
# Build hash tables keyed by relative path
$srcHash = @{}
foreach ($f in $srcFiles) {
$rel = $f.FullName.Substring($src.Length).TrimStart('\\')
$srcHash[$rel] = (Get-FileHash -Path $f.FullName -Algorithm SHA256).Hash
}
$dstHash = @{}
foreach ($f in $dstFiles) {
$rel = $f.FullName.Substring($dst.Length).TrimStart('\\')
$dstHash[$rel] = (Get-FileHash -Path $f.FullName -Algorithm SHA256).Hash
}
# Compare
$allKeys = ($srcHash.Keys + $dstHash.Keys) | Sort-Object -Unique
$mismatch = @()
foreach ($k in $allKeys) {
if (-not $srcHash.ContainsKey($k)) { $mismatch += "$k : missing in source" }
elseif (-not $dstHash.ContainsKey($k)) { $mismatch += "$k : missing in destination" }
elseif ($srcHash[$k] -ne $dstHash[$k]) { $mismatch += "$k : hash mismatch" }
}
if ($mismatch.Count -eq 0) {
Write-Host "All files match – integrity verified."
} else {
Write-Warning "Integrity issues found:"
$mismatch | ForEach-Object { Write-Host $_ }
}
The restore operation does not retain NTFS ACLs by default. To preserve them, include the ‑PreserveSecurity switch when calling Restore‑WBItem (or ensure the backup policy was created with security preservation). Example:
Restore‑WBItem -Item $backupItem -Destination $dst -PreserveSecurity -Quiet
If you omitted this switch, you must re‑apply ACLs after the restore using tools like icacls or a PowerShell script that reads a backup of the ACLs (e.g., Get‑ACL exported before backup).
A simple manifest can be produced alongside the hash comparison:
# After computing hashes, output a CSV manifest
$manifest = foreach ($rel in $srcHash.Keys) {
[PSCustomObject]@{
RelativePath = $rel
SourceHash = $srcHash[$rel]
DestHash = if ($dstHash.ContainsKey($rel)) { $dstHash[$rel] } else { '' }
Match = if ($srcHash[$rel] -eq $dstHash[$rel]) { 'OK' } else { 'MISMATCH' }
}
}
$manifest | Export-Csv -Path 'C:\Temp\RestoreManifest.csv' -NoTypeInformation
Write-Host "Manifest written to C:\Temp\RestoreManifest.csv"
If you need to verify that alternate data streams (ADS) or reparse points are also intact, please confirm whether such streams are present in your data; the hash‑only approach compares only the primary data stream.
Use comments to ask for clarification. Post a solution as an answer.
32,775 reputation · 13 Jan 2023, 08:41 UTC
The proposed Get-FileHash comparison works only if the source tree is unchanged since the backup ran. If any file was modified, moved, or deleted after the backup, a live source-to-restore diff will report mismatches that have nothing to do with restore corruption. The more reliable pattern is to capture a hash manifest at backup time (or from a VSS snapshot taken alongside it), archive that CSV with the backup set, and compare the restored tree against the frozen manifest rather than the live source.
Two smaller points worth adding:
-PreserveSecurity is necessary but not self-proving. You can spot-check with Get-Acl and compare the SDDL strings: (Get-Acl $src).Sddl -eq (Get-Acl $dst).Sddl. This catches silent permission resets when restoring across volumes.Get-ChildItem -File -Recurse hash pass skips ADS content and can follow or skip junctions inconsistently, so exclude or handle them explicitly.Test the whole loop on a small folder first, as the earlier verification step suggests.