Created
July 22, 2025 20:07
-
-
Save legendof-selda/c452021c81167d55341561e47fb7bea1 to your computer and use it in GitHub Desktop.
Steam auto shutdown after downloads
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Tracks Steam download activity and schedules a computer shutdown when downloads finish. | |
| .DESCRIPTION | |
| This script polls the Steam client’s registry and app manifest files to determine download status. | |
| It handles scenarios like network interruptions and manual cancellation by checking the presence | |
| and content of the appmanifest_<ID>.acf files. After observing a configurable number of idle | |
| cycles following a completed download, it triggers a system shutdown with a grace period to abort. | |
| .PARAMETER checkInterval | |
| Seconds to wait between each download-status check. | |
| .PARAMETER shutdownGracePeriod | |
| Time, in seconds, between issuing the shutdown command and actual power‑off. | |
| .PARAMETER idleCyclesBeforeShutdown | |
| Number of consecutive “no download” checks (with manifest intact) required before initiating shutdown. | |
| #> | |
| param ( | |
| [int] $checkInterval = 60, # default: 60s between checks | |
| [int] $shutdownGracePeriod = 900, # default: 15m until shutdown | |
| [int] $idleCyclesBeforeShutdown = 5 # default: 5 idle loops | |
| ) | |
| # Possible registry locations for Steam’s installation directory | |
| $ClientRegKeys = @( | |
| 'HKLM:\SOFTWARE\WOW6432Node\Valve\Steam', | |
| 'HKLM:\SOFTWARE\Valve\Steam' | |
| ) | |
| # Registry path where per‑app download flags live | |
| $AppsStatusRegPath = 'HKCU:\Software\Valve\Steam\Apps\*' | |
| $ClientProcessName = 'steam' | |
| $ManifestNameFormat = 'appmanifest_{0}.acf' | |
| function Resolve-ClientInstallDir { | |
| foreach ($key in $ClientRegKeys) { | |
| if (Test-Path $key) { | |
| $props = Get-ItemProperty -Path $key -Name InstallPath -ErrorAction SilentlyContinue | |
| if ($props.InstallPath -and (Test-Path $props.InstallPath)) { | |
| return $props.InstallPath | |
| } | |
| } | |
| } | |
| return $null | |
| } | |
| function Get-ActiveTransfers { | |
| <# | |
| .SYNOPSIS | |
| Returns registry entries for apps currently downloading (Updating = 1). | |
| #> | |
| Get-ItemProperty -Path $AppsStatusRegPath -Name Updating -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Updating -eq 1 } | |
| } | |
| function Check-TransferComplete { | |
| <# | |
| .SYNOPSIS | |
| Reads an appmanifest file and returns $true if BytesDownloaded == BytesToDownload. | |
| .PARAMETER manifestPath | |
| Full filepath to the appmanifest_<AppID>.acf file. | |
| #> | |
| param ( | |
| [Parameter(Mandatory)] | |
| [string] $manifestPath | |
| ) | |
| Write-Output ("[{0}] Checking manifest: {1}" -f (Get-Date -Format 'HH:mm:ss'), $manifestPath) | |
| if (-not (Test-Path $manifestPath)) { | |
| Write-Output " → Manifest missing; assuming canceled/uninstalled." | |
| return $false | |
| } | |
| $content = Get-Content -Path $manifestPath | |
| $totalMatch = $content | Select-String -Pattern '"BytesToDownload"\s+"(\d+)"' | |
| $doneMatch = $content | Select-String -Pattern '"BytesDownloaded"\s+"(\d+)"' | |
| if ($totalMatch -and $doneMatch) { | |
| $total = [int64]$totalMatch.Matches[0].Groups[1].Value | |
| $done = [int64]$doneMatch.Matches[0].Groups[1].Value | |
| Write-Output (" → BytesToDownload = {0:N0}, BytesDownloaded = {1:N0}" -f $total, $done) | |
| return ($total -eq $done) | |
| } | |
| Write-Output " → Could not find byte‑count entries." | |
| return $false | |
| } | |
| # —————————————— | |
| # Start of main logic | |
| # —————————————— | |
| $installDir = Resolve-ClientInstallDir | |
| if (-not $installDir) { | |
| Write-Error "Steam install directory not found. Aborting." | |
| exit 1 | |
| } | |
| $state = 'AwaitingClientStart' | |
| $currentAppID = $null | |
| $idleCounter = 0 | |
| while ($true) { | |
| $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' | |
| Write-Output "==============================" | |
| Write-Output ("[{0}] STATE: {1} | AppID: {2} | IdleCount: {3}" -f $timestamp, $state, ($currentAppID | % { $_ }), $idleCounter) | |
| # Check if Steam is still running | |
| $proc = Get-Process $ClientProcessName -ErrorAction SilentlyContinue | |
| if (-not $proc) { | |
| Write-Output ("[{0}] Steam process not found. Exiting monitor." -f $timestamp) | |
| break | |
| } | |
| switch ($state) { | |
| 'AwaitingClientStart' { | |
| Write-Output ("[{0}] Waiting for Steam to launch..." -f $timestamp) | |
| Start-Sleep -Seconds 3 | |
| if (Get-Process $ClientProcessName -ErrorAction SilentlyContinue) { | |
| Write-Output ("[{0}] Steam detected." -f (Get-Date -Format 'HH:mm:ss')) | |
| $state = 'LookingForDownloads' | |
| } | |
| } | |
| 'LookingForDownloads' { | |
| $transfer = Get-ActiveTransfers | |
| if ($transfer) { | |
| $currentAppID = $transfer.PSChildName | |
| Write-Output ("[{0}] Detected new download for AppID {1}." -f (Get-Date -Format 'HH:mm:ss'), $currentAppID) | |
| $idleCounter = 0 | |
| $state = 'Monitoring' | |
| } | |
| else { | |
| Write-Output ("[{0}] No active downloads." -f (Get-Date -Format 'HH:mm:ss')) | |
| Start-Sleep -Seconds 3 | |
| } | |
| } | |
| 'Monitoring' { | |
| $transfer = Get-ActiveTransfers | |
| if ($transfer) { | |
| Write-Output ("[{0}] Downloading AppID {1}…" -f (Get-Date -Format 'HH:mm:ss'), $transfer.PSChildName) | |
| $currentAppID = $transfer.PSChildName | |
| $idleCounter = 0 | |
| } | |
| else { | |
| $manifest = ($ManifestNameFormat -f $currentAppID) | |
| $manifestPath = Join-Path $installDir "steamapps\$manifest" | |
| if (Check-TransferComplete -manifestPath $manifestPath) { | |
| Write-Warning ("[{0}] Download complete. Idle check {1}/{2}." ` | |
| -f (Get-Date -Format 'HH:mm:ss'), ($idleCounter + 1), $idleCyclesBeforeShutdown) | |
| if ($idleCounter -ge $idleCyclesBeforeShutdown) { | |
| Write-Output ("[{0}] Scheduling shutdown in {1} seconds…" -f (Get-Date -Format 'HH:mm:ss'), $shutdownGracePeriod) | |
| Start-Process -FilePath shutdown ` | |
| -ArgumentList "/s","/f","/t",$shutdownGracePeriod -NoNewWindow | |
| Write-Output ("[{0}] To abort: press any key and run `shutdown /a`." -f (Get-Date -Format 'HH:mm:ss')) | |
| $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') | |
| exit 0 | |
| } | |
| $idleCounter++ | |
| } | |
| else { | |
| Write-Output ("[{0}] Transfer interrupted or manifest removed. Resetting state." -f (Get-Date -Format 'HH:mm:ss')) | |
| $state = 'LookingForDownloads' | |
| continue | |
| } | |
| } | |
| $nextCheck = (Get-Date).AddSeconds($checkInterval).ToString('yyyy-MM-dd HH:mm:ss') | |
| Write-Output ("[{0}] Next check at {1}" -f (Get-Date -Format 'HH:mm:ss'), $nextCheck) | |
| Start-Sleep -Seconds $checkInterval | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FYI, this is an AI generated Script.