-
Notifications
You must be signed in to change notification settings - Fork 4
/
ApplicationInfo.cs
254 lines (219 loc) · 8.06 KB
/
ApplicationInfo.cs
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
namespace Menees
{
#region Using Directives
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Menees.Diagnostics;
#endregion
/// <summary>
/// Provides information about the current application.
/// </summary>
public static partial class ApplicationInfo
{
#region Private Data Members
private static readonly Lazy<int> LazyProcessId = new(() =>
{
using (Process current = Process.GetCurrentProcess())
{
return current.Id;
}
});
private static string? applicationName;
private static int showingUnhandledExceptionErrorMessage;
private static bool? isDebugBuild;
private static Func<bool>? isActivated;
#endregion
#region Public Properties
/// <summary>
/// Gets the application name.
/// </summary>
public static string ApplicationName
{
get
{
string? result = applicationName;
if (string.IsNullOrEmpty(result))
{
result = GlobalLogContext.GetDefaultApplicationName();
}
return result!;
}
}
/// <summary>
/// Gets the base directory for the current application.
/// </summary>
/// <remarks>
/// This is usually the same as the <see cref="ExecutableFile"/>'s directory, but it can be
/// different for applications using custom AppDomains (e.g., web apps running in IIS).
/// </remarks>
public static string BaseDirectory
{
get
{
string result = AppDomain.CurrentDomain.BaseDirectory ?? string.Empty;
return result;
}
}
/// <summary>
/// Gets the full path for the executable file that started the application.
/// </summary>
/// <remarks>
/// This is similar to System.Windows.Forms.Application.ExecutablePath, except this supports paths
/// longer than MAX_PATH (260) and paths using a "\\?\" prefix (e.g., ASP.NET worker processes).
/// </remarks>
public static string ExecutableFile
{
get
{
string result = NativeMethods.GetModuleFileName(IntPtr.Zero);
return result;
}
}
/// <summary>
/// Gets the current application's Windows process ID.
/// </summary>
public static int ProcessId => LazyProcessId.Value;
/// <summary>
/// Gets whether the current application is activated per the lambda passed to <see cref="Initialize"/>.
/// </summary>
public static bool IsActivated
{
get
{
bool result = isActivated?.Invoke() ?? false;
return result;
}
}
/// <summary>
/// Gets whether the current application is running a debug build.
/// </summary>
/// <remarks>
/// This depends on the applicationAssembly parameter passed to <see cref="Initialize"/>.
/// If <see cref="Initialize"/>, hasn't been called, then this depends on the current assembly.
/// </remarks>
public static bool IsDebugBuild => isDebugBuild ?? ReflectionUtility.IsDebugBuild(typeof(ApplicationInfo).Assembly);
#endregion
#region Private Properties
private static bool IsWindowsUserRunningAsAdministrator
{
get
{
Conditions.RequireState(IsWindows, "This can only be called safely on Windows.");
#pragma warning disable CA1416 // Validate platform compatibility. The callers in .Core.cs and .Framework.cs ensure Windows.
using (WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal currentPrincipal = new(currentIdentity);
bool result = currentPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
return result;
}
#pragma warning restore CA1416 // Validate platform compatibility
}
}
#endregion
#region Public Methods
/// <summary>
/// Used to initialize the application's name, error handling, etc.
/// </summary>
/// <param name="applicationName">Pass null to use the current AppDomain's friendly name.</param>
/// <param name="applicationAssembly">The assembly that's initializing the application, typically the main executable.</param>
/// <param name="isActivated">A function to determine if <see cref="IsActivated"/> should consider the application activated.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void Initialize(string applicationName, Assembly? applicationAssembly = null, Func<bool>? isActivated = null)
{
// Try to log when unhandled or unobserved exceptions occur. This info can be very useful if the process crashes.
// Note: Windows Forms unhandled exceptions are logged via Menees.Windows.Forms.WindowsUtility.InitializeApplication.
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
LogLevel level = e.IsTerminating ? LogLevel.Fatal : LogLevel.Error;
Log.Write(typeof(ApplicationInfo), level, "An unhandled exception occurred.", e.ExceptionObject as Exception);
};
TaskScheduler.UnobservedTaskException += (s, e) =>
{
Log.Error(typeof(ApplicationInfo), "A Task exception occurred, but it was never observed by the Task caller.", e.Exception);
};
// If the name is null or empty, then the property accessor will use the AppDomain's friendly name.
ApplicationInfo.applicationName = applicationName;
ApplicationInfo.isActivated = isActivated;
// Put it in the log's global context, so it will appear in every log entry.
if (!string.IsNullOrEmpty(applicationName))
{
Log.GlobalContext.SetApplicationName(applicationName);
}
// Since apps refer to this library via NuGet references, they'll always use the release build of this library.
// So we'll check the main assembly's build configuration via reflection instead of using a compile-time constant.
Assembly assembly = applicationAssembly ?? Assembly.GetEntryAssembly() ?? typeof(ApplicationInfo).Assembly;
isDebugBuild = ReflectionUtility.IsDebugBuild(assembly);
// Call SetErrorMode to disable the display of Windows Shell modal error dialogs for
// file not found, Windows Error Reporting, and other errors. From SetErrorMode docs
// at http://msdn.microsoft.com/en-us/library/ms680621.aspx:
// "Best practice is that all applications call the process-wide SetErrorMode
// function with a parameter of SEM_FAILCRITICALERRORS at startup. This is
// to prevent error mode dialogs from hanging the application."
NativeMethods.DisableShellModalErrorDialogs();
InitializeTargetFramework();
}
/// <summary>
/// Creates a hierarchical store for loading and saving user-level settings for the current application.
/// </summary>
/// <returns>A new settings store.</returns>
public static ISettingsStore CreateUserSettingsStore() => new FileSettingsStore();
/// <summary>
/// Shows a single unhandled exception at a time.
/// </summary>
/// <param name="ex">The exception to show</param>
/// <param name="showExceptionMessage">The action to invoke for a simple "MessageBox" display.</param>
/// <param name="showExceptionCustom">The action to invoke for a custom exception display.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void ShowUnhandledException(
Exception ex,
Action<string>? showExceptionMessage,
Action<Exception>? showExceptionCustom = null)
{
// Only let the first thread in show a message box.
int showing = Interlocked.Increment(ref showingUnhandledExceptionErrorMessage);
try
{
if (showing == 1)
{
if (showExceptionCustom != null)
{
showExceptionCustom(ex);
}
else if (showExceptionMessage != null)
{
StringBuilder sb = new();
sb.AppendLine(Exceptions.GetMessage(ex));
if (IsDebugBuild)
{
// Show the root exception's type and call stack in debug builds.
sb.AppendLine().AppendLine(ex.ToString());
}
string message = sb.ToString().Trim();
showExceptionMessage(message);
}
}
}
finally
{
Interlocked.Decrement(ref showingUnhandledExceptionErrorMessage);
}
}
#endregion
#region Private Methods
static partial void InitializeTargetFramework();
#endregion
}
}