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
0 commit comments