Skip to content

Commit 30ccfe6

Browse files
authored
Merge pull request #1077 from KnifMelti/feature/arguments
Add per-app winget arguments support via {AppID}-arguments.txt
2 parents 5b70a38 + cd211af commit 30ccfe6

File tree

9 files changed

+421
-20
lines changed

9 files changed

+421
-20
lines changed

README.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,17 +242,63 @@ Share your mods with the community:<br>
242242
<https://github.com/Romanitho/Winget-AutoUpdate/discussions/categories/mods>
243243

244244
### Winget native parameters
245-
Another finess is the **AppID** followed by the `-override` suffix as a **text file** (.**txt**) that you can place under the **mods** folder.
245+
You can customize winget behavior per-app using **text files** (.**txt**) placed in the **mods** folder:
246+
247+
#### Override (Full installer control)
248+
Use **AppID**`-override.txt` to replace ALL installer arguments (without `-h` silent mode).
246249
> Example:<br>
247250
**Adobe.Acrobat.Reader.64-bit-override.txt** with the content `"-sfx_nu /sAll /rs /msi EULA_ACCEPT=YES DISABLEDESKTOPSHORTCUT=1"`
248251

249-
This will use the **content** of the text file as a native **winget --override** parameter when upgrading.
252+
This uses the **content** as a native **winget --override** parameter when upgrading.
250253

251-
Likewise you can use the **AppID** followed by the `-custom` suffix as a **text file** (.**txt**) that you can place under the **mods** folder (*Arguments to be passed on to the installer in addition to the defaults*).
254+
#### Custom (Add installer arguments)
255+
Use **AppID**`-custom.txt` to add extra arguments to the installer (with `-h` silent mode).
252256
> Example:<br>
253257
**Adobe.Acrobat.Reader.64-bit-custom.txt** with the content `"DISABLEDESKTOPSHORTCUT=1"`
254258

