Single-instance launcher + tolerant build pipeline
Single-instance --------------- Two PSLauncher.exe instances ran in parallel during the user's last release build, locking the repo-root copy and breaking the publish. Two-layer protection: 1. App.xaml.cs uses a Global\ named Mutex (UUID-based key) created at OnStartup. If the mutex is already held, the second instance PostMessages a registered Windows message (PSLAUNCHER_BRING_TO_FRONT) to HWND_BROADCAST and Shutdown()s immediately. 2. MainWindow hooks WndProc via SourceInitialized + HwndSource.AddHook; when it sees the broadcast, it restores from minimized, calls ShowWindow(SW_RESTORE) + Activate() + SetForegroundWindow so the already-running instance pops to the user's foreground. Net result: clicking the launcher icon a second time pops the existing window instead of starting a duplicate process. Tolerant build pipeline ----------------------- - Both csproj post-publish copy targets now have ContinueOnError="WarnAndContinue". A locked PSLauncher.exe at the repo root no longer fails the entire publish — the binary still exists in bin\Release\...\publish\ and the user gets a warning instead of an error. - All four .bat scripts (build-launcher / build-updater / build-all / build-installer) now run `taskkill /F /IM PSLauncher.exe /T` and the same for PSLauncher.Updater.exe before the publish step. This defensively closes any stray instance left behind by previous tests so the build pipeline is clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -24,6 +25,20 @@ namespace PSLauncher.App;
|
||||
public partial class App : Application
|
||||
{
|
||||
private IHost? _host;
|
||||
private static System.Threading.Mutex? _singleInstanceMutex;
|
||||
|
||||
private const string SingleInstanceMutexName = "Global\\PSLauncher-3F8E2C1A-9B47-4D5E-A0F8-2E9D1B6C7A3F";
|
||||
private const string BringToFrontMessage = "PSLAUNCHER_BRING_TO_FRONT";
|
||||
private static readonly int WM_PSLAUNCHER_BRING = RegisterWindowMessage(BringToFrontMessage);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int RegisterWindowMessage(string lpString);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
private const int HWND_BROADCAST = 0xFFFF;
|
||||
|
||||
public static string LogsDirectory { get; private set; } = string.Empty;
|
||||
|
||||
@@ -31,6 +46,17 @@ public partial class App : Application
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
// Single-instance : si une autre instance tourne déjà, on lui demande de
|
||||
// se mettre au premier plan via un broadcast Windows et on quitte tout de suite.
|
||||
_singleInstanceMutex = new System.Threading.Mutex(initiallyOwned: true,
|
||||
name: SingleInstanceMutexName, out var createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
PostMessage((IntPtr)HWND_BROADCAST, WM_PSLAUNCHER_BRING, IntPtr.Zero, IntPtr.Zero);
|
||||
Shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// Logs avant tout — pour ne rien perdre des erreurs au boot
|
||||
LogsDirectory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
|
||||
Reference in New Issue
Block a user