Skip to content

Commit eadb558

Browse files
fixes
1 parent 858a999 commit eadb558

3 files changed

Lines changed: 253 additions & 10 deletions

File tree

build/Wait-MsiInstall.ps1

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
function Wait-MsiInstall {
2+
<#
3+
.SYNOPSIS
4+
Waits for all MSI installations to complete before proceeding.
5+
6+
.DESCRIPTION
7+
This function monitors the Windows Installer service and waits for all MSI installations
8+
to complete. It prevents overlapping installations which can cause failures.
9+
10+
.PARAMETER TimeoutMinutes
11+
Maximum time to wait in minutes. Default is 30 minutes.
12+
13+
.PARAMETER CheckIntervalSeconds
14+
How often to check for running installations in seconds. Default is 5 seconds.
15+
16+
.EXAMPLE
17+
Wait-MsiInstall
18+
19+
Waits for all MSI installations to complete with default timeout.
20+
21+
.EXAMPLE
22+
Wait-MsiInstall -TimeoutMinutes 60 -CheckIntervalSeconds 10
23+
24+
Waits up to 60 minutes, checking every 10 seconds.
25+
#>
26+
[CmdletBinding()]
27+
param(
28+
[int]$TimeoutMinutes = 30,
29+
[int]$CheckIntervalSeconds = 5
30+
)
31+
32+
$timeoutTime = (Get-Date).AddMinutes($TimeoutMinutes)
33+
$msiexecRunning = $true
34+
35+
Write-Host "Checking for running MSI installations..." -ForegroundColor Yellow
36+
37+
while ($msiexecRunning -and (Get-Date) -lt $timeoutTime) {
38+
# Check for msiexec processes
39+
$msiProcesses = Get-Process -Name "msiexec" -ErrorAction SilentlyContinue
40+
41+
# Check Windows Installer service status
42+
$installerService = Get-Service -Name "msiserver" -ErrorAction SilentlyContinue
43+
44+
# Check for installation mutex (Windows Installer global mutex)
45+
$mutex = $null
46+
try {
47+
$mutex = [System.Threading.Mutex]::OpenExisting("Global\_MSIExecute")
48+
$mutexExists = $true
49+
}
50+
catch {
51+
$mutexExists = $false
52+
}
53+
finally {
54+
if ($mutex) {
55+
$mutex.Dispose()
56+
}
57+
}
58+
59+
if ($msiProcesses -or ($installerService -and $installerService.Status -eq "Running") -or $mutexExists) {
60+
Write-Host "MSI installation in progress... waiting $CheckIntervalSeconds seconds" -ForegroundColor Yellow
61+
Start-Sleep -Seconds $CheckIntervalSeconds
62+
}
63+
else {
64+
$msiexecRunning = $false
65+
Write-Host "No MSI installations detected. Safe to proceed." -ForegroundColor Green
66+
}
67+
}
68+
69+
if ((Get-Date) -ge $timeoutTime) {
70+
Write-Warning "Timeout reached while waiting for MSI installations to complete."
71+
return $false
72+
}
73+
74+
# Additional safety wait to ensure installer has fully released resources
75+
Start-Sleep -Seconds 2
76+
return $true
77+
}
78+
79+
function Wait-SpecificMsiProcess {
80+
<#
81+
.SYNOPSIS
82+
Waits for a specific MSI installation process to complete by monitoring its process ID.
83+
84+
.DESCRIPTION
85+
This function waits for a specific msiexec process to complete based on its process ID.
86+
Useful when you start an MSI installation and want to wait for that specific installation.
87+
88+
.PARAMETER ProcessId
89+
The process ID of the msiexec process to monitor.
90+
91+
.PARAMETER TimeoutMinutes
92+
Maximum time to wait in minutes. Default is 30 minutes.
93+
94+
.EXAMPLE
95+
$process = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i", "package.msi", "/quiet" -PassThru
96+
Wait-SpecificMsiProcess -ProcessId $process.Id
97+
#>
98+
[CmdletBinding()]
99+
param(
100+
[Parameter(Mandatory)]
101+
[int]$ProcessId,
102+
[int]$TimeoutMinutes = 30
103+
)
104+
105+
$timeoutTime = (Get-Date).AddMinutes($TimeoutMinutes)
106+
107+
Write-Host "Waiting for MSI process $ProcessId to complete..." -ForegroundColor Yellow
108+
109+
try {
110+
# Wait for the process using a different approach that preserves exit code
111+
$process = $null
112+
$attempts = 0
113+
while (-not $process -and $attempts -lt 5) {
114+
$process = Get-Process -Id $ProcessId -ErrorAction SilentlyContinue
115+
if (-not $process) {
116+
Start-Sleep -Milliseconds 500
117+
$attempts++
118+
}
119+
}
120+
121+
if (-not $process) {
122+
Write-Host "MSI process $ProcessId not found or already completed." -ForegroundColor Green
123+
return 0
124+
}
125+
126+
# Wait for process to exit
127+
$process.WaitForExit(($TimeoutMinutes * 60 * 1000))
128+
129+
if ($process.HasExited) {
130+
# Get exit code using WMI for reliability
131+
$exitCode = (Get-WmiObject Win32_Process -Filter "ProcessId = $ProcessId" -ErrorAction SilentlyContinue).ExitCode
132+
if ($null -eq $exitCode) {
133+
# Fallback to process ExitCode if available
134+
$exitCode = $process.ExitCode
135+
}
136+
Write-Host "MSI process $ProcessId completed with exit code: $exitCode" -ForegroundColor Green
137+
return $exitCode
138+
}
139+
else {
140+
Write-Warning "Timeout reached waiting for MSI process $ProcessId"
141+
return -1
142+
}
143+
}
144+
catch {
145+
Write-Host "MSI process $ProcessId completed or error occurred: $_" -ForegroundColor Yellow
146+
return 0
147+
}
148+
}
149+
150+
function Install-MsiPackageWithWait {
151+
<#
152+
.SYNOPSIS
153+
Installs an MSI package and waits for completion.
154+
155+
.DESCRIPTION
156+
This function installs an MSI package with proper waiting and error handling.
157+
It ensures no other MSI installations are running before starting.
158+
159+
.PARAMETER MsiPath
160+
Path to the MSI file to install.
161+
162+
.PARAMETER Arguments
163+
Additional arguments to pass to msiexec. Default is "/quiet /norestart".
164+
165+
.PARAMETER TimeoutMinutes
166+
Maximum time to wait for installation. Default is 30 minutes.
167+
168+
.EXAMPLE
169+
Install-MsiPackageWithWait -MsiPath "C:\temp\package.msi"
170+
171+
Installs the MSI package quietly and waits for completion.
172+
173+
.EXAMPLE
174+
Install-MsiPackageWithWait -MsiPath "C:\temp\package.msi" -Arguments "/quiet /log C:\temp\install.log"
175+
176+
Installs with custom arguments including logging.
177+
#>
178+
[CmdletBinding()]
179+
param(
180+
[Parameter(Mandatory)]
181+
[string]$MsiPath,
182+
[string]$Arguments = "/quiet /norestart",
183+
[int]$TimeoutMinutes = 30
184+
)
185+
186+
if (-not (Test-Path $MsiPath)) {
187+
throw "MSI file not found: $MsiPath"
188+
}
189+
190+
# Wait for any existing MSI installations to complete
191+
Write-Host "Ensuring no other MSI installations are running..." -ForegroundColor Yellow
192+
if (-not (Wait-MsiInstall -TimeoutMinutes 10)) {
193+
throw "Timeout waiting for existing MSI installations to complete"
194+
}
195+
196+
# Start the installation
197+
Write-Host "Starting MSI installation: $MsiPath" -ForegroundColor Green
198+
$fullArguments = "/i `"$MsiPath`" $Arguments"
199+
200+
try {
201+
$process = Start-Process -FilePath "msiexec.exe" -ArgumentList $fullArguments -PassThru -Wait
202+
203+
Write-Host "MSI installation completed with exit code: $($process.ExitCode)" -ForegroundColor Green
204+
205+
# Common MSI exit codes
206+
switch ($process.ExitCode) {
207+
0 { Write-Host "Installation successful" -ForegroundColor Green }
208+
1641 { Write-Host "Installation successful, restart required" -ForegroundColor Yellow }
209+
3010 { Write-Host "Installation successful, restart required" -ForegroundColor Yellow }
210+
1618 { Write-Warning "Another installation is already in progress" }
211+
1619 { Write-Warning "Installation package could not be opened" }
212+
1620 { Write-Warning "Installation package could not be opened" }
213+
1633 { Write-Warning "This installation package is not supported on this platform" }
214+
default { Write-Warning "Installation completed with exit code: $($process.ExitCode)" }
215+
}
216+
217+
return $process.ExitCode
218+
}
219+
catch {
220+
Write-Error "Failed to start MSI installation: $($_.Exception.Message)"
221+
throw
222+
}
223+
}
224+
225+
# Functions are automatically available when dot-sourced

build/build.ps1

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
$PSDefaultParameterValues["*:Force"] = $true
22
$PSDefaultParameterValues["*:Confirm"] = $false
33

4+
# Import MSI waiting functions
5+
. "$PSScriptRoot\Wait-MsiInstall.ps1"
6+
47
# Get script root and project root
58
$scriptroot = $PSScriptRoot
69
if (-not $scriptroot) {
@@ -98,10 +101,30 @@ $ProgressPreference = "Continue"
98101
7z x .\temp\sqlpackage-linux.zip "-o.\temp\linux" -y
99102
7z x .\temp\sqlpackage-macos.zip "-o.\temp\mac" -y
100103

101-
msiexec /a $(Resolve-Path .\temp\DacFramework.msi) /qb TARGETDIR=$(Resolve-Path .\temp\dacfull)
102-
Start-Sleep 3
103-
msiexec /a $(Resolve-Path .\temp\XESmartTarget_x64.msi) /qb TARGETDIR=$(Resolve-Path .\temp\xe)
104-
Start-Sleep 3
104+
# Install DacFramework MSI with proper waiting
105+
Write-Host "Installing DacFramework MSI..."
106+
$dacMsiPath = Resolve-Path .\temp\DacFramework.msi
107+
$dacTargetDir = Resolve-Path .\temp\dacfull
108+
$dacProcess = Start-Process -FilePath "msiexec.exe" -ArgumentList "/a", "`"$dacMsiPath`"", "/qb", "TARGETDIR=`"$dacTargetDir`"" -PassThru -Wait
109+
$dacExitCode = $dacProcess.ExitCode
110+
Write-Host "DacFramework MSI installation completed with exit code: $dacExitCode"
111+
if ($dacExitCode -ne 0) {
112+
Write-Warning "DacFramework MSI installation may have failed with exit code: $dacExitCode"
113+
}
114+
115+
# Small delay to ensure resources are released
116+
Start-Sleep -Seconds 2
117+
118+
# Install XESmartTarget MSI with proper waiting
119+
Write-Host "Installing XESmartTarget MSI..."
120+
$xeTargetDir = Resolve-Path .\temp\xe
121+
$xeMsiPath = Resolve-Path .\temp\XESmartTarget_x64.msi
122+
$xeProcess = Start-Process -FilePath "msiexec.exe" -ArgumentList "/a", "`"$xeMsiPath`"", "/qb", "TARGETDIR=`"$xeTargetDir`"" -PassThru -Wait
123+
$xeExitCode = $xeProcess.ExitCode
124+
Write-Host "XESmartTarget MSI installation completed with exit code: $xeExitCode"
125+
if ($xeExitCode -ne 0) {
126+
Write-Warning "XESmartTarget MSI installation may have failed with exit code: $xeExitCode"
127+
}
105128

106129
# Copy XESmartTarget preserving structure
107130
robocopy ./temp/xe/XESmartTarget ./lib/third-party/XESmartTarget /E /NFL /NDL /NJH /NJS /nc /ns /np
@@ -145,11 +168,6 @@ Copy-Item "./temp/linux/Microsoft.Data.Tools*" -Destination "./lib/linux-dac/" -
145168
Copy-Item "./temp/mac/Microsoft.SqlServer.Dac*" -Destination "./lib/mac-dac/" -Force
146169
Copy-Item "./temp/mac/Microsoft.Data.Tools*" -Destination "./lib/mac-dac/" -Force
147170

148-
# Copy SqlPackage executables
149-
Copy-Item "./temp/linux/SqlPackage" -Destination "./lib/linux-dac/SqlPackage" -Force
150-
Copy-Item "./temp/mac/SqlPackage" -Destination "./lib/mac-dac/SqlPackage" -Force
151-
Copy-Item "$dacPath\SqlPackage.exe" -Destination "./lib/win-dac/SqlPackage.exe" -Force
152-
153171
# Core files are already in place from dotnet publish
154172

155173
# Copy var/misc files to appropriate locations

dbatools.library.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#
88
@{
99
# Version number of this module.
10-
ModuleVersion = '2025.7.11'
10+
ModuleVersion = '2025.7.12'
1111

1212
# ID used to uniquely identify this module
1313
GUID = '00b61a37-6c36-40d8-8865-ac0180288c84'

0 commit comments

Comments
 (0)