A .NET 10.0 tool that verifies downloaded torrent content against a .torrent file using SHA1 piece hashes — the same method the BitTorrent protocol uses internally.
Available as both a CLI tool and a PowerShell module.
If you have torrent files sitting in long-term storage, there's no easy way to check whether they've been corrupted over time — bit rot, bad sectors, or accidental modification. BitTorrentVerify lets you quickly re-verify any downloaded torrent against its original .torrent metadata to confirm the content is still intact.
BitTorrentVerify <torrent-file> <torrent-folder>
Example:
dotnet run --project BitTorrentVerify -- "my-download.torrent" "D:\Downloads\my-download"
Torrent: my-download
Files: 2
Pieces: 2048
Piece Size: 512 KB
Total Size: 1.02 GB
Verifying: 100.0% (Piece 2048/2048)
=== Verification Results ===
[PASS] file1.mkv
Size: 800 MB
Pieces: 1600/1600 valid
[FAIL] file2.mkv
Size: 220 MB
Pieces: 0/448 valid
Failed pieces: 1600, 1601, 1602, ...
=== Summary ===
Total files: 2
Valid: 1
Invalid: 1
Partially valid: 0
Errors: 0
Verification FAILED.
Exit codes: 0 = all files passed, 1 = one or more files failed or errored.
The BitTorrentVerify.PowerShell project provides an Invoke-TorrentVerification cmdlet. It supports pipeline input from Get-ChildItem, making batch verification straightforward.
dotnet build BitTorrentVerify.PowerShell -c Release
Import-Module .\BitTorrentVerify.PowerShell\bin\Release\net10.0\BitTorrentVerify.PowerShell.dll| Parameter | Type | Description |
|---|---|---|
-TorrentFile |
string |
Path to the .torrent file. Accepts pipeline input; binds FullName from FileInfo objects. |
-BaseFolder |
string |
Folder where torrent content lives. Mandatory. Bindable per-object from the pipeline. |
-UseSubfolder |
switch | Appends the torrent's name to -BaseFolder before verifying. Useful when each torrent is in its own subfolder under a common root. |
-PassThru |
switch | Outputs individual FileVerificationStatus objects instead of the per-torrent summary. |
Single torrent — BaseFolder is the torrent's own content folder:
Invoke-TorrentVerification -TorrentFile "movie.torrent" -BaseFolder "D:\Downloads\movie"Batch — each torrent has a subfolder named after it under D:\Downloads:
Get-ChildItem "C:\Torrents\*.torrent" | Invoke-TorrentVerification -BaseFolder "D:\Downloads" -UseSubfolderPer-torrent folders via pipeline property binding:
$items = @(
[pscustomobject]@{ FullName = "C:\Torrents\showA.torrent"; BaseFolder = "D:\Downloads\ShowA" },
[pscustomobject]@{ FullName = "C:\Torrents\showB.torrent"; BaseFolder = "E:\Media\ShowB" }
)
$items | Invoke-TorrentVerificationFilter to failed files only:
Get-ChildItem "C:\Torrents\*.torrent" |
Invoke-TorrentVerification -BaseFolder "D:\Downloads" -UseSubfolder -PassThru |
Where-Object { $_.Status -ne 'Valid' }Default (one per torrent):
| Property | Type | Description |
|---|---|---|
TorrentFile |
string |
Path to the .torrent file |
TorrentName |
string |
Display name from the torrent metadata |
Passed |
bool |
$true if all files are Valid |
Files |
FileVerificationStatus[] |
Per-file results |
With -PassThru (one per file inside each torrent):
| Property | Type | Description |
|---|---|---|
FilePath |
string |
Relative path of the file |
FileSize |
long |
File size in bytes |
Status |
FileStatus |
Valid, Invalid, PartiallyValid, or Error |
TotalPieces |
int |
Number of pieces overlapping this file |
ValidPieces |
int |
Number of pieces that passed |
FailedPieces |
int[] |
Indices of failed pieces |
Errors |
(int, Exception)[] |
Piece-level errors |
BitTorrent divides all files into fixed-size pieces (typically 256 KB–4 MB) and stores a SHA1 hash for each piece in the .torrent file. Pieces can span across file boundaries in multi-file torrents.
BitTorrentVerify reads each piece from disk, computes its SHA1 hash, and compares it against the expected hash. It then maps piece results back to individual files to produce per-file status.
dotnet build
dotnet test