protected override int HandleFirstInvocation()

in sources/Google.Solutions.IapDesktop/Program.cs [315:768]


        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;
                }
            }
        }