Skip to content

Instantly share code, notes, and snippets.

@kiler129
Last active December 16, 2025 20:28
Show Gist options
  • Select an option

  • Save kiler129/ddc62e359b0fa7be10b88e6da8bb7928 to your computer and use it in GitHub Desktop.

Select an option

Save kiler129/ddc62e359b0fa7be10b88e6da8bb7928 to your computer and use it in GitHub Desktop.
Mass-validate all Steam games on Windows
########################################################################################################################
# Mass Steam Game Validator
#
# This script, meant to be used on Windows, validates whole Steam libraries. By default, Steam client can only do it
# one-by-one which is cumbersome in larger libraries. This tool uses the Valve's official steamCMD.
# The tool, by default, will attempt to validate all games in all libraries found, using the default account. Use -Help
# to see available options.
#
# To run this tool, save it as ".ps1" file, start Windows PowerShell/Windows Terminal, and type
# .\Validate-Steam-Games.ps1 in the console. You may need to enable PS scripts (https://superuser.com/a/106362) first.
#
# License: MIT; (c) Gregory House (https://github.com/kiler129)
param (
[switch]$DryRun,
[string]$Skip,
[switch]$Help,
[string]$LibraryPath,
[string]$User,
[switch]$Verbose
)
# --- Help ---
if ($Help) {
$scriptName = $MyInvocation.MyCommand.Name
Write-Host "Mass Steam Game Validator" -ForegroundColor Yellow
Write-Host ""
Write-Host "USAGE:" -ForegroundColor Cyan
Write-Host " .\$scriptName [-DryRun] [-Skip <AppIDs>] [-LibraryPath <Path>] [-User <Name>] [-Verbose] [-Help]" -ForegroundColor White
Write-Host ""
Write-Host "OPTIONS:" -ForegroundColor Cyan
Write-Host " -DryRun Verifies Steam login and lists games, but does not validate them." -ForegroundColor White
Write-Host " -Skip <IDs> Comma-separated AppIDs to skip (e.g. -Skip 346110,123456)." -ForegroundColor White
Write-Host " -LibraryPath <Path> Use a specific Steam library root (folder containing 'steamapps')." -ForegroundColor White
Write-Host " If omitted, libraries are auto-detected from registry and libraryfolders.vdf." -ForegroundColor White
Write-Host " -User <Name> Steam username to use. If omitted, auto-detected from loginusers.vdf (or prompted)." -ForegroundColor White
Write-Host " -Verbose Echo the exact steamcmd command and stream its live output." -ForegroundColor White
Write-Host " -Help Show this help screen and exit." -ForegroundColor White
exit
}
# --- Parse Skip list ---
$skipList = @()
if ($Skip) {
$skipList = $Skip.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ }
if ($skipList.Count) { Write-Host "[INFO] Will skip AppIDs: $($skipList -join ', ')" -ForegroundColor Yellow }
}
# --- Steam install path ---
$steamReg = Get-ItemProperty -Path "HKCU:\Software\Valve\Steam" -ErrorAction SilentlyContinue
if (-not $steamReg -or -not $steamReg.SteamPath) {
Write-Host "[ERROR] Could not find Steam in registry." -ForegroundColor Red
$steamPath = Read-Host "Enter your Steam install path (e.g. C:\Program Files (x86)\Steam)"
} else {
$steamPath = $steamReg.SteamPath
}
$libraryFile = Join-Path $steamPath "steamapps\libraryfolders.vdf"
$loginUsersFile = Join-Path $steamPath "config\loginusers.vdf"
# --- Detect usernames from loginusers.vdf ---
function Detect-SteamUsers {
$users = @()
if (Test-Path -LiteralPath $loginUsersFile) {
$content = Get-Content -LiteralPath $loginUsersFile -Raw
$rx = '"AccountName"\s*"([^"]+)"'
$matches = [regex]::Matches($content, $rx)
foreach ($m in $matches) { $users += $m.Groups[1].Value }
}
$users | Where-Object { $_ -and $_.Trim() -ne '' } | Select-Object -Unique
}
$detectedUsers = Detect-SteamUsers
# --- Resolve steamcmd ---
function Resolve-SteamCmd {
$cmd = Get-Command steamcmd -ErrorAction SilentlyContinue
if ($cmd) {
Write-Host "[INFO] Found steamcmd in PATH." -ForegroundColor Green
return "steamcmd"
}
Write-Host "[ERROR] steamcmd not found in PATH." -ForegroundColor Red
if (Get-Command winget -ErrorAction SilentlyContinue) {
Write-Host "Install it with:" -ForegroundColor Cyan
Write-Host " winget install steamcmd" -ForegroundColor White
} else {
Write-Host "Download: https://developer.valvesoftware.com/wiki/SteamCMD" -ForegroundColor Cyan
}
$choice = Read-Host "Do you want to manually enter the path now? (Y/N)"
if ($choice -notmatch '^[Yy]$') { throw "steamcmd is required but not installed or provided." }
while ($true) {
$candidate = Read-Host "Enter full path to steamcmd.exe (or Ctrl+C to cancel)"
if ([string]::IsNullOrWhiteSpace($candidate)) { Write-Host "[WARN] Path cannot be empty." -ForegroundColor Yellow; continue }
$candidate = $candidate.Trim('"')
if (-not (Test-Path -LiteralPath $candidate)) { Write-Host "[ERROR] File not found: $candidate" -ForegroundColor Red; continue }
try {
& $candidate +quit | Out-Null
if ($LASTEXITCODE -eq 0) { Write-Host "[INFO] steamcmd verified at: $candidate" -ForegroundColor Green; return $candidate }
Write-Host "[ERROR] steamcmd exited with code $LASTEXITCODE when tested." -ForegroundColor Red
} catch { Write-Host "[ERROR] Failed to run steamcmd: $($_.Exception.Message)" -ForegroundColor Red }
}
}
# --- Resolve username ---
function Resolve-SteamUser {
param([string]$UserOverride, [string[]]$Detected)
if (-not [string]::IsNullOrWhiteSpace($UserOverride)) {
Write-Host "[INFO] Using specified Steam user: $UserOverride" -ForegroundColor Green
return $UserOverride
}
if ($Detected.Count -eq 1) {
Write-Host "[INFO] Auto-detected Steam user: $($Detected[0])" -ForegroundColor Green
return $Detected[0]
}
if ($Detected.Count -gt 1) {
Write-Host "[INFO] Multiple Steam users detected:" -ForegroundColor Yellow
for ($i = 0; $i -lt $Detected.Count; $i++) {
Write-Host " [$i] $($Detected[$i])"
}
while ($true) {
$choice = Read-Host "Select user number"
if ($choice -match '^\d+$' -and [int]$choice -ge 0 -and [int]$choice -lt $Detected.Count) {
$selected = $Detected[[int]$choice]
Write-Host "[INFO] Selected Steam user: $selected" -ForegroundColor Green
return $selected
}
Write-Host "[WARN] Invalid choice, try again." -ForegroundColor Yellow
}
}
Write-Host "[WARN] No Steam usernames detected in loginusers.vdf." -ForegroundColor Yellow
$manual = Read-Host "Enter your Steam username"
Write-Host "[INFO] Using manually entered Steam user: $manual" -ForegroundColor Green
return $manual
}
# --- Test login (interactive always) ---
function Test-SteamLogin {
param([string]$SteamCmdPath, [string]$SteamUser, [switch]$Verbose)
Write-Host "[INFO] Testing Steam login for user '$SteamUser'..." -ForegroundColor Cyan
Write-Host " If cached credentials are missing, SteamCMD will ask for your password/Steam Guard." -ForegroundColor Yellow
Write-Host " This prompt is from the official Steam tool, not from this script." -ForegroundColor Yellow
$args = @("+login", $SteamUser, "+quit")
if ($Verbose) { Write-Host "[VERBOSE] Executing: $SteamCmdPath $($args -join ' ')" -ForegroundColor Magenta }
& $SteamCmdPath @args
$exit = $LASTEXITCODE
if ($exit -eq 0) {
Write-Host "[INFO] Steam login successful." -ForegroundColor Green
} else {
Write-Host "[ERROR] Steam login failed with exit code $exit." -ForegroundColor Red
throw "Steam login failed. Cannot proceed."
}
}
# --- Steam libraries (return roots, not steamapps) ---
function Get-SteamLibraries {
param([string]$ManualPath)
if ($ManualPath) { return ,$ManualPath }
$libs = New-Object System.Collections.Generic.List[string]
$libs.Add($steamPath)
if (Test-Path -LiteralPath $libraryFile) {
$content = Get-Content -LiteralPath $libraryFile
foreach ($line in $content) {
if ($line -match '"path"\s+"(.+)"') {
$raw = $matches[1] -replace '\\\\','\'
try {
$resolved = (Resolve-Path $raw -ErrorAction SilentlyContinue)
if ($resolved) {
$libs.Add($resolved.Path)
} else {
$libs.Add($raw)
}
} catch { $libs.Add($raw) }
}
}
}
$libs | ForEach-Object { $_.ToLowerInvariant() } | Sort-Object -Unique | ForEach-Object {
try { (Resolve-Path $_).Path } catch { $_ }
}
}
# --- Parse manifest for name and installdir ---
function Get-AppInfoFromManifest($manifestPath) {
$name = "<Unknown>"
$installdir = $null
try {
$hitName = Select-String -Path $manifestPath -Pattern '"name"\s+"([^"]+)"' -ErrorAction SilentlyContinue | Select-Object -First 1
if ($hitName -and $hitName.Matches.Count -gt 0) { $name = $hitName.Matches[0].Groups[1].Value }
$hitDir = Select-String -Path $manifestPath -Pattern '"installdir"\s+"([^"]+)"' -ErrorAction SilentlyContinue | Select-Object -First 1
if ($hitDir -and $hitDir.Matches.Count -gt 0) { $installdir = $hitDir.Matches[0].Groups[1].Value }
} catch {}
[PSCustomObject]@{ Name = $name; InstallDir = $installdir }
}
# --- Validate one game at its real install path ---
function Validate-App([string]$appId, [string]$gameName, [string]$installPath) {
if ($skipList -contains $appId) { Write-Host "[SKIP] $appId ($gameName)" -ForegroundColor DarkYellow; return }
if ($DryRun) { Write-Host "[DRYRUN] Would validate $appId ($gameName) at [$installPath]" -ForegroundColor DarkCyan; return }
if (-not (Test-Path -LiteralPath $installPath)) {
Write-Host "[WARN] Install path not found, creating (SteamCMD may re-download): $installPath" -ForegroundColor Yellow
New-Item -ItemType Directory -Force -Path $installPath | Out-Null
}
$args = @(
"+force_install_dir", $installPath,
"+login", $steamUser,
"+app_update", $appId, "validate",
"+quit"
)
Write-Host "[INFO] Validating $appId ($gameName) at [$installPath]..." -ForegroundColor Cyan
if ($Verbose) { Write-Host "[VERBOSE] Executing: $steamCmdPath $($args -join ' ')" -ForegroundColor Magenta }
& $steamCmdPath @args
if ($LASTEXITCODE -eq 0) { Write-Host "[SUCCESS] $gameName validated." -ForegroundColor Green }
else { Write-Host "[ERROR] $gameName failed with code $LASTEXITCODE." -ForegroundColor Red }
}
# --- Main ---
Write-Host "=======================================" -ForegroundColor Yellow
Write-Host ("Mass Steam Game Validation {0} Started: {1}" -f ($(if ($DryRun) {"(DRY RUN)"} else {""}), (Get-Date))) -ForegroundColor Yellow
Write-Host "=======================================" -ForegroundColor Yellow
Write-Host "NOTE: Validation checks files and only re-downloads missing/corrupted ones, not full reinstall." -ForegroundColor Yellow
$steamCmdPath = Resolve-SteamCmd
$steamUser = Resolve-SteamUser -UserOverride $User -Detected $detectedUsers
Test-SteamLogin -SteamCmdPath $steamCmdPath -SteamUser $steamUser -Verbose:$Verbose
$libraries = Get-SteamLibraries -ManualPath $LibraryPath
foreach ($libRoot in $libraries) {
$steamapps = Join-Path $libRoot "steamapps"
if (-not (Test-Path -LiteralPath $steamapps)) {
Write-Host "[WARN] No 'steamapps' in library: $libRoot" -ForegroundColor Yellow
continue
}
Write-Host "[LIB] $libRoot" -ForegroundColor DarkYellow
Get-ChildItem -LiteralPath $steamapps -Filter "appmanifest_*.acf" -ErrorAction SilentlyContinue |
ForEach-Object {
if ($_.Name -match "appmanifest_(\d+).acf") {
$appId = $matches[1]
$info = Get-AppInfoFromManifest $_.FullName
$gameName = $info.Name
$installdir = $info.InstallDir
if ([string]::IsNullOrWhiteSpace($installdir)) {
Write-Host "[WARN] Manifest missing 'installdir' for AppID $appId; skipping to avoid mis-install." -ForegroundColor Yellow
return
}
$installPath = Join-Path (Join-Path $libRoot "steamapps\common") $installdir
Validate-App $appId $gameName $installPath
}
}
}
Write-Host "=======================================" -ForegroundColor Yellow
Write-Host "Validation Completed: $(Get-Date)" -ForegroundColor Yellow
Write-Host "=======================================" -ForegroundColor Yellow
@bickford
Copy link

I'm wondering, have SteamCMD's command-line options changed?

Your script functions and it does validate games - but currently it seems to download the entire game again as part of the validation process. Eg: I left it run validating today for many hours, and it got up to Robocop: Rogue City. It was downloading the entire game again as part of the 'validation' (20-something gigabytes), so I cancelled out and re-opened the Steam GUI and instead validated that game directly: done in 2 minutes, without downloading more than a few megabytes.

@kiler129
Copy link
Author

@bickford I actually had this issue before and it was related to the script seeing a different install dir than the Steam GUI vs SteamCMD. I will have some time next week to look into that, as probably there's some edge case, or the SteamCMD intepretation changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment