Esempio n. 1
0
        private void StartLogging()
        {
            _logger = new HostLogger(LogLevel);

            // We need to not write log messages to Stdio
            // if it's being used as a protocol transport
            if (!Stdio)
            {
                var hostLogger = new PSHostLogger(Host.UI);
                _loggerUnsubscribers.Add(_logger.Subscribe(hostLogger));
            }

            string logDirPath = GetLogDirPath();
            string logPath    = Path.Combine(logDirPath, "StartEditorServices.log");

            // Temp debugging sessions may try to reuse this same path,
            // so we ensure they have a unique path
            if (File.Exists(logPath))
            {
                int randomInt = new Random().Next();
                logPath = Path.Combine(logDirPath, $"StartEditorServices-temp{randomInt.ToString("X", CultureInfo.InvariantCulture.NumberFormat)}.log");
            }

            var fileLogger = StreamLogger.CreateWithNewFile(logPath);

            _disposableResources.Add(fileLogger);
            IDisposable fileLoggerUnsubscriber = _logger.Subscribe(fileLogger);

            fileLogger.AddUnsubscriber(fileLoggerUnsubscriber);
            _loggerUnsubscribers.Add(fileLoggerUnsubscriber);

            _logger.Log(PsesLogLevel.Diagnostic, "Logging started");
        }
        /// <summary>
        /// Write a startup failure to the session file.
        /// </summary>
        /// <param name="reason">The reason for the startup failure.</param>
        /// <param name="details">Any extra details, which will be serialized as JSON.</param>
        public void WriteSessionFailure(string reason, object details)
        {
            _logger.Log(PsesLogLevel.Diagnostic, "Writing session failure");

            var sessionObject = new Dictionary <string, object>
            {
                { "status", "failed" },
                { "reason", reason },
            };

            if (details != null)
            {
                sessionObject["details"] = details;
            }

            WriteSessionObject(sessionObject);
        }
        /// <summary>
        /// Write a startup failure to the session file.
        /// </summary>
        /// <param name="reason">The reason for the startup failure.</param>
        /// <param name="details">Any extra details, which will be serialized as JSON.</param>
        public void WriteSessionFailure(string reason, object details)
        {
            _logger.Log(PsesLogLevel.Diagnostic, "Writing session failure");

            Dictionary <string, object> sessionObject = new()
            {
                { "status", "failed" },
                { "reason", reason },
            };
        private void StartLogging()
        {
            _logger = new HostLogger(LogLevel);

            // We need to not write log messages to Stdio
            // if it's being used as a protocol transport
            if (!Stdio)
            {
                var hostLogger = new PSHostLogger(Host.UI);
                _loggerUnsubscribers.Add(_logger.Subscribe(hostLogger));
            }

            string logPath    = Path.Combine(GetLogDirPath(), "StartEditorServices.log");
            var    fileLogger = StreamLogger.CreateWithNewFile(logPath);

            _disposableResources.Add(fileLogger);
            IDisposable fileLoggerUnsubscriber = _logger.Subscribe(fileLogger);

            fileLogger.AddUnsubscriber(fileLoggerUnsubscriber);
            _loggerUnsubscribers.Add(fileLoggerUnsubscriber);

            _logger.Log(PsesLogLevel.Diagnostic, "Logging started");
        }
        /// <summary>
        /// Create a new Editor Services loader.
        /// </summary>
        /// <param name="logger">The host logger to use.</param>
        /// <param name="hostConfig">The host configuration to start editor services with.</param>
        /// <param name="sessionFileWriter">The session file writer to write the session file with.</param>
        /// <returns></returns>
        public static EditorServicesLoader Create(
            HostLogger logger,
            EditorServicesConfig hostConfig,
            ISessionFileWriter sessionFileWriter,
            IReadOnlyCollection <IDisposable> loggersToUnsubscribe)
        {
            if (logger == null)
            {
                throw new ArgumentNullException(nameof(logger));
            }

            if (hostConfig == null)
            {
                throw new ArgumentNullException(nameof(hostConfig));
            }

#if CoreCLR
            // In .NET Core, we add an event here to redirect dependency loading to the new AssemblyLoadContext we load PSES' dependencies into
            logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading");

            var psesLoadContext = new PsesLoadContext(s_psesDependencyDirPath);

            if (hostConfig.LogLevel == PsesLogLevel.Diagnostic)
            {
                AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) =>
                {
                    logger.Log(
                        PsesLogLevel.Diagnostic,
                        $"Loaded into load context {AssemblyLoadContext.GetLoadContext(args.LoadedAssembly)}: {args.LoadedAssembly}");
                };
            }

            AssemblyLoadContext.Default.Resolving += (AssemblyLoadContext defaultLoadContext, AssemblyName asmName) =>
            {
                logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}");

                // We only want the Editor Services DLL; the new ALC will lazily load its dependencies automatically
                if (!string.Equals(asmName.Name, "Microsoft.PowerShell.EditorServices", StringComparison.Ordinal))
                {
                    return(null);
                }

                string asmPath = Path.Combine(s_psesDependencyDirPath, $"{asmName.Name}.dll");

                logger.Log(PsesLogLevel.Verbose, "Loading PSES DLL using new assembly load context");

                return(psesLoadContext.LoadFromAssemblyPath(asmPath));
            };
