Skip to content

Instantly share code, notes, and snippets.

@kiler129
Last active September 25, 2025 15:46
Show Gist options
  • Save kiler129/ddc62e359b0fa7be10b88e6da8bb7928 to your computer and use it in GitHub Desktop.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment