Last active
February 10, 2025 11:25
-
-
Save darkliquid/69db089694756c07b322102227daaff7 to your computer and use it in GitHub Desktop.
This file contains 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
# DLCs specifies how we want to handle DLCs when updating games. | |
enum DLCs { | |
All # Update all DLCs | |
Installed # Only update installed DLCs | |
None # Don't update any DLCs | |
} | |
# legendaryUpdateCustomisations is a custom object that we use to store update settings | |
# to the update process for a specific app. | |
class legendaryUpdateCustomisations { | |
[string[]] $Except | |
[string[]] $Only | |
[string[]] $Tags | |
[DLCs] $DLCs | |
legendaryUpdateCustomisations([string]$file) { | |
$data = ConvertFrom-Json (Get-Content -Raw $file) | |
$this.Except = $data.Except | |
$this.Only = $data.Only | |
$this.Tags = $data.Tags | |
switch ($data.DLCs) { | |
'All' { | |
$this.DLCs = [DLCs]::All | |
break | |
} | |
'Installed' { | |
$this.DLCs = [DLCs]::Installed | |
break | |
} | |
'None' { | |
$this.DLCs = [DLCs]::None | |
break | |
} | |
default { | |
Write-Warning "Invalid DLCs value $($data.DLCs), using Installed" | |
$this.DLCs = [DLCs]::Installed | |
break | |
} | |
} | |
} | |
} | |
function log { | |
if ($VerbosePreference -eq 'continue') { | |
Write-Information "$($args -join ' ')" -InformationAction Continue | |
} | |
} | |
function humanReadableSize { | |
param( | |
[Parameter(ValueFromPipeline = $true)][ValidateNotNullOrEmpty()][float]$number | |
) | |
$sizes = @('KB', 'MB', 'GB', 'TB', 'PB') | |
for ($x = 0; $x -lt $sizes.count; $x++) { | |
# PS magically coerces the a number with its byte unit | |
# in a string to a numeric byte count, so we can easily | |
# compare the number to a byte unit. | |
if ($number -lt [int64]"1$($sizes[$x])") { | |
if ($x -eq 0) { | |
return "$number B" | |
} | |
else { | |
$num = $number / [int64]"1$($sizes[$x-1])" | |
$num = '{0:N2}' -f $num | |
return "$num $($sizes[$x-1])" | |
} | |
} | |
} | |
} | |
# GetLegendaryFiles gets a list of files for a specific app from legendary. | |
function getLegendaryFiles { | |
param( | |
[Parameter(Mandatory)][string]$appName | |
) | |
# Get the list of files for the app, and add annotations for excluded, only and tagged files. | |
$files = legendary list-files $appName --csv 2>$errOutput | ConvertFrom-Csv | |
if (![string]::IsNullOrEmpty($errOutput)) { | |
log $errOutput | |
} | |
$files | ForEach-Object { | |
$_ | Add-Member -Force -MemberType NoteProperty -Name 'exclude' -Value $false | |
$_ | Add-Member -Force -MemberType NoteProperty -Name 'only' -Value $false | |
$_ | Add-Member -Force -MemberType NoteProperty -Name 'tagged' -Value $false | |
} | |
return $files | |
} | |
function updateLegendaryApp { | |
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'low')] | |
param( | |
[Parameter(Mandatory)]$app, | |
[Parameter][string[]]$ExceptFiles, | |
[Parameter][string[]]$OnlyFiles, | |
[Parameter][string[]]$OnlyTags, | |
[Parameter][DLCs]$DLCs | |
) | |
$appName = $app.'App name' | |
$appTitle = $app.'App title' | |
if ([string]::IsNullOrEmpty($app.'App title')) { | |
$appTitle = $appName | |
} | |
# Build up our parameter list for the legendary command. | |
$params = @('-y') | |
# DLCs | |
$prompt = "Update $($appTitle)" | |
switch ($DLCs) { | |
All { | |
$params += '--with-dlcs' | |
$prompt += ', including all missing and installed DLCs' | |
break | |
} | |
None { | |
$params += '--skip-dlcs' | |
$prompt += ', ignoring all DLCs' | |
break | |
} | |
default { | |
$prompt += ', including all installed DLCs' | |
break | |
} | |
} | |
# Onlys | |
if ($OnlyFiles.Count -gt 0) { | |
log "[$($appTitle)] Only updating the following files (if they match the selected install tags):" | |
$OnlyFiles | ForEach-Object { | |
log "[$($appTitle)] $($_)" | |
$params += '--prefix' | |
$params += $_ | |
} | |
} | |
# Except | |
if ($ExceptFiles.Count -gt 0) { | |
log "[$($appTitle)] Excluding the following files:" | |
$ExceptFiles | ForEach-Object { | |
log "[$($appTitle)] $($_)" | |
$params += '--exclude' | |
$params += $_ | |
} | |
} | |
# Tags | |
if ($OnlyTags.Count -gt 0) { | |
log "[$($appTitle)] Only updating for the following install tags:" | |
$OnlyTags | ForEach-Object { | |
log "[$($appTitle)] $($_)" | |
$params += '--install-tag' | |
$params += $_ | |
} | |
} | |
# Confirmation | |
if ($PSCmdlet.ShouldProcess($prompt, $appTitle, 'Update')) { | |
# Run the legendary command to update the app. | |
legendary install $appName --update-only @params 2>$errOutput | |
if (![string]::IsNullOrEmpty($errOutput)) { | |
log $errOutput | |
} | |
} | |
} | |
# Get-LegendaryUpdates gets a list of apps that have updates available from legendary. | |
function Get-LegendaryUpdates { | |
$updates = legendary list-installed --check-updates --csv 2>$errOutput | |
if (![string]::IsNullOrEmpty($errOutput)) { | |
log $errOutput | |
} | |
return $updates | ConvertFrom-Csv | Where-Object -Property 'Update available' -eq 'True' | |
} | |
# applyLegendaryCustomisations loads update settings for a specific app from an update settings file and | |
# applies them to the legendary command invocation | |
function applyLegendaryCustomisations($app) { | |
$appName = $app.'App name' | |
$appTitle = $app.'App title' | |
if ([string]::IsNullOrEmpty($app.'App title')) { | |
$appTitle = $appName | |
} | |
# Determine the path to our update settings json for the specific app. | |
$updatesSettingsFile = "$($env:APPDATA)\Legendary\$($appName)\updates.json" | |
# If no file exists, return. | |
if (!(Test-Path $updatesSettingsFile)) { | |
log "[$($appTitle)] No update settings file found at $($updatesSettingsFile)" | |
return @{} | |
} | |
log "[$($appTitle)] Processing update settings file $($updatesSettingsFile)" | |
$settings = [legendaryUpdateCustomisations]::new($updatesSettingsFile) | |
if (!$settings) { | |
Write-Error "[$($appTitle)] update settings file is corrupt or invalid. Continue without update settings?" -ErrorAction Inquire | |
} | |
if ($settings.Only.Count -gt 0 -and $settings.Except.Count -gt 0) { | |
Write-Warning "[$($appTitle)] Only and Except are mutally exclusive, ignoring Except." | |
$settings.Except = @() | |
} | |
# Get the list of files for the app and annotate each file so we | |
# can filter them based on the update settings. | |
$appFiles = getLegendaryFiles($appName) | |
$tags = @() | |
$appFiles | ForEach-Object { | |
$file = $_ | |
# Mark file as excludes if it's path matches any of the Except patterns. | |
foreach ($pattern in $settings.Except) { | |
if ($_.path -match $pattern) { | |
$file.exclude = $true | |
break | |
} | |
} | |
foreach ($pattern in $settings.Only) { | |
if ($file.path -match $pattern) { | |
$file.only = $true | |
break | |
} | |
} | |
foreach ($pattern in $settings.Tags) { | |
foreach ($tag in $file.install_tags) { | |
if ($tag -match $pattern) { | |
$file.tagged = $true | |
$tags += $tag | |
} | |
} | |
} | |
} | |
$exceptFiles = ($appFiles | Where-Object -Property exclude -EQ $true) | |
$onlyFiles = ($appFiles | Where-Object -Property only -EQ $true) | |
$taggedFiles = ($appFiles | Where-Object -Property tagged -EQ $true) | |
# If the verbose flag is passed, then show estimated maximum download savings/sizes. | |
if ($VerbosePreference -eq 'continue') { | |
if ($exceptFiles.Count -gt 0) { | |
$totalSize = ($exceptFiles | Measure-Object -Property size -Sum).Sum | |
log "[$($appTitle)] Excluding $($exceptFiles.Count) files totalling $($totalSize | humanReadableSize) (max)." | |
} | |
if ($onlyFiles.Count -gt 0) { | |
$totalSize = ($onlyFiles | Measure-Object -Property size -Sum).Sum | |
# If we've also specified tags, then it's possible that some of the only files are excluded by the tag filter. | |
if ($taggedFiles.Count -gt 0) { | |
$totalSize = ($onlyFiles | Where-Object -Property tagged -EQ $true | Measure-Object -Property size -Sum).Sum | |
} | |
log "[$($appTitle)] Only downloading $($exceptFiles.Count) files totalling $($totalSize | humanReadableSize) (max)." | |
} | |
if ($taggedFiles.Count -gt 0 -and $onlyFiles.Count -eq 0) { | |
$totalSize = ($taggedFiles | Measure-Object -Property size -Sum).Sum | |
log "[$($appTitle)] Only downloading files with the following tags $($tags -join ', ') totalling $($totalSize | humanReadableSize) (max)." | |
} | |
} | |
return @{ | |
ExceptFiles = ($exceptFiles | ForEach-Object { $_.path }) | |
OnlyFiles = ($onlyFiles | ForEach-Object { $_.path }) | |
OnlyTags = ($tags | Sort-Object -Unique) | |
DLCs = $settings.DLCs | |
} | |
} | |
# Update-LegendaryApps updates all apps that have updates available from legendary. | |
function Update-LegendaryApps { | |
<# | |
.SYNOPSIS | |
Updates all installed legendary apps. | |
.DESCRIPTION | |
Finds apps installed by legendary that need updating and updates them. | |
Apps can have additional settings applied to them by creating an updates.json file in the | |
in the $env:APPDATA\Legendary\$appName directory, where $appName is the internal name of the app. | |
This file should contain a JSON object with the following properties: | |
- Only: An array of regex patterns to match files to be downloaded. Only files matching one or more patterns will be downloaded. | |
- Except: An array of regex patterns to match files to be downloaded. If a pattern matches, then that file will be excluded from the update. | |
- Tags: An array of regex patterns to match against the install tags of the app. If a pattern matches, then only files with that tag will be updated. | |
- DLCs: A value that determines how to handle DLCs when updating the app. Can be one of the following values (default is Installed): | |
- All: Update all DLCs and install any missing ones | |
- Installed: Only update installed DLCs | |
- None: Don't update any DLCs | |
.NOTES | |
To discover the internal name of an app, you can use the legendary list-installed command | |
or pass the -Verbose flag to this function which will show the file path it is using to | |
find the customisation file. | |
.PARAMETER AppName | |
Allows you to specify a regex to filter the apps by application name (the internal name). | |
.PARAMETER AppTitle | |
Allows you to specify a regex to filter the apps by application title (the friendly name). | |
.PARAMETER NoConfirm | |
Skips asking for confirmation and just does it. | |
.LINK | |
https://github.com/derrod/legendary | |
#> | |
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'low')] | |
param( | |
[Parameter][string]$AppName, | |
[Parameter][string]$AppTitle | |
) | |
$updates = Get-LegendaryUpdates | |
if (![string]::IsNullOrEmpty($AppName)) { | |
$updates = $updates | Where-Object -Property 'App name' -Match $AppName | |
} | |
if (![string]::IsNullOrEmpty($AppTitle)) { | |
$updates = $updates | Where-Object -Property 'App title' -Match $AppTitle | |
} | |
if ($updates.Count -eq 0) { | |
log 'No apps with updates available' | |
return | |
} | |
log "Found $($updates.Count) apps with updates available" | |
$updates | ForEach-Object { | |
$name = $_.'App title' | |
if ([string]::IsNullOrEmpty($name)) { | |
$name = $_.'App name' | |
} | |
log " $($name)" | |
} | |
foreach ($update in $updates) { | |
$splat = applyLegendaryCustomisations $update | |
updateLegendaryApp $update @splat | |
} | |
} | |
# Export our functions so they can be used in other scripts. | |
Export-ModuleMember -Function Update-LegendaryApps | |
Export-ModuleMember -Function Get-LegendaryUpdates |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment