@@ -746,31 +746,35 @@ function Invoke-ExternalCommand {
746746 $Process.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle ]::Hidden
747747 }
748748 if ($ArgumentList.Length -gt 0 ) {
749- $ArgumentList = $ArgumentList | ForEach-Object { [regex ]::Split($_.Replace (' "' , ' ' ), ' (?<=(?<![:\w])[/-]\w+) | (?=[/-])' ) }
750- # Use legacy argument escaping for commands having non-standard behavior
751- # with regard to argument passing. `msiexec` requires some args like
752- # `TARGETDIR="C:\Program Files"`, which is non-standard, therefore we
753- # treat it as a legacy command.
749+ # Remove existing double quotes and split arguments
750+ # '(?<=(?<![:\w])[/-]\w+) ' matches a space after a command line switch starting with a slash ('/') or a hyphen ('-')
751+ # The inner item '(?<![:\w])[/-]' matches a slash ('/') or a hyphen ('-') not preceded by a colon (':') or a word character ('\w')
752+ # 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')
753+ # ' (?=[/-])' matches a space followed by a slash ('/') or a hyphen ('-'), i.e. the space before a command line switch
754+ $ArgumentList = $ArgumentList.ForEach ({ $_ -replace ' "' -split ' (?<=(?<![:\w])[/-]\w+) | (?=[/-])' })
755+ # Use legacy argument escaping for commands having non-standard behavior with regard to argument passing.
756+ # `msiexec` requires some args like `TARGETDIR="C:\Program Files"`, which is non-standard, therefore we treat it as a legacy command.
757+ # NSIS installer's '/D' param may not work with the ArgumentList property, so we need to escape arguments manually.
754758 # ref-1: https://learn.microsoft.com/en-us/powershell/scripting/learn/experimental-features?view=powershell-7.4#psnativecommandargumentpassing
755- $LegacyCommand = $FilePath -match ' ^((cmd|cscript|find|sqlcmd|wscript|msiexec)(\.exe)?|.*\.(bat|cmd|js|vbs|wsf))$'
759+ # ref-2: https://nsis.sourceforge.io/Docs/Chapter3.html
760+ $LegacyCommand = $FilePath -match ' ^((cmd|cscript|find|sqlcmd|wscript|msiexec)(\.exe)?|.*\.(bat|cmd|js|vbs|wsf))$' -or
761+ ($ArgumentList -match ' ^/S$|^/D=[A-Z]:[\\/].*$' ).Length -eq 2
756762 $SupportArgumentList = $Process.StartInfo.PSObject.Properties.Name -contains ' ArgumentList'
757763 if ((-not $LegacyCommand ) -and $SupportArgumentList ) {
758764 # ArgumentList is supported in PowerShell 6.1 and later (built on .NET Core 2.1+)
759765 # ref-1: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.argumentlist?view=net-6.0
760766 # 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
761- $ArgumentList | ForEach-Object { $Process.StartInfo.ArgumentList.Add ($_ ) }
767+ $ArgumentList. ForEach ( { $Process.StartInfo.ArgumentList.Add ($_ ) })
762768 } else {
763- # escape arguments manually in lower versions, refer to https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
764- $escapedArgs = $ArgumentList | ForEach-Object {
765- # escape N consecutive backslash(es), which are followed by a double quote or at the end of the string, to 2N consecutive ones
766- $s = $_ -replace ' (\\+)(""|$)' , ' $1$1$2'
767- # quote the path if it contains spaces and is not NSIS's '/D' argument
768- # ref: https://nsis.sourceforge.io/Docs/Chapter3.html
769- if ($s -match ' ' -and $s -notmatch ' /D=[A-Z]:[\\/].*' ) {
770- $s -replace ' ([A-Z]:[\\/].*)' , ' "$1"'
771- } else {
772- $s
773- }
769+ # Escape arguments manually in lower versions
770+ $escapedArgs = switch - regex ($ArgumentList ) {
771+ # Quote paths starting with a drive letter
772+ ' (?<!/D=)[A-Z]:[\\/].*' { $_ -replace ' ([A-Z]:[\\/].*)' , ' "$1"' ; continue }
773+ # Do not quote paths if it is NSIS's '/D' argument
774+ ' /D=[A-Z]:[\\/].*' { $_ ; continue }
775+ # Quote args with spaces
776+ ' ' { " `" $_ `" " ; continue }
777+ default { $_ ; continue }
774778 }
775779 $Process.StartInfo.Arguments = $escapedArgs -join ' '
776780 }
@@ -1221,8 +1225,8 @@ function Test-ScoopCoreOnHold() {
12211225}
12221226
12231227function substitute ($entity , [Hashtable ] $params , [Bool ]$regexEscape = $false ) {
1224- $newentity = $entity
1225- if ( $null -ne $newentity ) {
1228+ if ( $null -ne $entity ) {
1229+ $newentity = $entity .PSObject.Copy ()
12261230 switch ($entity.GetType ().Name) {
12271231 ' String' {
12281232 $params.GetEnumerator () | ForEach-Object {
@@ -1234,7 +1238,7 @@ function substitute($entity, [Hashtable] $params, [Bool]$regexEscape = $false) {
12341238 }
12351239 }
12361240 ' Object[]' {
1237- $newentity = $entity | ForEach-Object { , (substitute $_ $params $regexEscape ) }
1241+ $newentity = $entity | ForEach-Object { , (substitute $_ $params $regexEscape ) }
12381242 }
12391243 ' PSCustomObject' {
12401244 $newentity.PSObject.Properties | ForEach-Object { $_.Value = substitute $_.Value $params $regexEscape }
0 commit comments