sources/Google.Solutions.IapDesktop/Program.cs (590 lines of code) (raw):

// // Copyright 2020 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Apis.Util; using Google.Solutions.Apis; using Google.Solutions.Apis.Analytics; using Google.Solutions.Apis.Auth; using Google.Solutions.Apis.Auth.Gaia; using Google.Solutions.Apis.Auth.Iam; using Google.Solutions.Apis.Client; using Google.Solutions.Apis.Compute; using Google.Solutions.Apis.Crm; using Google.Solutions.Apis.Logging; using Google.Solutions.Common; using Google.Solutions.Common.Diagnostics; using Google.Solutions.Common.Threading; using Google.Solutions.Common.Util; using Google.Solutions.Iap; using Google.Solutions.Iap.Net; using Google.Solutions.IapDesktop.Application; using Google.Solutions.IapDesktop.Application.Client; using Google.Solutions.IapDesktop.Application.Diagnostics; using Google.Solutions.IapDesktop.Application.Host; using Google.Solutions.IapDesktop.Application.Profile; using Google.Solutions.IapDesktop.Application.Profile.Auth; using Google.Solutions.IapDesktop.Application.Profile.Settings; using Google.Solutions.IapDesktop.Application.Theme; using Google.Solutions.IapDesktop.Application.ToolWindows.ProjectExplorer; using Google.Solutions.IapDesktop.Application.ToolWindows.Update; using Google.Solutions.IapDesktop.Application.Windows; using Google.Solutions.IapDesktop.Application.Windows.Auth; using Google.Solutions.IapDesktop.Application.Windows.Dialog; using Google.Solutions.IapDesktop.Application.Windows.Options; using Google.Solutions.IapDesktop.Application.Windows.ProjectExplorer; using Google.Solutions.IapDesktop.Application.Windows.ProjectPicker; using Google.Solutions.IapDesktop.Core; using Google.Solutions.IapDesktop.Core.ClientModel.Protocol; using Google.Solutions.IapDesktop.Core.ClientModel.Transport; using Google.Solutions.IapDesktop.Core.ObjectModel; using Google.Solutions.IapDesktop.Core.ProjectModel; using Google.Solutions.IapDesktop.Windows; using Google.Solutions.Mvvm.Binding; using Google.Solutions.Mvvm.Controls; using Google.Solutions.Mvvm.Diagnostics; using Google.Solutions.Mvvm.Theme; using Google.Solutions.Platform; using Google.Solutions.Platform.Dispatch; using Google.Solutions.Platform.Net; using Google.Solutions.Platform.Security; using Google.Solutions.Platform.Security.Cryptography; using Google.Solutions.Settings.Collection; using Google.Solutions.Ssh; using Google.Solutions.Terminal; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.WebSockets; using System.Reflection; using System.Security.Cryptography; using System.Threading; using System.Windows.Forms; #pragma warning disable CA1031 // Do not catch general exception types namespace Google.Solutions.IapDesktop { public class Program : SingletonApplicationBase { private static bool tracingEnabled = false; private static readonly TraceSource[] TraceSources = new[] { ApiTraceSource.Log, PlatformTraceSource.Log, CommonTraceSource.Log, IapTraceSource.Log, SshTraceSource.Log, TerminalTraceSource.Log, ApplicationTraceSource.Log, CoreTraceSource.Log, }; public static string LogFile => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Google", "IAP Desktop", "Logs", $"{DateTime.Now:yyyy-MM-dd_HHmm}.log"); public static bool IsLoggingEnabled { get { return tracingEnabled; } set { tracingEnabled = value; if (tracingEnabled) { var logFilePath = LogFile; Directory.CreateDirectory(new FileInfo(logFilePath).DirectoryName); var logListener = new TextWriterTraceListener(logFilePath); foreach (var trace in TraceSources) { trace.Listeners.Add(new DefaultTraceListener()); trace.Listeners.Add(logListener); trace.Switch.Level = SourceLevels.Verbose; } } else { foreach (var trace in TraceSources) { foreach (var listener in trace.Listeners.Cast<TraceListener>()) { listener.Flush(); } trace.Switch.Level = SourceLevels.Off; } } } } private static IEnumerable<Assembly> LoadExtensionAssemblies() { var deprecatedExtensions = new[] { "google.solutions.iapdesktop.extensions.rdp.dll", "google.solutions.iapdesktop.extensions.activity.dll", "google.solutions.iapdesktop.extensions.os.dll", "google.solutions.iapdesktop.extensions.shell.dll" }; return Directory.GetFiles( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location), "*.Extensions.*.dll") .Where(dllPath => !deprecatedExtensions.Contains(new FileInfo(dllPath).Name.ToLower())) .Select(dllPath => Assembly.LoadFrom(dllPath)); } private static UserProfile LoadProfileOrExit(Install install, CommandLineOptions options) { try { return install.OpenProfile(options.Profile); } catch (Exception e) { MessageBox.Show( e.Message, "Profile", MessageBoxButtons.OK, MessageBoxIcon.Error); Environment.Exit(1); throw new InvalidOperationException(); } } private static IAuthorization AuthorizeOrExit(IServiceProvider serviceProvider) { using (var dialog = serviceProvider .GetService<WindowActivator<AuthorizeView, AuthorizeViewModel, IDialogTheme>>() .CreateDialog()) { // // Initialize the view model. // dialog.ViewModel.DeviceEnrollment = DeviceEnrollment.Create( new CertificateStore(), serviceProvider.GetService<IRepository<IAccessSettings>>()); dialog.ViewModel.ClientRegistrations = new List<OidcClientRegistration>() { new OidcClientRegistration( OidcIssuer.Gaia, OAuthClient.Secrets.ClientId, OAuthClient.Secrets.ClientSecret, "/authorize/"), new OidcClientRegistration( OidcIssuer.Sts, OAuthClient.SdkSecrets.ClientId, OAuthClient.SdkSecrets.ClientSecret, "/") }; // // Allow recovery from common errors. // dialog.ViewModel.OAuthScopeNotGranted += (_, retryArgs) => { // // User did not grant 'cloud-platform' scope. // using (var scopeDialog = serviceProvider .GetService<WindowActivator<OAuthScopeNotGrantedView, OAuthScopeNotGrantedViewModel, IDialogTheme>>() .CreateDialog()) { retryArgs.Retry = scopeDialog.ShowDialog(dialog.ViewModel.View) == DialogResult.OK; } }; dialog.ViewModel.NetworkError += (_, retryArgs) => { // // This exception might be due to a missing/incorrect proxy // configuration, so give the user a chance to change proxy // settings. // var dialogParameters = new TaskDialogParameters( "Authorization failed", "IAP Desktop failed to complete the OAuth authorization. " + "This might be due to network communication issues.", retryArgs.Exception.Message) { Footnote = retryArgs.Exception.FullMessage(), Icon = TaskDialogIcon.Error }; dialogParameters.Buttons.Add(TaskDialogStandardButton.Cancel); dialogParameters.Buttons.Add(new TaskDialogCommandLinkButton( "Change network settings", DialogResult.OK)); if (serviceProvider .GetService<ITaskDialog>() .ShowDialog(dialog.ViewModel.View, dialogParameters) == DialogResult.OK) { // // Open settings. // retryArgs.Retry = OptionsDialog.Show( dialog.ViewModel.View!, (IServiceCategoryProvider)serviceProvider) == DialogResult.OK; } }; dialog.ViewModel.ShowOptions += (_, args) => { using (var scopeDialog = serviceProvider .GetService<WindowActivator<AuthorizeOptionsView, AuthorizeOptionsViewModel, IDialogTheme>>() .CreateDialog()) { scopeDialog.ShowDialog(dialog.ViewModel.View); } }; if (dialog.ShowDialog(null) == DialogResult.OK) { Debug.Assert(dialog.ViewModel.Authorization != null); return dialog.ViewModel.Authorization!; } else { // // User closed the dialog without completing the sign-in. // // // Ensure logs are flushed. // IsLoggingEnabled = false; Environment.Exit(1); throw new InvalidOperationException(); } } } //--------------------------------------------------------------------- // SingletonApplicationBase overrides. //--------------------------------------------------------------------- private MainForm? initializedMainForm = null; private readonly ManualResetEvent mainFormInitialized = new ManualResetEvent(false); private readonly CommandLineOptions commandLineOptions; internal Program( string name, CommandLineOptions commandLineOptions) : base(name) { this.commandLineOptions = commandLineOptions; } protected override int HandleFirstInvocation(string[] args) { IsLoggingEnabled = this.commandLineOptions.IsLoggingEnabled; // // Set up process mitigations. This must be done early, otherwise it's // ineffective. // try { ProcessMitigations.Apply(); } catch (Exception e) { ApplicationTraceSource.Log.TraceError(e); } // // Use the GDI-based TextRenderer class. // // NB. This must be set early, before the first window is created. // System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); System.Windows.Forms.Application.EnableVisualStyles(); #if DEBUG ApplicationTraceSource.Log.Switch.Level = SourceLevels.Verbose; TerminalTraceSource.Log.Switch.Level = SourceLevels.Verbose; #endif // // Install patches requires for IAP. // try { SystemPatch.UnrestrictUserAgentHeader.Install(); } catch (InvalidOperationException e) { ApplicationTraceSource.Log.TraceWarning( "Installing UnrestrictUserAgentHeader patch failed: {0}", e); } try { WebSocket.RegisterPrefixes(); SystemPatch.SetUsernameAsHostHeaderForWssRequests.Install(); } catch (Exception e) { ApplicationTraceSource.Log.TraceWarning( "Installing SetUsernameAsHostHeaderForWssRequests patch failed: {0}", e); } // // Set up layers. Services in a layer can lookup services in a lower layer, // but not in a higher layer. // var preAuthLayer = new ServiceRegistry(); var install = new Install(Install.DefaultBaseKeyPath); using (var profile = LoadProfileOrExit(install, this.commandLineOptions)) using (var processFactory = new Win32ChildProcessFactory(true)) { Debug.Assert(!Install.IsExecutingTests); // // Load pre-auth layer: Platform abstractions, API adapters. // // We can only load and access services that don't require // authorization. In particular, this means that we cannot access // any Google APIs. // preAuthLayer.AddSingleton<IInstall>(install); preAuthLayer.AddSingleton<UserAgent>(Install.UserAgent); preAuthLayer.AddSingleton(profile); preAuthLayer.AddSingleton<IUserProfile>(profile); preAuthLayer.AddSingleton<IClock>(SystemClock.Default); preAuthLayer.AddTransient<IConfirmationDialog, ConfirmationDialog>(); preAuthLayer.AddTransient<ITaskDialog, TaskDialog>(); preAuthLayer.AddTransient<ICredentialDialog, CredentialDialog>(); preAuthLayer.AddTransient<IInputDialog, InputDialog>(); preAuthLayer.AddTransient<IExceptionDialog, ExceptionDialog>(); preAuthLayer.AddTransient<IOperationProgressDialog, OperationProgressDialog>(); preAuthLayer.AddTransient<INotifyDialog, NotifyDialog>(); preAuthLayer.AddSingleton<IExternalRestClient, ExternalRestClient>(); preAuthLayer.AddTransient<HelpClient>(); preAuthLayer.AddTransient<BugReportClient>(); preAuthLayer.AddTransient<IHttpProxyAdapter, HttpProxyAdapter>(); preAuthLayer.AddSingleton<ProtocolRegistry>(); preAuthLayer.AddSingleton<IWin32ProcessFactory>(processFactory); preAuthLayer.AddSingleton<IWin32ProcessSet>(processFactory); preAuthLayer.AddSingleton<IKeyStore>( new KeyStore(CngProvider.MicrosoftSoftwareKeyStorageProvider)); // // Initialize UI, DPI mode. // var themeSettingsRepository = new ThemeSettingsRepository( profile.SettingsKey.CreateSubKey("Theme")); var dpiMode = (themeSettingsRepository.GetSettings().ScalingMode.Value) switch { ScalingMode.None => DpiAwarenessMode.DpiUnaware, ScalingMode.LegacyGdi => DpiAwarenessMode.SystemAware, _ => DpiAwarenessMode.SystemAware, }; if (DpiAwareness.IsSupported && dpiMode != DpiAwarenessMode.DpiUnaware) { // // Set DPI mode. // // NB. Setting the DPI mode programmatically (instead of using the // app manifest or app.config) does causes WinForms to *not* deliver // DPI change (WM_DPICHANGE) events, but that's ok. // try { DpiAwareness.ProcessMode = dpiMode; } catch (Exception e) { ApplicationTraceSource.Log.TraceWarning( "Setting DPI mode to {0} failed: {1}", dpiMode, e.Message); } } // // Load settings. // var appSettingsRepository = new ApplicationSettingsRepository(profile); preAuthLayer.AddSingleton<IRepository<IApplicationSettings>>(appSettingsRepository); if (appSettingsRepository.IsPolicyPresent) { // // If there are policies in place, mark the UA as // Enterprise-managed. // Install.UserAgent.Extensions = "Enterprise"; } var authSettingsRepository = new AuthSettingsRepository( profile.SettingsKey.CreateSubKey("Auth")); var accessSettingsRepository = new AccessSettingsRepository( profile.SettingsKey.CreateSubKey("Application"), profile.MachinePolicyKey?.OpenSubKey("Application"), profile.UserPolicyKey?.OpenSubKey("Application")); preAuthLayer.AddSingleton<IRepository<IAuthSettings>>(authSettingsRepository); preAuthLayer.AddSingleton<IOidcOfflineCredentialStore>(authSettingsRepository); preAuthLayer.AddSingleton<IRepository<IAccessSettings>>(accessSettingsRepository); preAuthLayer.AddSingleton<IRepository<IThemeSettings>>(themeSettingsRepository); preAuthLayer.AddSingleton(new ToolWindowStateRepository( profile.SettingsKey.CreateSubKey("ToolWindows"))); preAuthLayer.AddSingleton<IBindingContext, ViewBindingContext>(); preAuthLayer.AddTransient<IBrowserProtocolRegistry, BrowserProtocolRegistry>(); // // Load themes. // var themes = Themes.Load(themeSettingsRepository); preAuthLayer.AddSingleton<ISystemDialogTheme>(themes.SystemDialog); preAuthLayer.AddSingleton<IDialogTheme>(themes.Dialog); preAuthLayer.AddSingleton<IToolWindowTheme>(themes.ToolWindow); preAuthLayer.AddSingleton<IMainWindowTheme>(themes.MainWindow); // // Configure networking settings. // // NB. Until now, no network connections have been made. // var appSettings = appSettingsRepository.GetSettings(); // // Override default set of TLS versions. // ServicePointManager.SecurityProtocol = appSettings.TlsVersions.Value; try { // // Activate proxy settings based on app settings. // preAuthLayer.GetService<IHttpProxyAdapter>().ActivateSettings(appSettings); PscAndMtlsAwareHttpClientFactory.NtlmProxyAuthenticationRetries = (ushort)appSettings.ProxyAuthenticationRetries.Value; } catch (Exception) { // // Settings invalid -> ignore. // } // // Register and configure API client endpoints. // var serviceRoute = ServiceRoute.Public; { var accessSettings = accessSettingsRepository.GetSettings(); if (accessSettings.PrivateServiceConnectEndpoint.Value is var pscEndpoint && !string.IsNullOrEmpty(pscEndpoint)) { // // Enable PSC. // serviceRoute = new ServiceRoute(pscEndpoint); } // // Set connection pool limit. This limit applies per endpoint. // ServicePointManager.DefaultConnectionLimit = accessSettings.ConnectionLimit.Value; } preAuthLayer.AddSingleton(OAuthClient.ApiKey); preAuthLayer.AddSingleton(serviceRoute); preAuthLayer.AddSingleton(GaiaOidcClient.CreateEndpoint(serviceRoute)); preAuthLayer.AddSingleton(WorkforcePoolClient.CreateEndpoint(serviceRoute)); preAuthLayer.AddSingleton(ResourceManagerClient.CreateEndpoint(serviceRoute)); preAuthLayer.AddSingleton(ComputeEngineClient.CreateEndpoint(serviceRoute)); preAuthLayer.AddSingleton(OsLoginClient.CreateEndpoint(serviceRoute)); preAuthLayer.AddSingleton(LoggingClient.CreateEndpoint(serviceRoute)); preAuthLayer.AddSingleton(IapClient.CreateEndpoint(serviceRoute)); // // Enable telemetry if the user allows it. Do this before // authorization takes place. // TelemetryLog.Current = new AnalyticsLog( new MeasurementClient( MeasurementClient.CreateEndpoint(), Install.UserAgent, AnalyticsStream.ApiKey, AnalyticsStream.MeasurementId), install, new Dictionary<string, object>() { { DefaultParameters.UserAgent, Install.UserAgent.ToApplicationName() }, { DefaultParameters.UserAgentArchitecture, ProcessEnvironment.ProcessArchitecture.ToString() }, { DefaultParameters.UserAgentPlatformVersion, Environment.OSVersion.Version.ToString() }, { "osdrk", SystemTheme.ShouldAppsUseDarkMode ? "1" : "0" }, { "oscdp", DeviceCapabilities.Current.Dpi.ToString() }, { "ossdp", DeviceCapabilities.System.Dpi.ToString() }, { "apent", appSettingsRepository.IsPolicyPresent ? "1" : "0" }, { "apsrt", serviceRoute == ServiceRoute.Public ? "Public" : "PSC" }, }) { Enabled = appSettings.IsTelemetryEnabled.Value }; preAuthLayer.AddSingleton(TelemetryLog.Current); preAuthLayer.AddTransient<AuthorizeView>(); preAuthLayer.AddTransient<AuthorizeViewModel>(); preAuthLayer.AddTransient<AuthorizeOptionsView>(); preAuthLayer.AddTransient<AuthorizeOptionsViewModel>(); preAuthLayer.AddTransient<OAuthScopeNotGrantedView>(); preAuthLayer.AddTransient<OAuthScopeNotGrantedViewModel>(); preAuthLayer.AddTransient<PropertiesView>(); preAuthLayer.AddTransient<PropertiesViewModel>(); var authorization = AuthorizeOrExit(preAuthLayer); // // Authorization complete, now the main part of the application // can be initialized. // // Load main layer, containing everything else (except for // extensions). // var mainLayer = new ServiceRegistry(preAuthLayer); mainLayer.AddSingleton<IAuthorization>(authorization); mainLayer.AddTransient<IToolWindowHost, ToolWindowHost>(); var mainForm = new MainForm(mainLayer) { StartupUrl = this.commandLineOptions.StartupUrl, ShowWhatsNew = this.commandLineOptions.IsPostInstall && install.PreviousVersion != null }; mainLayer.AddSingleton<IJobHost>(mainForm); // // Load main services. // var eventService = new EventQueue(mainForm); // // Register API clients as singletons to ensure connection reuse. // mainLayer.AddSingleton<IResourceManagerClient, ResourceManagerClient>(); mainLayer.AddSingleton<IComputeEngineClient, ComputeEngineClient>(); mainLayer.AddSingleton<ILoggingClient, LoggingClient>(); mainLayer.AddSingleton<IOsLoginClient, OsLoginClient>(); mainLayer.AddSingleton<IIapClient, IapClient>(); mainLayer.AddSingleton<IReleaseFeed>(new GithubClient( new ExternalRestClient(), OAuthClient.RepositoryName)); mainLayer.AddTransient<IAddressResolver, AddressResolver>(); mainLayer.AddTransient<IWindowsCredentialGenerator, WindowsCredentialGenerator>(); mainLayer.AddSingleton<IJobService, JobService>(); mainLayer.AddSingleton<IEventQueue>(eventService); mainLayer.AddSingleton<ISessionBroker, SessionBroker>(); mainLayer.AddSingleton<IBrowser>(Browser.Default); var projectRepository = new ProjectRepository(profile.SettingsKey.CreateSubKey("Inventory")); mainLayer.AddSingleton<IProjectRepository>(projectRepository); mainLayer.AddSingleton<IProjectSettingsRepository>(projectRepository); mainLayer.AddSingleton<IProjectWorkspace, ProjectWorkspace>(); mainLayer.AddTransient<ICloudConsoleClient, CloudConsoleClient>(); mainLayer.AddTransient<IUpdatePolicy, UpdatePolicy>(); mainLayer.AddSingleton<IIapTransportFactory, IapTransportFactory>(); mainLayer.AddSingleton<IDirectTransportFactory, DirectTransportFactory>(); // // Load windows. // mainLayer.AddSingleton<IMainWindow>(mainForm); mainLayer.AddSingleton<IWin32Window>(mainForm); mainLayer.AddTransient<AccessInfoFlyoutView>(); mainLayer.AddTransient<AccessInfoViewModel>(); mainLayer.AddTransient<NewProfileView>(); mainLayer.AddTransient<NewProfileViewModel>(); mainLayer.AddTransient<IProjectPickerDialog, ProjectPickerDialog>(); mainLayer.AddTransient<ProjectPickerView>(); mainLayer.AddTransient<ProjectPickerViewModel>(); mainLayer.AddSingleton<IProjectExplorer, ProjectExplorer>(); mainLayer.AddSingleton<ProjectExplorerView>(); mainLayer.AddTransient<ProjectExplorerViewModel>(); mainLayer.AddTransient<ReleaseNotesView>(); mainLayer.AddTransient<ReleaseNotesViewModel>(); mainLayer.AddSingleton<UrlCommands>(); // // Load extensions. // foreach (var extension in LoadExtensionAssemblies()) { mainLayer.AddExtensionAssembly(extension); } // // Run app. // this.initializedMainForm = mainForm; this.initializedMainForm.Shown += (_, __) => { // // Form is now ready to handle subsequent invocations. // this.mainFormInitialized.Set(); }; this.initializedMainForm.FormClosing += (_, __) => { // // Stop handling subsequent invocations. // this.initializedMainForm = null; }; using (new DebugMessageThrottle(TimeSpan.FromMilliseconds(100))) using (var recorder = new MessageTraceRecorder(8)) { // // Replace the standard WinForms exception dialog. // System.Windows.Forms.Application.ThreadException += (_, exArgs) => ShowFatalErrorAndExit(exArgs.Exception, recorder.Capture()); mainForm.Shown += (_, __) => { // // Try to force the window into the foreground. This might // not be allowed in all circumstances, but ensures that the // window becomes visible after the user has completed a // (browser-based) authorization. // TrySetForegroundWindow(Process.GetCurrentProcess().Id); }; // // Show the main window. // try { System.Windows.Forms.Application.Run(mainForm); } catch (Exception e) { ShowFatalErrorAndExit(e, recorder.Capture()); } if (processFactory.ChildProcesses > 0) { // // Instead of killing child processes outright, give // them a chance to close gracefully (and possibly // save any work). // try { WaitDialog.Wait( null, "Waiting for applications to close...", async cancellationToken => { // // Give child processes a fixed time to close, // but the user might cancel early. // using (var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30))) using (var combinedCts = timeoutCts.Token.Combine(cancellationToken)) { await processFactory .CloseAsync(combinedCts.Token) .ConfigureAwait(true); } }); } catch (Exception e) { #if DEBUG if (!e.IsCancellation()) { ShowFatalErrorAndExit(e, recorder.Capture()); } #else _ = e; #endif } } // // Ensure logs are flushed. // IsLoggingEnabled = false; return 0; } } } protected override int HandleSubsequentInvocation(string[] args) { var options = CommandLineOptions.ParseOrExit(args); // // Make sure the main form is ready. If that's not the case // within a few seconds, then the process might be "stuck" // in the sign-in process. // if (!this.mainFormInitialized.WaitOne(TimeSpan.FromSeconds(3))) { throw new TimeoutException("The main form is not ready"); } // // This method is called on the named pipe server thread - switch to // main thread before doing any GUI stuff. // this.initializedMainForm?.Invoke(((Action)(() => { if (options.StartupUrl != null) { this.initializedMainForm.ConnectToUrl(options.StartupUrl); } }))); return 1; } protected override void HandleSubsequentInvocationException(Exception e) => ShowFatalErrorAndExit(e, null); private static void ShowFatalErrorAndExit(Exception e, MessageTrace? messageTrace) { // // Ensure logs are flushed. // IsLoggingEnabled = false; TelemetryLog.Current.Write( "app_crash", new Dictionary<string, object> { // // Only include a base set of information that's // safe to not contain any PII. // { "error", e.GetType().Name }, { "cause", e.InnerException?.GetType().Name ?? string.Empty }, { "location", e.ToString(ExceptionFormatOptions.Compact) } }); // // NB. This could be called on any thread, at any time, so avoid // touching the main form. // ErrorDialog.Show(new BugReport(typeof(Program), e) { WindowMessageTrace = messageTrace }); Environment.Exit(e.HResult); } /// <summary> /// The main entry point for the application. /// </summary> [STAThread] public static void Main(string[] args) { // // Parse command line to catch errors before even passing an invalid // command line to another instance of the app. // var options = CommandLineOptions.ParseOrExit(args); try { var appName = "IapDesktop"; if (options.Profile != null) { // // Incorporate the profile name (if provided) into the // name of the singleton app so that instances can // coexist if thex use different profiles. // appName += $"_{options.Profile}"; } new Program(appName, options).Run(args); } catch (Exception e) { ShowFatalErrorAndExit(e, null); } } internal static void LaunchNewInstance(CommandLineOptions options) { using (var process = new Process()) { process.StartInfo = new ProcessStartInfo() { FileName = Assembly.GetExecutingAssembly().Location, Arguments = options.ToString(), WindowStyle = ProcessWindowStyle.Normal }; process.Start(); } } } }