Skip to content

Commit 1752050

Browse files
authored
fix(core): Fix "Invoke-ExternalCommand" quoting rules (#5945)
1 parent edaae8d commit 1752050

File tree

6 files changed

+68
-26
lines changed

6 files changed

+68
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- **autoupdate:** Copy `PSCustomObject`-type properties within `autoupdate` to prevent reference changes ([#5934](https://github.com/ScoopInstaller/Scoop/issues/5934))
1313
- **system:** Fix argument passing to `Split-PathLikeEnvVar()` in deprecated `strip_path()` ([#5937](https://github.com/ScoopInstaller/Scoop/issues/5937))
1414
- **scoop-cache:** Fix regression in 36026f18 ([#5944](https://github.com/ScoopInstaller/Scoop/issues/5944))
15+
- **core:** Fix "Invoke-ExternalCommand" quoting rules ([#5945](https://github.com/ScoopInstaller/Scoop/issues/5945))
1516

1617
## [v0.4.1](https://github.com/ScoopInstaller/Scoop/compare/v0.4.0...v0.4.1) - 2024-04-25
1718

lib/core.ps1

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -771,31 +771,35 @@ function Invoke-ExternalCommand {
771771
$Process.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
772772
}
773773
if ($ArgumentList.Length -gt 0) {
774-
$ArgumentList = $ArgumentList | ForEach-Object { [regex]::Split($_.Replace('"', ''), '(?<=(?<![:\w])[/-]\w+) | (?=[/-])') }
775-
# Use legacy argument escaping for commands having non-standard behavior
776-
# with regard to argument passing. `msiexec` requires some args like
777-
# `TARGETDIR="C:\Program Files"`, which is non-standard, therefore we
778-
# treat it as a legacy command.
774+
# Remove existing double quotes and split arguments
775+
# '(?<=(?<![:\w])[/-]\w+) ' matches a space after a command line switch starting with a slash ('/') or a hyphen ('-')
776+
# The inner item '(?<![:\w])[/-]' matches a slash ('/') or a hyphen ('-') not preceded by a colon (':') or a word character ('\w')
777+
# so that it must be a command line switch, otherwise, it would be a path (e.g. 'C:/Program Files') or other word (e.g. 'some-arg')
778+
# ' (?=[/-])' matches a space followed by a slash ('/') or a hyphen ('-'), i.e. the space before a command line switch
779+
$ArgumentList = $ArgumentList.ForEach({ $_ -replace '"' -split '(?<=(?<![:\w])[/-]\w+) | (?=[/-])' })
780+
# Use legacy argument escaping for commands having non-standard behavior with regard to argument passing.
781+
# `msiexec` requires some args like `TARGETDIR="C:\Program Files"`, which is non-standard, therefore we treat it as a legacy command.
782+
# NSIS installer's '/D' param may not work with the ArgumentList property, so we need to escape arguments manually.
779783
# ref-1: https://learn.microsoft.com/en-us/powershell/scripting/learn/experimental-features?view=powershell-7.4#psnativecommandargumentpassing
780-
$LegacyCommand = $FilePath -match '^((cmd|cscript|find|sqlcmd|wscript|msiexec)(\.exe)?|.*\.(bat|cmd|js|vbs|wsf))$'
784+
# ref-2: https://nsis.sourceforge.io/Docs/Chapter3.html
785+
$LegacyCommand = $FilePath -match '^((cmd|cscript|find|sqlcmd|wscript|msiexec)(\.exe)?|.*\.(bat|cmd|js|vbs|wsf))$' -or
786+
($ArgumentList -match '^/S$|^/D=[A-Z]:[\\/].*$').Length -eq 2
781787
$SupportArgumentList = $Process.StartInfo.PSObject.Properties.Name -contains 'ArgumentList'
782788
if ((-not $LegacyCommand) -and $SupportArgumentList) {
783789
# ArgumentList is supported in PowerShell 6.1 and later (built on .NET Core 2.1+)
784790
# ref-1: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.argumentlist?view=net-6.0
785791
# ref-2: https://docs.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell?view=powershell-7.2#net-framework-vs-net-core
786-
$ArgumentList | ForEach-Object { $Process.StartInfo.ArgumentList.Add($_) }
792+
$ArgumentList.ForEach({ $Process.StartInfo.ArgumentList.Add($_) })
787793
} else {
788-
# escape arguments manually in lower versions, refer to https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
789-
$escapedArgs = $ArgumentList | ForEach-Object {
790-
# escape N consecutive backslash(es), which are followed by a double quote or at the end of the string, to 2N consecutive ones
791-
$s = $_ -replace '(\\+)(""|$)', '$1$1$2'
792-
# quote the path if it contains spaces and is not NSIS's '/D' argument
793-
# ref: https://nsis.sourceforge.io/Docs/Chapter3.html
794-
if ($s -match ' ' -and $s -notmatch '/D=[A-Z]:[\\/].*') {
795-
$s -replace '([A-Z]:[\\/].*)', '"$1"'
796-
} else {
797-
$s
798-
}
794+
# Escape arguments manually in lower versions
795+
$escapedArgs = switch -regex ($ArgumentList) {
796+
# Quote paths starting with a drive letter
797+
'(?<!/D=)[A-Z]:[\\/].*' { $_ -replace '([A-Z]:[\\/].*)', '"$1"'; continue }
798+
# Do not quote paths if it is NSIS's '/D' argument
799+
'/D=[A-Z]:[\\/].*' { $_; continue }
800+
# Quote args with spaces
801+
' ' { "`"$_`""; continue }
802+
default { $_; continue }
799803
}
800804
$Process.StartInfo.Arguments = $escapedArgs -join ' '
801805
}

lib/decompress.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ function Expand-MsiArchive {
156156
$ArgList = @('x', $Path, "$DestinationPath\")
157157
} else {
158158
$MsiPath = 'msiexec.exe'
159-
$ArgList = @('/a', "`"$Path`"", '/qn', "TARGETDIR=`"$DestinationPath\SourceDir`"")
159+
$ArgList = @('/a', $Path, '/qn', "TARGETDIR=$DestinationPath\SourceDir")
160160
}
161161
$LogPath = "$(Split-Path $Path)\msi.log"
162162
if ($Switches) {

test/Scoop-Decompress.Tests.ps1

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
1414

1515
function test_extract($extract_fn, $from, $removal) {
1616
$to = (strip_ext $from) -replace '\.tar$', ''
17-
& $extract_fn ($from -replace '/', '\') ($to -replace '/', '\') -Removal:$removal
17+
& $extract_fn ($from -replace '/', '\') ($to -replace '/', '\') -Removal:$removal -ExtractDir $args[0]
1818
return $to
1919
}
2020

@@ -25,7 +25,7 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
2525
}
2626
It 'Test cases should exist and hash should match' {
2727
$testcases | Should -Exist
28-
(Get-FileHash -Path $testcases -Algorithm SHA256).Hash.ToLower() | Should -Be '791bfce192917a2ff225dcdd87d23ae5f720b20178d85e68e4b1b56139cf8e6a'
28+
(Get-FileHash -Path $testcases -Algorithm SHA256).Hash.ToLower() | Should -Be '23a23a63e89ff95f5ef27f0cacf08055c2779cf41932266d8f509c2e200b8b63'
2929
}
3030
It 'Test cases should be extracted correctly' {
3131
{ Microsoft.PowerShell.Archive\Expand-Archive -Path $testcases -DestinationPath $working_dir } | Should -Not -Throw
@@ -50,12 +50,31 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
5050
$test6_1 = "$working_dir\7ZipTest6.part01.rar"
5151
$test6_2 = "$working_dir\7ZipTest6.part02.rar"
5252
$test6_3 = "$working_dir\7ZipTest6.part03.rar"
53+
$test7 = "$working_dir\NSISTest.exe"
54+
}
55+
56+
AfterEach {
57+
Remove-Item -Path $to -Recurse -Force
5358
}
5459

5560
It 'extract normal compressed file' {
5661
$to = test_extract 'Expand-7zipArchive' $test1
5762
$to | Should -Exist
5863
"$to\empty" | Should -Exist
64+
(Get-ChildItem $to).Count | Should -Be 3
65+
}
66+
67+
It 'extract "extract_dir" correctly' {
68+
$to = test_extract 'Expand-7zipArchive' $test1 $false 'tmp'
69+
$to | Should -Exist
70+
"$to\empty" | Should -Exist
71+
(Get-ChildItem $to).Count | Should -Be 1
72+
}
73+
74+
It 'extract "extract_dir" with spaces correctly' {
75+
$to = test_extract 'Expand-7zipArchive' $test1 $false 'tmp 2'
76+
$to | Should -Exist
77+
"$to\empty" | Should -Exist
5978
(Get-ChildItem $to).Count | Should -Be 1
6079
}
6180

@@ -94,21 +113,39 @@ Describe 'Decompression function' -Tag 'Scoop', 'Windows', 'Decompress' {
94113
(Get-ChildItem $to).Count | Should -Be 1
95114
}
96115

116+
It 'extract NSIS installer' {
117+
$to = test_extract 'Expand-7zipArchive' $test7
118+
$to | Should -Exist
119+
"$to\empty" | Should -Exist
120+
(Get-ChildItem $to).Count | Should -Be 1
121+
}
122+
123+
It 'self-extract NSIS installer' {
124+
$to = "$working_dir\NSIS Test"
125+
$null = Invoke-ExternalCommand -FilePath $test7 -ArgumentList @('/S', '/NCRC', "/D=$to")
126+
$to | Should -Exist
127+
"$to\empty" | Should -Exist
128+
(Get-ChildItem $to).Count | Should -Be 1
129+
}
130+
97131
It 'works with "-Removal" switch ($removal param)' {
98132
$test1 | Should -Exist
99-
test_extract 'Expand-7zipArchive' $test1 $true
133+
$to = test_extract 'Expand-7zipArchive' $test1 $true
134+
$to | Should -Exist
100135
$test1 | Should -Not -Exist
101136
$test5_1 | Should -Exist
102137
$test5_2 | Should -Exist
103138
$test5_3 | Should -Exist
104-
test_extract 'Expand-7zipArchive' $test5_1 $true
139+
$to = test_extract 'Expand-7zipArchive' $test5_1 $true
140+
$to | Should -Exist
105141
$test5_1 | Should -Not -Exist
106142
$test5_2 | Should -Not -Exist
107143
$test5_3 | Should -Not -Exist
108144
$test6_1 | Should -Exist
109145
$test6_2 | Should -Exist
110146
$test6_3 | Should -Exist
111-
test_extract 'Expand-7zipArchive' $test6_1 $true
147+
$to = test_extract 'Expand-7zipArchive' $test6_1 $true
148+
$to | Should -Exist
112149
$test6_1 | Should -Not -Exist
113150
$test6_2 | Should -Not -Exist
114151
$test6_3 | Should -Not -Exist

test/Scoop-TestLib.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# copies fixtures to a working directory
22
function setup_working($name) {
3-
$fixtures = "$PSScriptRoot/fixtures/$name"
3+
$fixtures = "$PSScriptRoot\fixtures\$name"
44
if (!(Test-Path $fixtures)) {
55
Write-Host "couldn't find fixtures for $name at $fixtures" -f red
66
exit 1
77
}
88

99
# reset working dir
10-
$working_dir = "$([IO.Path]::GetTempPath())ScoopTestFixtures/$name"
10+
$working_dir = "$([IO.Path]::GetTempPath())ScoopTestFixtures\$name"
1111

1212
if (Test-Path $working_dir) {
1313
Remove-Item -Recurse -Force $working_dir
32 KB
Binary file not shown.

0 commit comments

Comments
 (0)