#else
            // In .NET Framework we add an event here to redirect dependency loading in the current AppDomain for PSES' dependencies
            logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for dependency loading");

            if (hostConfig.LogLevel == PsesLogLevel.Diagnostic)
            {
                AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) =>
                {
                    logger.Log(
                        PsesLogLevel.Diagnostic,
                        $"Loaded {args.LoadedAssembly.GetName()}");
                };
            }

            // Unlike in .NET Core, we need to be look for all dependencies in .NET Framework, not just PSES.dll
            AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs args) =>
            {
                logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}");

                var asmName = new AssemblyName(args.Name);
                var dllName = $"{asmName.Name}.dll";

                // First look for the required assembly in the .NET Framework DLL dir
                string baseDirAsmPath = Path.Combine(s_psesBaseDirPath, dllName);
                if (File.Exists(baseDirAsmPath))
                {
                    logger.Log(PsesLogLevel.Diagnostic, $"Loading {args.Name} from PSES base dir into LoadFrom context");
                    return(Assembly.LoadFrom(baseDirAsmPath));
                }

                // Then look in the shared .NET Standard directory
                string asmPath = Path.Combine(s_psesDependencyDirPath, dllName);
                if (File.Exists(asmPath))
                {
                    logger.Log(PsesLogLevel.Diagnostic, $"Loading {args.Name} from PSES dependency dir into LoadFrom context");
                    return(Assembly.LoadFrom(asmPath));
                }

                return(null);
            };
#endif

            return(new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe));
        }
        /// <summary>
        /// Load Editor Services and its dependencies in an isolated way and start it.
        /// This method's returned task will end when Editor Services shuts down.
        /// </summary>
        /// <returns></returns>
        public async Task LoadAndRunEditorServicesAsync()
        {
            // Log important host information here
            LogHostInformation();

#if !CoreCLR
            // Make sure the .NET Framework version supports .NET Standard 2.0
            CheckNetFxVersion();
#endif
            // Ensure the language mode allows us to run
            CheckLanguageMode();

            // Add the bundled modules to the PSModulePath
            UpdatePSModulePath();

            // Check to see if the configuration we have is valid
            ValidateConfiguration();

            // Method with no implementation that forces the PSES assembly to load, triggering an AssemblyResolve event
            _logger.Log(PsesLogLevel.Verbose, "Loading PowerShell Editor Services");
            LoadEditorServices();

            _logger.Log(PsesLogLevel.Verbose, "Starting EditorServices");
            using (var editorServicesRunner = new EditorServicesRunner(_logger, _hostConfig, _sessionFileWriter, _loggersToUnsubscribe))
            {
                // The trigger method for Editor Services
                // We will wait here until Editor Services shuts down
                await editorServicesRunner.RunUntilShutdown().ConfigureAwait(false);
            }
        }
        /// <summary>
        /// Start and run Editor Services and then wait for shutdown.
        /// </summary>
        /// <returns>A task that ends when Editor Services shuts down.</returns>
        public async Task RunUntilShutdown()
        {
            // Start Editor Services
            Task runAndAwaitShutdown = CreateEditorServicesAndRunUntilShutdown();

            // Now write the session file
            _logger.Log(PsesLogLevel.Diagnostic, "Writing session file");
            _sessionFileWriter.WriteSessionStarted(_config.LanguageServiceTransport, _config.DebugServiceTransport);

            // Finally, wait for Editor Services to shut down
            await runAndAwaitShutdown.ConfigureAwait(false);
        }
        /// <summary>
        /// Start and run Editor Services and then wait for shutdown.
        /// </summary>
        /// <returns>A task that ends when Editor Services shuts down.</returns>
        public Task RunUntilShutdown()
        {
            // Start Editor Services
            Task runAndAwaitShutdown = CreateEditorServicesAndRunUntilShutdown();

            // Now write the session file
            _logger.Log(PsesLogLevel.Diagnostic, "Writing session file");
            _sessionFileWriter.WriteSessionStarted(_config.LanguageServiceTransport, _config.DebugServiceTransport);

            // Finally, wait for Editor Services to shut down
            _logger.Log(PsesLogLevel.Diagnostic, "Waiting on PSES run/shutdown");
            return(runAndAwaitShutdown);
        }
        protected override void EndProcessing()
        {
            _logger.Log(PsesLogLevel.Diagnostic, "Beginning EndProcessing block");

            try
            {
                // First try to remove PSReadLine to decomplicate startup
                // If PSReadLine is enabled, it will be re-imported later
                RemovePSReadLineForStartup();

                // Create the configuration from parameters
                EditorServicesConfig editorServicesConfig = CreateConfigObject();

                var sessionFileWriter = new SessionFileWriter(_logger, SessionDetailsPath);
                _logger.Log(PsesLogLevel.Diagnostic, "Session file writer created");

                using (var psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, sessionFileWriter, _loggerUnsubscribers))
                {
                    _logger.Log(PsesLogLevel.Verbose, "Loading EditorServices");
                    psesLoader.LoadAndRunEditorServicesAsync().Wait();
                }
            }
            catch (Exception e)
            {
                _logger.LogException("Exception encountered starting EditorServices", e);

                // Give the user a chance to read the message if they have a console
                if (!Stdio)
                {
                    Host.UI.WriteLine("\n== Press any key to close terminal ==");
                    Console.ReadKey();
                }

                ThrowTerminatingError(new ErrorRecord(e, "PowerShellEditorServicesError", ErrorCategory.NotSpecified, this));
            }
            finally
            {
                foreach (IDisposable disposableResource in _disposableResources)
                {
                    disposableResource.Dispose();
                }
            }
        }