Skip to content

Instantly share code, notes, and snippets.

@kiler129
Last active January 3, 2026 06:41
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
@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.

@bickford
Copy link

bickford commented Jan 3, 2026

Thanks @kiler129 - let me know if you need help testing.

I've not been fast to respond on Github lately due to the time of year (and am away the next week), but when I'm back I generally respond in a day or two and would be glad to help test against a different config for you.
Cheers

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