From c9dce65a459ba11bf97989d74b78f2f7db208c61 Mon Sep 17 00:00:00 2001 From: Rod Christiansen Date: Tue, 21 Jul 2026 13:02:31 -0700 Subject: [PATCH] Serialize concurrent sessions; demote critical-package retry note to Info Two BootstrapMate sessions running at once (an MDM-triggered run racing an interactive one) share the cache directory and the Windows Installer mutex, so they corrupt each other: 1618s, cache files deleted mid-install, "file in use" failures. Add a Global\BootstrapMate.SingleInstance mutex - the second session waits (up to 30 minutes) for the first to finish, then runs normally, so concurrency becomes a queue instead of an error class. Also log "CRITICAL PACKAGE ... aggressive retry strategy" at Info: it describes normal behavior for critical packages, not a problem. --- Program.cs | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/Program.cs b/Program.cs index 463696d..55abede 100644 --- a/Program.cs +++ b/Program.cs @@ -284,8 +284,45 @@ static int Main(string[] args) Console.WriteLine("[+] Running with administrator privileges"); Console.WriteLine(); } - - return MainAsync(args).GetAwaiter().GetResult(); + + // Single-instance guard. Two concurrent sessions (e.g. an Intune-triggered + // run racing an interactive one) share the same cache directory and the + // Windows Installer mutex, so they corrupt each other: 1618s, cache files + // deleted mid-install, "file in use" failures. Serialize instead - the + // second session waits for the first to finish, then runs normally. + using var instanceMutex = new Mutex(initiallyOwned: false, @"Global\BootstrapMate.SingleInstance"); + bool ownsInstanceMutex = false; + try + { + try { ownsInstanceMutex = instanceMutex.WaitOne(TimeSpan.Zero); } + catch (AbandonedMutexException) { ownsInstanceMutex = true; } + + if (!ownsInstanceMutex) + { + Logger.Info("Another BootstrapMate instance is already running - waiting for it to finish"); + if (!silentMode) + { + Console.WriteLine("[i] Another BootstrapMate instance is already running - waiting for it to finish..."); + } + + try { ownsInstanceMutex = instanceMutex.WaitOne(TimeSpan.FromMinutes(30)); } + catch (AbandonedMutexException) { ownsInstanceMutex = true; } + + if (!ownsInstanceMutex) + { + Logger.Error("Timed out after 30 minutes waiting for another BootstrapMate instance to finish"); + return 1; + } + + Logger.Info("Previous BootstrapMate instance finished - continuing"); + } + + return MainAsync(args).GetAwaiter().GetResult(); + } + finally + { + if (ownsInstanceMutex) instanceMutex.ReleaseMutex(); + } } static async Task MainAsync(string[] args) @@ -1290,7 +1327,7 @@ static async Task RunMsiInstaller(string msiPath, JsonElement packageInfo) if (isSbinInstaller) { - Logger.Warning($"CRITICAL PACKAGE: {packageName} - using aggressive retry strategy"); + Logger.Info($"CRITICAL PACKAGE: {packageName} - using aggressive retry strategy"); WriteLog($"CRITICAL PACKAGE: {packageName} - using aggressive retry strategy"); }