255-
This will use the **content** of the text file as a native **winget --custom** parameter when upgrading.
259+
This uses the **content** as a native **winget --custom** parameter when upgrading.
260+
261+
#### Arguments (Winget-level parameters) ⭐ NEW
262+
Use **AppID**`-arguments.txt` to pass **winget parameters** (not installer arguments, with `-h` silent mode).
263+
264+
💡 **Locale Tip:** Many applications revert to English or system default language during WAU upgrades because winget doesn't remember the original installation locale. To prevent this:
265+
- **Best solution:** Use locale-specific package IDs in `included_apps.txt` (e.g., `Mozilla.Firefox.sv-SE` instead of `Mozilla.Firefox`)
266+
- **Alternative:** Create `{AppID}-arguments.txt` with `--locale` parameter to force language on every upgrade
267+
268+
> Example for language control ([#1073](https://github.com/Romanitho/Winget-AutoUpdate/issues/1073)):<br>
269+
**Mozilla.Firefox-arguments.txt** with the content `--locale pl`<br>
270+
*This prevents Firefox from reverting to English after WAU upgrades.*<br>
271+
*Better solution: Use `Mozilla.Firefox.pl` in included_apps.txt instead of `Mozilla.Firefox`.*
272+
273+
> Example for dependency issues ([#1075](https://github.com/Romanitho/Winget-AutoUpdate/issues/1075)):<br>
274+
**Cloudflare.Warp-arguments.txt** with the content `--skip-dependencies`
275+
276+
> Example with multiple parameters:<br>
277+
**Microsoft.VisualStudio.2022.Community-arguments.txt** with the content `--locale en-US --architecture x64`
278+
279+
**Common use cases:**
280+
- `--locale <locale>` - Force application language (e.g., `pl-PL`, `en-US`, `de-DE`)
281+
- 💡 **Recommended alternative:** Use locale-specific package IDs when available (e.g., `Mozilla.Firefox.sv-SE`, `Mozilla.Firefox.de`, `Mozilla.Firefox.ESR.pl`) to get latest versions
282+
- `--skip-dependencies` - Skip dependency installations when they conflict
283+
- `--architecture <arch>` - Force architecture (`x86`, `x64`, `arm64`)
284+
- `--version <version>` - Pin to specific version
285+
- `--ignore-security-hash` - Bypass hash verification
286+
- `--ignore-local-archive-malware-scan` - Skip AV scanning
287+
288+
⚠️ **Important:** When combining `--locale` and `--version`, the specific version must have an installer available for that locale. Not all versions support all locales. Check available versions with `winget show --id <AppID> --versions`.
289+
290+
💡 **Locale Best Practice:** Search for locale-specific packages with `winget search <AppName>` to see if your language has a dedicated package ID (e.g., `Mozilla.Firefox.sv-SE` for Swedish Firefox). These packages are maintained with the latest versions in your preferred language.
291+
292+
**Command-line usage:** You can also pass arguments when calling `Winget-Install.ps1`:
293+
```powershell
294+
.\winget-install.ps1 -AppIDs "Mozilla.Firefox --locale sv-SE"
295+
.\winget-install.ps1 -AppIDs "7zip.7zip, Notepad++.Notepad++"
296+
.\winget-install.ps1 -AppIDs "Adobe.Acrobat.Reader.64-bit --scope machine --override \"-sfx_nu /sAll /msi EULA_ACCEPT=YES\""
297+
```
298+
299+
**Priority:** Override > Custom > Arguments (file) > Arguments (command-line) > Default
300+
301+
See [_AppID-arguments-template.txt](Sources/Winget-AutoUpdate/mods/_AppID-arguments-template.txt) for more examples.
256302

257303

258304
## Known issues

Sources/Winget-AutoUpdate/Winget-Install.ps1

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ else {
7171
. "$realPath\functions\Write-ToLog.ps1"
7272
. "$realPath\functions\Confirm-Installation.ps1"
7373
. "$realPath\functions\Compare-SemVer.ps1"
74+
. "$realPath\functions\ConvertTo-WingetArgumentArray.ps1"
7475

7576
#Check if App exists in Winget Repository
7677
function Confirm-Exist ($AppID) {
@@ -100,6 +101,15 @@ function Test-ModsInstall ($AppID) {
100101
if (Test-Path "$Mods\$AppID-custom.txt") {
101102
$ModsCustom = (Get-Content "$Mods\$AppID-custom.txt" -Raw).Trim()
102103
}
104+
if (Test-Path "$Mods\$AppID-arguments.txt") {
105+
# Read file and filter out comments and empty lines
106+
$lines = Get-Content "$Mods\$AppID-arguments.txt" | Where-Object {
107+
$_.Trim() -ne "" -and -not $_.TrimStart().StartsWith("#")
108+
}
109+
if ($lines) {
110+
$ModsArguments = ($lines -join " ").Trim()
111+
}
112+
}
103113
if (Test-Path "$Mods\$AppID-install.ps1") {
104114
$ModsInstall = "$Mods\$AppID-install.ps1"
105115
}
@@ -108,7 +118,7 @@ function Test-ModsInstall ($AppID) {
108118
}
109119
}
110120

111-
return $ModsPreInstall, $ModsOverride, $ModsCustom, $ModsInstall, $ModsInstalled
121+
return $ModsPreInstall, $ModsOverride, $ModsCustom, $ModsArguments, $ModsInstall, $ModsInstalled
112122
}
113123

114124
#Check if uninstall modifications exist in "mods" directory
@@ -132,8 +142,8 @@ function Test-ModsUninstall ($AppID) {
132142
function Install-App ($AppID, $AppArgs) {
133143
$IsInstalled = Confirm-Installation $AppID
134144
if (!($IsInstalled) -or $AllowUpgrade ) {
135-
#Check if mods exist (or already exist) for preinstall/override/custom/install/installed
136-
$ModsPreInstall, $ModsOverride, $ModsCustom, $ModsInstall, $ModsInstalled = Test-ModsInstall $($AppID)
145+
#Check if mods exist (or already exist) for preinstall/override/custom/arguments/install/installed
146+
$ModsPreInstall, $ModsOverride, $ModsCustom, $ModsArguments, $ModsInstall, $ModsInstalled = Test-ModsInstall $($AppID)
137147

138148
#If PreInstall script exist
139149
if ($ModsPreInstall) {
@@ -155,8 +165,15 @@ function Install-App ($AppID, $AppArgs) {
155165
Write-ToLog "-> Arguments (customizing default): $ModsCustom" # With -h (user customizes default)
156166
$WingetArgs = "install --id $AppID -e --accept-package-agreements --accept-source-agreements -s winget -h --custom $ModsCustom" -split " "
157167
}
168+
elseif ($ModsArguments -or (-not [string]::IsNullOrWhiteSpace($AppArgs))) {
169+
# Prioritize ModsArguments from file over AppArgs from command line
170+
$finalArgs = if ($ModsArguments) { $ModsArguments } else { $AppArgs }
171+
Write-ToLog "-> Arguments (winget-level): $finalArgs" # Winget parameters with -h
172+
$argArray = ConvertTo-WingetArgumentArray $finalArgs
173+
$WingetArgs = @("install", "--id", $AppID, "-e", "--accept-package-agreements", "--accept-source-agreements", "-s", "winget") + $argArray + @("-h")
174+
}
158175
else {
159-
$WingetArgs = "install --id $AppID -e --accept-package-agreements --accept-source-agreements -s winget -h $AppArgs" -split " "
176+
$WingetArgs = "install --id $AppID -e --accept-package-agreements --accept-source-agreements -s winget -h" -split " "
160177
}
161178

162179
Write-ToLog "-> Running: `"$Winget`" $WingetArgs"
@@ -210,7 +227,14 @@ function Uninstall-App ($AppID, $AppArgs) {
210227

211228
#Uninstall App
212229
Write-ToLog "-> Uninstalling $AppID..." "DarkYellow"
213-
$WingetArgs = "uninstall --id $AppID -e --accept-source-agreements -h $AppArgs" -split " "
230+
if (-not [string]::IsNullOrWhiteSpace($AppArgs)) {
231+
Write-ToLog "-> Arguments (winget-level): $AppArgs"
232+
$argArray = ConvertTo-WingetArgumentArray $AppArgs
233+
$WingetArgs = @("uninstall", "--id", $AppID, "-e", "--accept-source-agreements") + $argArray + @("-h")
234+
}
235+
else {
236+
$WingetArgs = "uninstall --id $AppID -e --accept-source-agreements -h" -split " "
237+
}
214238
Write-ToLog "-> Running: `"$Winget`" $WingetArgs"
215239
& "$Winget" $WingetArgs | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append
216240

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<#
2+
.SYNOPSIS
3+
Parses a string of winget arguments respecting quotes and spaces.
4+
5+
.DESCRIPTION
6+
Splits a string of winget command-line arguments into an array,
7+
properly handling both single and double quotes, multiple spaces,
8+
and quoted values containing spaces.
9+
10+
.PARAMETER ArgumentString
11+
The raw argument string to parse (e.g., "--locale en-US --skip-dependencies")
12+
13+
.OUTPUTS
14+
Array of individual argument strings
15+
16+
.EXAMPLE
17+
ConvertTo-WingetArgumentArray "--skip-dependencies"
18+
Returns: @("--skip-dependencies")
19+
20+
.EXAMPLE
21+
ConvertTo-WingetArgumentArray '--locale "en-US" --architecture x64'
22+
Returns: @("--locale", "en-US", "--architecture", "x64")
23+
24+
.EXAMPLE
25+
ConvertTo-WingetArgumentArray "--override '-sfx_nu /sAll /msi EULA_ACCEPT=YES'"
26+
Returns: @("--override", "-sfx_nu /sAll /msi EULA_ACCEPT=YES")
27+
#>
28+
function ConvertTo-WingetArgumentArray {
29+
[CmdletBinding()]
30+
[OutputType([System.Object[]])]
31+
param(
32+
[Parameter(Mandatory = $false)]
33+
[string]$ArgumentString
34+
)
35+
36+
# Return empty array if input is null or whitespace
37+
if ([string]::IsNullOrWhiteSpace($ArgumentString)) {
38+
return @()
39+
}
40+
41+
$ArgumentString = $ArgumentString.Trim()
42+
43+
# Regex pattern that handles:
44+
# - Double quoted strings: "value with spaces"
45+
# - Single quoted strings: 'value with spaces'
46+
# - Unquoted arguments: --flag or value
47+
# Pattern explanation:
48+
# "([^"]*)" - Captures content between double quotes (group 1)
49+
# '([^']*)' - Captures content between single quotes (group 2)
50+
# (\S+) - Captures non-whitespace sequences (group 3)
51+
$pattern = '(?:"([^"]*)"|''([^'']*)''|(\S+))'
52+
53+
try {
54+
# Use [regex]::Matches() to find all argument tokens
55+
$regex = [regex]::new($pattern)
56+
$regexMatches = $regex.Matches($ArgumentString)
57+
58+
$result = @()
59+
foreach ($match in $regexMatches) {
60+
# Get the captured value from whichever group matched
61+
$value = if ($match.Groups[1].Success) {
62+
# Double-quoted value
63+
$match.Groups[1].Value
64+
}
65+
elseif ($match.Groups[2].Success) {
66+
# Single-quoted value
67+
$match.Groups[2].Value
68+
}
69+
else {
70+
# Unquoted value
71+
$match.Groups[3].Value
72+
}
73+
74+
# Only add non-empty values
75+
if (-not [string]::IsNullOrWhiteSpace($value)) {
76+
$result += $value
77+
}
78+
}
79+
80+
return $result
81+
}
82+
catch {
83+
Write-ToLog "Warning: Failed to parse arguments '$ArgumentString' - Error: $($_.Exception.Message)" "Yellow"
84+
Write-ToLog "Falling back to simple space split" "Yellow"
85+
86+
# Fallback to simple split if regex parsing fails
87+
$fallbackResult = $ArgumentString.Trim() -split '\s+'
88+
return $fallbackResult
89+
}
90+
}

Sources/Winget-AutoUpdate/functions/Test-Mods.ps1

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
55
.DESCRIPTION
66
Searches for app-specific scripts that customize the install/upgrade process.
7-
Hooks: preinstall, override, custom, upgrade, install, installed, notinstalled.
7+
Hooks: preinstall, override, custom, arguments, upgrade, install, installed, notinstalled.
88
99
.PARAMETER app
1010
The WinGet application ID to check.
1111
1212
.OUTPUTS
13-
Array: [PreInstall, Override, Custom, Upgrade, Install, Installed, NotInstalled]
13+
Array: [PreInstall, Override, Custom, Arguments, Upgrade, Install, Installed, NotInstalled]
1414
#>
1515
function Test-Mods ($app) {
1616

@@ -19,6 +19,7 @@ function Test-Mods ($app) {
1919
PreInstall = $null
2020
Override = $null
2121
Custom = $null
22+
Arguments = $null
2223
Upgrade = $null
2324
Install = $null
2425
Installed = $null
@@ -35,6 +36,15 @@ function Test-Mods ($app) {
3536
if (Test-Path "$Mods\$app-preinstall.ps1") { $result.PreInstall = "$Mods\$app-preinstall.ps1" }
3637
if (Test-Path "$Mods\$app-override.txt") { $result.Override = (Get-Content "$Mods\$app-override.txt" -Raw).Trim() }
3738
if (Test-Path "$Mods\$app-custom.txt") { $result.Custom = (Get-Content "$Mods\$app-custom.txt" -Raw).Trim() }
39+
if (Test-Path "$Mods\$app-arguments.txt") {
40+
# Read file and filter out comments and empty lines
41+
$lines = Get-Content "$Mods\$app-arguments.txt" | Where-Object {
42+
$_.Trim() -ne "" -and -not $_.TrimStart().StartsWith("#")
43+
}
44+
if ($lines) {
45+
$result.Arguments = ($lines -join " ").Trim()
46+
}
47+
}
3848
if (Test-Path "$Mods\$app-install.ps1") {
3949
$result.Install = "$Mods\$app-install.ps1"
4050
$result.Upgrade = "$Mods\$app-install.ps1"
@@ -44,5 +54,5 @@ function Test-Mods ($app) {
4454
if (Test-Path "$Mods\$app-notinstalled.ps1") { $result.NotInstalled = "$Mods\$app-notinstalled.ps1" }
4555
}
4656

47-
return $result.PreInstall, $result.Override, $result.Custom, $result.Upgrade, $result.Install, $result.Installed, $result.NotInstalled
57+
return $result.PreInstall, $result.Override, $result.Custom, $result.Arguments, $result.Upgrade, $result.Install, $result.Installed, $result.NotInstalled
4858
}

Sources/Winget-AutoUpdate/functions/Update-App.ps1

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
Function Update-App ($app) {
1313

1414
# Helper function to build winget command parameters
15-
function Get-WingetParams ($Command, $ModsOverride, $ModsCustom) {
15+
function Get-WingetParams ($Command, $ModsOverride, $ModsCustom, $ModsArguments) {
1616
$params = @($Command, "--id", $app.Id, "-e", "--accept-package-agreements", "--accept-source-agreements", "-s", "winget")
1717
if ($Command -eq "install") { $params += "--force" }
1818

@@ -22,6 +22,11 @@ Function Update-App ($app) {
2222
elseif ($ModsCustom) {
2323
return @{ Params = $params + @("-h", "--custom", $ModsCustom); Log = "$Command (custom): $ModsCustom" }
2424
}
25+
elseif ($ModsArguments) {
26+
# Parse arguments respecting quotes and spaces
27+
$argArray = ConvertTo-WingetArgumentArray $ModsArguments
28+
return @{ Params = $params + $argArray + @("-h"); Log = "$Command (arguments): $ModsArguments" }
29+
}
2530
return @{ Params = $params + "-h"; Log = $Command }
2631
}
2732

@@ -36,7 +41,7 @@ Function Update-App ($app) {
3641
-MessageType "info" -Balise $app.Name -Button1Action $ReleaseNoteURL -Button1Text $Button1Text
3742

3843
# Load mods
39-
$ModsPreInstall, $ModsOverride, $ModsCustom, $ModsUpgrade, $ModsInstall, $ModsInstalled, $ModsNotInstalled = Test-Mods $app.Id
44+
$ModsPreInstall, $ModsOverride, $ModsCustom, $ModsArguments, $ModsUpgrade, $ModsInstall, $ModsInstalled, $ModsNotInstalled = Test-Mods $app.Id
4045

4146
Write-ToLog "########## WINGET UPGRADE: $($app.Id) ##########" "Gray"
4247

@@ -50,7 +55,7 @@ Function Update-App ($app) {
5055
}
5156

5257
# Try upgrade first
53-
$cmd = Get-WingetParams "upgrade" $ModsOverride $ModsCustom
58+
$cmd = Get-WingetParams "upgrade" $ModsOverride $ModsCustom $ModsArguments
5459
Write-ToLog "-> $($cmd.Log)"
5560
& $Winget $cmd.Params | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append
5661

@@ -68,7 +73,7 @@ Function Update-App ($app) {
6873
for ($retry = 1; $retry -le $maxRetry -and -not $ConfirmInstall; $retry++) {
6974
Write-ToLog "-> Upgrade failed, trying install ($retry/$maxRetry)..." "DarkYellow"
7075

71-
$cmd = Get-WingetParams "install" $ModsOverride $ModsCustom
76+
$cmd = Get-WingetParams "install" $ModsOverride $ModsCustom $ModsArguments
7277
Write-ToLog "-> $($cmd.Log)"
7378
& $Winget $cmd.Params | Where-Object { $_ -notlike " *" } | Tee-Object -file $LogFile -Append
7479

0 commit comments

Comments
 (0)