Skip to content

Instantly share code, notes, and snippets.

@inxomnyaa
Created March 6, 2026 21:29
Show Gist options
  • Select an option

  • Save inxomnyaa/edf17f85ad9793a17f441827cd2a48b7 to your computer and use it in GitHub Desktop.

Select an option

Save inxomnyaa/edf17f85ad9793a17f441827cd2a48b7 to your computer and use it in GitHub Desktop.
Steam Shortcut Icon Fixer - Fixes your blank icons in the start menu or folders
# Steam Shortcut Icon Fixer
Write-Host "Steam Shortcut Icon Fixer" -ForegroundColor Cyan
Write-Host ("=" * 60)
Write-Host ""
# Use environment variables for paths
$defaultShortcutsPath = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Steam"
$defaultSteamPath = "C:\Program Files (x86)\Steam"
# Prompt for paths
$shortcutsPath = Read-Host "Enter shortcuts folder path (default: $defaultShortcutsPath)"
if ([string]::IsNullOrWhiteSpace($shortcutsPath)) {
$shortcutsPath = $defaultShortcutsPath
}
$steamInstallPath = Read-Host "Enter Steam installation path (default: $defaultSteamPath)"
if ([string]::IsNullOrWhiteSpace($steamInstallPath)) {
$steamInstallPath = $defaultSteamPath
}
Write-Host ""
Write-Host "Shortcuts folder: $shortcutsPath" -ForegroundColor Gray
Write-Host "Steam install path: $steamInstallPath" -ForegroundColor Gray
Write-Host ""
# Validate paths
if (-not (Test-Path $shortcutsPath)) {
Write-Host "Error: Shortcuts folder not found!" -ForegroundColor Red
exit
}
if (-not (Test-Path $steamInstallPath)) {
Write-Host "Error: Steam installation path not found!" -ForegroundColor Red
exit
}
# Parse libraryfolders.vdf to get all Steam library paths
$libraryVdfPath = Join-Path $steamInstallPath "steamapps\libraryfolders.vdf"
$steamLibraries = @($steamInstallPath)
if (Test-Path $libraryVdfPath) {
$vdfContent = Get-Content -Path $libraryVdfPath -Raw
# Extract paths from VDF file
$matches = [regex]::Matches($vdfContent, '"path"\s+"([^"]+)"')
foreach ($match in $matches) {
$libPath = $match.Groups[1].Value -replace '\\\\', '\'
if ($libPath -ne $steamInstallPath -and -not $steamLibraries.Contains($libPath)) {
$steamLibraries += $libPath
}
}
}
Write-Host "Found $($steamLibraries.Count) Steam library location(s):" -ForegroundColor Yellow
foreach ($lib in $steamLibraries) {
Write-Host " • $lib" -ForegroundColor Gray
}
Write-Host ""
# Build game ID to folder name mapping
$gameIdMap = @{}
foreach ($libPath in $steamLibraries) {
$appManifestPath = Join-Path $libPath "steamapps"
if (-not (Test-Path $appManifestPath)) {
continue
}
# Read all appmanifest_*.acf files
$manifests = Get-ChildItem -Path $appManifestPath -Filter "appmanifest_*.acf" -ErrorAction SilentlyContinue
foreach ($manifest in $manifests) {
$manifestContent = Get-Content -Path $manifest.FullName -Raw
# Extract appid
if ($manifestContent -match '"appid"\s+"(\d+)"') {
$appId = $matches[1]
# Extract installdir
if ($manifestContent -match '"installdir"\s+"([^"]+)"') {
$installDir = $matches[1]
$gameIdMap[$appId] = @{
FolderName = $installDir
LibraryPath = $libPath
}
}
}
}
}
Write-Host "Mapped $($gameIdMap.Count) game(s) from app manifests." -ForegroundColor Yellow
Write-Host ""
# Function to check if this is a mod (has gameinfo.txt)
function Test-IsMod {
param([string]$GameFolder)
$gameInfoFiles = Get-ChildItem -Path $GameFolder -Filter "gameinfo.txt" -Recurse -ErrorAction SilentlyContinue
return $gameInfoFiles.Count -gt 0
}
# Function to find icon from gameinfo.txt
function Find-ModIcon {
param([string]$GameFolder)
# Look for gameinfo.txt recursively
$gameInfoFiles = Get-ChildItem -Path $GameFolder -Filter "gameinfo.txt" -Recurse -ErrorAction SilentlyContinue
foreach ($gameInfoFile in $gameInfoFiles) {
$gameInfoContent = Get-Content -Path $gameInfoFile.FullName -Raw
# Extract icon path from gameinfo.txt
if ($gameInfoContent -match 'icon\s+"([^"]+)"') {
$iconPath = $matches[1]
$gameInfoDir = $gameInfoFile.Directory.FullName
# Try icon extensions (skip .tga and .bmp as they don't work well)
$iconExtensions = @('.ico', '.png')
foreach ($ext in $iconExtensions) {
$fullIconPath = Join-Path $gameInfoDir ($iconPath + $ext)
if (Test-Path $fullIconPath) {
return $fullIconPath
}
}
}
}
return $null
}
# Function to find custom icon in resource folder
function Find-ResourceIcon {
param([string]$GameFolder)
# Look for resource folders
$resourceFolders = Get-ChildItem -Path $gameFolder -Filter "resource" -Directory -Recurse -ErrorAction SilentlyContinue
# Only look for .ico and .png (skip .tga and .bmp)
$iconNames = @('game.ico', 'icon.ico', 'game-icon.ico', '*.ico', 'game.png', 'icon.png')
foreach ($resourceFolder in $resourceFolders) {
foreach ($iconName in $iconNames) {
$fullPath = Join-Path $resourceFolder.FullName $iconName
if (Test-Path $fullPath) {
return $fullPath
}
}
}
return $null
}
# Initialize counters
$fixed = @()
$failed = @()
# Get all .url files
$urlFiles = Get-ChildItem -Path $shortcutsPath -Filter "*.url" -ErrorAction SilentlyContinue
if ($urlFiles.Count -eq 0) {
Write-Host "No .url files found in $shortcutsPath" -ForegroundColor Yellow
exit
}
Write-Host "Found $($urlFiles.Count) shortcut(s). Processing..." -ForegroundColor Yellow
Write-Host ""
foreach ($file in $urlFiles) {
$gameName = $file.BaseName
$shortcutPath = $file.FullName
# Read the .url file to extract game ID
$urlContent = Get-Content -Path $shortcutPath -Raw
# Extract game ID from steam://rungameid/XXXXX
if ($urlContent -match 'steam://rungameid/(\d+)') {
$gameId = $matches[1]
# Look up the game in our map
if ($gameIdMap.ContainsKey($gameId)) {
$gameInfo = $gameIdMap[$gameId]
# Chain Join-Path calls properly
$gameFolder = Join-Path (Join-Path $gameInfo.LibraryPath "steamapps") "common"
$gameFolder = Join-Path $gameFolder $gameInfo.FolderName
if (Test-Path $gameFolder) {
$iconPath = $null
$isMod = Test-IsMod -GameFolder $gameFolder
# Only use mod detection if it's actually a mod
if ($isMod) {
# Priority 1: Check for mod icon in gameinfo.txt
$iconPath = Find-ModIcon -GameFolder $gameFolder
# Priority 2: Check for resource folder icons
if (-not $iconPath) {
$iconPath = Find-ResourceIcon -GameFolder $gameFolder
}
}
# Priority 3: Find the main .exe with priority matching
if (-not $iconPath) {
$allExes = Get-ChildItem -Path $gameFolder -Filter "*.exe" -Recurse |
Where-Object { $_.Name -notmatch "unins|setup|launcher|vcredist|dotnet|redist|helper|crash|report|config|benchmark" }
$exeFile = $null
# Priority 3a: Root folder only (no subfolders)
if (-not $exeFile) {
$exeFile = $allExes | Where-Object { $_.DirectoryName -eq $gameFolder } | Select-Object -First 1
}
# Priority 3b: Match game folder name
if (-not $exeFile) {
$exeFile = $allExes | Where-Object { $_.BaseName -match [regex]::Escape($gameInfo.FolderName) } | Select-Object -First 1
}
## TODO improve the following, portal revolution and reloaded fail
# Priority 3c: Common main executable names
if (-not $exeFile) {
$exeFile = $allExes | Where-Object { $_.BaseName -match "^(game|main|start|run|play)" } | Select-Object -First 1
}
# Priority 3d: Largest exe (usually the main one)
if (-not $exeFile) {
$exeFile = $allExes | Sort-Object -Property Length -Descending | Select-Object -First 1
}
if ($exeFile) {
$iconPath = $exeFile.FullName
}
}
if ($iconPath) {
# Update the .url file with icon path
# Remove any existing IconFile line
$newUrlContent = $urlContent -replace '\r?\nIconFile=.*', ''
# Add the new IconFile line with proper escaping for spaces
$newUrlContent = $newUrlContent.TrimEnd() + "`r`nIconFile=$iconPath`r`n"
Set-Content -Path $shortcutPath -Value $newUrlContent -Force
$fixed += @{
Name = $gameName
GameId = $gameId
IconPath = $iconPath
}
} else {
$failed += @{
Name = $gameName
GameId = $gameId
Reason = "No icon or .exe found"
FolderPath = $gameFolder
}
}
} else {
$failed += @{
Name = $gameName
GameId = $gameId
Reason = "Game folder not found"
FolderPath = $gameFolder
}
}
} else {
$failed += @{
Name = $gameName
GameId = $gameId
Reason = "Game ID not found in Steam app manifests"
}
}
} else {
$failed += @{
Name = $gameName
Reason = "Could not extract game ID from URL"
}
}
}
# Display summary BEFORE clearing cache
Write-Host ""
Write-Host ("=" * 60)
Write-Host "SUMMARY" -ForegroundColor Cyan
Write-Host ("=" * 60)
if ($fixed.Count -gt 0) {
Write-Host ""
Write-Host "✓ FIXED ($($fixed.Count)):" -ForegroundColor Green
foreach ($item in $fixed) {
Write-Host " • $($item.Name) (ID: $($item.GameId))" -ForegroundColor Green
Write-Host " → $($item.IconPath)" -ForegroundColor DarkGreen
}
}
if ($failed.Count -gt 0) {
Write-Host ""
Write-Host "✗ FAILED ($($failed.Count)):" -ForegroundColor Red
foreach ($item in $failed) {
Write-Host " • $($item.Name)" -ForegroundColor Red
if ($item.GameId) {
Write-Host " ID: $($item.GameId)" -ForegroundColor DarkRed
}
Write-Host " Reason: $($item.Reason)" -ForegroundColor DarkRed
if ($item.FolderPath) {
Write-Host " Expected path: $($item.FolderPath)" -ForegroundColor DarkRed
}
}
}
Write-Host ""
Write-Host "Total: $($fixed.Count) fixed, $($failed.Count) failed" -ForegroundColor Cyan
Write-Host ""
# Prompt before clearing cache
$proceed = Read-Host "Clear icon cache and restart Explorer? (Y/n)"
if ($proceed -ne "n") {
Write-Host ""
Write-Host "Clearing icon cache and restarting Explorer..." -ForegroundColor Cyan
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
Remove-Item -Path "$env:LOCALAPPDATA\IconCache.db" -Force -ErrorAction SilentlyContinue
Start-Process explorer
Start-Sleep -Seconds 2
# Open the shortcuts folder
Write-Host "Opening shortcuts folder..." -ForegroundColor Cyan
Invoke-Item $shortcutsPath
}
Write-Host "Done!" -ForegroundColor Green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment