From 2a77cf1a02379fd9787b9fc4d93dc8ec376c28d7 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:58:00 -0800 Subject: [PATCH] Add MSI path quoting to handle spaces in msiexec arguments Introduces a new function to detect and properly quote unquoted MSI file paths in msiexec command arguments. This prevents installation failures when MSI paths contain spaces, as the Windows installer requires quoted paths to correctly parse arguments with whitespace. The solution uses pattern matching to identify `/i` arguments followed by unquoted `.msi` file paths and automatically wraps them in double quotes. This runs automatically during application installation when msiexec is detected, ensuring reliable installations regardless of path formatting in the configuration. --- .../Apps/Orchestration/Install-Win32Apps.ps1 | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 b/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 index d3c4af1..6d4ab63 100644 --- a/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 +++ b/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 @@ -92,6 +92,49 @@ function Invoke-Process { } } +function Format-MsiArguments { + <# + .SYNOPSIS + Ensures MSI file paths in msiexec arguments are properly quoted. + .DESCRIPTION + Detects /i arguments followed by an unquoted path ending in .msi + and wraps the path in double quotes to handle paths with spaces. + #> + param( + [Parameter(Mandatory)] + [string]$CommandLine, + + [Parameter(Mandatory)] + [string]$Arguments + ) + + # Only process if the command is msiexec + if ($CommandLine -notmatch '^msiexec(\.exe)?$') { + return $Arguments + } + + # Regex pattern explanation: + # (?i) - Case-insensitive matching + # (/i)\s+ - Match /i followed by whitespace + # (?!") - Negative lookahead: not already quoted + # (.+?\.msi) - Capture path ending in .msi (lazy match to stop at first .msi) + # (?=\s+/|\s*$) - Followed by another switch or end of string + + # Pattern to match /i followed by an unquoted MSI path + $pattern = '(?i)(/i)\s+(?!")(.+?\.msi)(?=\s+/|\s*$)' + + if ($Arguments -match $pattern) { + $originalArgs = $Arguments + # Replace with quoted path + $Arguments = $Arguments -replace $pattern, '$1 "$2"' + Write-Host "Detected unquoted MSI path in msiexec arguments. Adjusted arguments:" + Write-Host "Original: $originalArgs" + Write-Host "Modified: $Arguments" + } + + return $Arguments +} + function Install-Applications { param( [Parameter(Mandatory)] @@ -177,6 +220,15 @@ function Install-Applications { $ignoreNonZeroExitCodes = $app.IgnoreNonZeroExitCodes } + # Auto-quote MSI paths if using msiexec and path contains spaces but no quotes + if ($null -ne $argumentsToPass -and $argumentsToPass.Count -gt 0) { + $joinedArgs = $argumentsToPass -join ' ' + $formattedArgs = Format-MsiArguments -CommandLine $app.CommandLine -Arguments $joinedArgs + if ($formattedArgs -ne $joinedArgs) { + $argumentsToPass = @($formattedArgs) + } + } + if ($null -eq $argumentsToPass -or $argumentsToPass.Count -eq 0) { Write-Host "Running command: $($app.CommandLine) (no arguments)" $result = Invoke-Process -FilePath $app.CommandLine -AdditionalSuccessCodes $additionalSuccessCodes -IgnoreNonZeroExitCodes $ignoreNonZeroExitCodes