Repository navigation
Expand file tree
/
Copy pathProgram.cs
More file actions
329 lines (295 loc) · 13.5 KB
/
Copy pathProgram.cs
File metadata and controls
329 lines (295 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
using System.Diagnostics;
using System.Linq;
using System.IO.Pipes;
using System.Runtime.InteropServices;
namespace Wrok
{
internal static class Program
{
private const string MutexName = "Global\\Wrok_SingleInstanceMutex";
private const string PipeName = "Wrok_SingleInstancePipe";
private static Mutex? _singleInstanceMutex;
private const int HWND_BROADCAST = 0xFFFF;
// Eigene, private Fensternachricht statt der echten Windows-Systemnachricht
// WM_SHOWWINDOW: die wurde zuvor per HWND_BROADCAST an ALLE Top-Level-Fenster
// auf dem Desktop geschickt - andere laufende Anwendungen mit eigener
// WM_SHOWWINDOW-Behandlung koennten das faelschlich als echten
// Sichtbarkeits-Wechsel interpretieren. RegisterWindowMessage liefert fuer
// denselben String prozessuebergreifend immer denselben Wert und wird von
// fremden Fenstern einfach ignoriert (Standard-Win32-Muster fuer private
// IPC-Nachrichten, siehe z. B. "TaskbarCreated").
internal const string ShowRequestMessageName = "Wrok_ShowRequest_F3B2A6D1";
[DllImport("user32.dll", SetLastError = true)]
private static extern bool PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern uint RegisterWindowMessage(string lpString);
private static readonly int WM_WROK_SHOW = (int)RegisterWindowMessage(ShowRequestMessageName);
[STAThread]
private static void Main()
{
// --- Persistent file logging setup (writes Trace to %LOCALAPPDATA%\Wrok\logs\app.log) ---
try
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var appDir = Path.Combine(localAppData, "Wrok");
var logsDir = Path.Combine(appDir, "logs");
Directory.CreateDirectory(logsDir);
var logFile = Path.Combine(logsDir, "app.log");
// Keep a rolling simple policy: rename existing file if bigger than 5 MB,
// then prune old archives beyond a small retention count - otherwise the
// archived app-<timestamp>.log files pile up forever for a long-running
// "portable" tool that nobody ever manually cleans out.
try
{
const long maxSize = 5 * 1024 * 1024;
const int maxArchives = 5;
if (File.Exists(logFile))
{
var fi = new FileInfo(logFile);
if (fi.Length > maxSize)
{
var archived = Path.Combine(logsDir, $"app-{DateTime.UtcNow:yyyyMMddHHmmss}.log");
try { File.Move(logFile, archived); } catch { /* ignore */ }
}
}
try
{
var oldArchives = new DirectoryInfo(logsDir)
.GetFiles("app-*.log")
.OrderByDescending(f => f.LastWriteTimeUtc)
.Skip(maxArchives);
foreach (var old in oldArchives)
{
try { old.Delete(); } catch { /* ignore einzelne Datei */ }
}
}
catch { /* defensive - Aufraeumen ist nicht kritisch */ }
}
catch { /* defensive */ }
var tw = new StreamWriter(new FileStream(logFile, FileMode.Append, FileAccess.Write, FileShare.Read))
{
AutoFlush = true
};
Trace.Listeners.Add(new TextWriterTraceListener(tw));
Trace.AutoFlush = true;
Trace.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Wrok starting (pid={Process.GetCurrentProcess().Id})");
}
catch
{
// If logging setup fails, continue without file logging.
}
// --- end logging setup ---
bool isNewInstance;
_singleInstanceMutex = new Mutex(true, MutexName, out isNewInstance);
if (!isNewInstance)
{
// Another instance is running � notify it to show its window and exit.
NotifyExistingInstance();
return;
}
// In the single (first) instance: start a named-pipe server to receive SHOW messages.
StartNamedPipeServer();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
try
{
// Null-conditional: nach RestartApplication() ist das Feld bereits
// freigegeben und genullt, dann soll hier einfach nichts passieren.
_singleInstanceMutex?.ReleaseMutex();
_singleInstanceMutex?.Dispose();
}
catch
{
// Swallow exceptions on shutdown; not critical.
}
}
/// <summary>
/// Startet eine neue Instanz und beendet die aktuelle geordnet. Für
/// Einstellungen, die erst beim Neuerzeugen der CoreWebView2Environment
/// wirken (z. B. Proxy). Mutex zuerst freigeben, dann die neue Instanz
/// starten – sonst hält die alte Instanz den Mutex noch, wenn die neue
/// ihn prüft, und die neue hielte sich fälschlich für einen Zweitstart.
/// </summary>
public static void RestartApplication()
{
try
{
_singleInstanceMutex?.ReleaseMutex();
_singleInstanceMutex?.Dispose();
_singleInstanceMutex = null;
}
catch (Exception ex)
{
Trace.WriteLine($"RestartApplication: Mutex-Freigabe fehlgeschlagen: {ex}");
}
try
{
Process.Start(Environment.ProcessPath ?? Application.ExecutablePath);
}
catch (Exception ex)
{
Trace.WriteLine($"RestartApplication: neue Instanz konnte nicht gestartet werden: {ex}");
}
Application.Exit();
}
/// <summary>
/// Startet den (bereits heruntergeladenen und signaturgeprüften, siehe
/// UpdateInstaller) Installer und beendet die laufende Instanz danach - fürs
/// Selbst-Update aus der App heraus, analog zu RestartApplication() oben,
/// nur eben mit einem zwischengeschalteten Setup statt einem reinen
/// Neustart.
///
/// Ablauf:
/// 1. Installer per ShellExecute starten (nötig für die UAC-Erhöhung über
/// dessen eingebettetes Manifest - PrivilegesRequired=admin in
/// InstallScript.iss; ein normaler Process.Start ohne ShellExecute würde
/// nur mit "Elevation erforderlich" fehlschlagen, statt den UAC-Dialog
/// zu zeigen).
/// 2. Einen von uns unabhängigen kleinen Warte-Prozess anstossen, der auf
/// das Ende des Installers wartet und danach Wrok neu startet. Not-
/// wendig, weil unser eigener Prozess selbst nicht mehr da sein wird,
/// wenn der Installer fertig ist: Entweder er beendet sich gleich unten
/// selbst, oder - falls das Timing eng wird - vorher schon durch
/// CloseApplications=force im Setup (das über den Windows Restart
/// Manager per WM_QUERYENDSESSION genau die laufende Instanz schliesst,
/// deren Programmdatei ersetzt werden soll - siehe auch _sessionEnding
/// in MainForm.cs, das für genau diesen Fall ein echtes Beenden statt
/// eines Minimierens in den Tray erzwingt).
/// 3. Mutex freigeben und Application.Exit() - wie bei RestartApplication().
/// </summary>
public static void RestartApplicationForUpdate(string installerPath)
{
var exePath = Environment.ProcessPath ?? Application.ExecutablePath;
try
{
var installer = Process.Start(new ProcessStartInfo
{
FileName = installerPath,
Arguments = "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /NOICONS",
UseShellExecute = true
});
if (installer != null)
{
StartRelaunchWatcher(installer.Id, exePath);
}
else
{
Trace.WriteLine("RestartApplicationForUpdate: Installer-Prozess konnte nicht ermittelt werden.");
}
}
catch (Exception ex)
{
// Installer nicht gestartet (z. B. UAC-Dialog abgebrochen) - dann auch
// nicht die laufende Instanz beenden, sonst bleibt der Nutzer ganz
// ohne laufendes Wrok zurück.
Trace.WriteLine($"RestartApplicationForUpdate: Installer konnte nicht gestartet werden: {ex}");
return;
}
try
{
_singleInstanceMutex?.ReleaseMutex();
_singleInstanceMutex?.Dispose();
_singleInstanceMutex = null;
}
catch (Exception ex)
{
Trace.WriteLine($"RestartApplicationForUpdate: Mutex-Freigabe fehlgeschlagen: {ex}");
}
Application.Exit();
}
/// <summary>
/// Wartet - unabhängig vom (gleich endenden) aktuellen Prozess - auf das Ende
/// des Installers und startet danach Wrok neu. Läuft bewusst unerhöht
/// (PowerShell selbst braucht keine Admin-Rechte; nur der Installer, der schon
/// separat läuft, brauchte sie). Rein informativ: schlägt das Starten des
/// Watchers fehl, bleibt Wrok nach dem Update einfach zu - der Nutzer kann es
/// dann manuell wieder öffnen, das Update selbst ist davon nicht betroffen.
/// </summary>
private static void StartRelaunchWatcher(int installerProcessId, string exePath)
{
try
{
var script =
$"Wait-Process -Id {installerProcessId} -ErrorAction SilentlyContinue; " +
"Start-Sleep -Milliseconds 500; " +
$"Start-Process -FilePath '{exePath}'";
Process.Start(new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-NoProfile -WindowStyle Hidden -Command \"{script}\"",
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
});
}
catch (Exception ex)
{
Trace.WriteLine($"StartRelaunchWatcher: Warte-Prozess konnte nicht gestartet werden: {ex}");
}
}
private static void NotifyExistingInstance()
{
try
{
using (var client = new NamedPipeClientStream(
".",
PipeName,
PipeDirection.Out,
PipeOptions.Asynchronous))
{
client.Connect(500); // 0.5 second timeout
using (var writer = new StreamWriter(client))
{
writer.AutoFlush = true;
writer.WriteLine("SHOW");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Failed to notify existing instance: {ex.Message}");
}
}
private static void StartNamedPipeServer()
{
var thread = new Thread(() =>
{
while (true)
{
try
{
using (var server = new NamedPipeServerStream(
PipeName,
PipeDirection.In,
1,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous))
{
server.WaitForConnection();
using (var reader = new StreamReader(server))
{
var message = reader.ReadLine();
if (string.Equals(message, "SHOW", StringComparison.OrdinalIgnoreCase))
{
// Eigene Nachricht broadcasten - MainForm.WndProc reagiert darauf und holt das Fenster nach vorne.
PostMessage((IntPtr)HWND_BROADCAST, WM_WROK_SHOW, IntPtr.Zero, IntPtr.Zero);
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Named pipe server error: {ex.Message}");
// Continue loop and accept next client.
}
}
})
{
IsBackground = true,
Name = "Wrok_SingleInstancePipeServer"
};
thread.Start();
}
}
}