private static int Attach()

in src/InstrumentationEngine.Attach/CommandHandler/AttachCommandHandler.cs [58:291]


        private static int Attach(int processId,
            FileInfo[] configs,
            LoggingFlags logLevel,
            LoggingFlags logFileLevel,
            FileSystemInfo? logFilePath)
        {
            #region Target Process
            Process process;
            try
            {
                process = Process.GetProcessById(processId);
            }
            catch (Exception ex)
            {
                WriteError(ex.Message);
                return ExitCodeFailure;
            }
            #endregion

            #region Configuration Sources
            var sourceInfos = new List<ConfigurationSourceInfo>();

            // Schema file embedded name is the same that the full type name that was generated from the schema file
            string schemaResourceName = typeof(InstrumentationConfigurationSources).FullName + ".xsd";

            foreach (var configFile in configs)
            {
                var sourceInfo = new ConfigurationSourceInfo()
                {
                    ConfigSourceFilePath = configFile.FullName
                };

                // Attempt to resolve fullpath
                string? configFileDirectory = Path.GetDirectoryName(sourceInfo.ConfigSourceFilePath);
                if (null == configFileDirectory || !Directory.Exists(configFileDirectory))
                {
                    WriteError(Invariant($"Could not find directory '{configFileDirectory}'."));
                    return ExitCodeFailure;
                }

                sourceInfo.ConfigSourceDirectory = configFileDirectory;

                #region Parse Configuration Sources

                // Read schema file
                using (Stream? schemaStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(schemaResourceName))
                using (XmlReader schemaReader = XmlReader.Create(schemaStream, new XmlReaderSettings() { XmlResolver = null, DtdProcessing = DtdProcessing.Prohibit }))
                {
                    // Create reader settings that conducts validation
                    XmlReaderSettings readerSettings = new XmlReaderSettings() { XmlResolver = null, DtdProcessing = DtdProcessing.Prohibit };
                    readerSettings.Schemas.Add(string.Empty, schemaReader);
                    readerSettings.ValidationType = ValidationType.Schema;

                    // Read configuration source file
                    // XmlReader handles filepaths as URI and escapes widechars so we convert it to a stream first.
                    using (var stream = new StreamReader(sourceInfo.ConfigSourceFilePath))
                    using (XmlReader reader = XmlReader.Create(stream, readerSettings))
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(InstrumentationConfigurationSources));
                        try
                        {
                            sourceInfo.Sources = (InstrumentationConfigurationSources)serializer.Deserialize(reader);
                        }
                        // InvalidOperationException is thrown when XML does not conform to the schema
                        catch (InvalidOperationException ex)
                        {
                            // Log the location of the error
                            WriteError($"Error: {ex.Message}");
                            if (null != ex.InnerException)
                            {
                                // Log the detailed information of why the XML does not conform to the schema
                                WriteError(ex.InnerException.Message);
                            }
                            return ExitCodeFailure;
                        }
                    }
                }

                #endregion

                sourceInfos.Add(sourceInfo);
            }
            #endregion

            #region Engine RootPath
            // This executable should be in the [Root]\Tools\Attach directory. Get the root directory
            // and build back to the instrumentation engine file path.

            // CONSIDER: Is there a better way to get the engine path instead of building the path
            // relative to the current executable? For example, requiring the root path of the engine
            // to be passed as a parameter (which increases the difficulty of using this executable).

            string? rootDirectory = Environment.GetEnvironmentVariable("MicrosoftInstrumentationEngine_InstallationRoot");
            if (string.IsNullOrEmpty(rootDirectory))
            {
                string? attachDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                if (null == attachDirectory || !Directory.Exists(attachDirectory))
                {
                    WriteError(Invariant($"Directory '{attachDirectory}' does not exist."));
                    return ExitCodeFailure;
                }

                string? toolsDirectory = Path.GetDirectoryName(attachDirectory);
                if (null == toolsDirectory || !Directory.Exists(toolsDirectory))
                {
                    WriteError(Invariant($"Directory '{toolsDirectory}' does not exist."));
                    return ExitCodeFailure;
                }

                rootDirectory = Path.GetDirectoryName(toolsDirectory);
            }
            else
            {
                rootDirectory = Environment.ExpandEnvironmentVariables(rootDirectory);
            }

            if (null == rootDirectory || !Directory.Exists(rootDirectory))
            {
                WriteError(Invariant($"Directory '{rootDirectory}' does not exist."));
                return ExitCodeFailure;
            }
            #endregion

            #region Target Process Architecture
            bool isTargetProcess32Bit = true;
            if (RuntimeInformation.OSArchitecture == Architecture.X64)
            {
                isTargetProcess32Bit = false;
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    try
                    {
                        // Returns true for 32-bit applications running on a 64-bit OS
                        if (!NativeMethods.IsWow64Process(process.Handle, out isTargetProcess32Bit))
                        {
                            Exception? ex = Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
                            if (null != ex)
                            {
                                WriteError(Invariant($"Failed to check process bitness: {ex.Message}"));
                            }
                            else
                            {
                                WriteError(Invariant($"Failed to check process bitness."));
                            }
                            return ExitCodeFailure;
                        }
                    }
                    catch (Win32Exception ex)
                    {
                        WriteError(Invariant($"Failed to check process bitness (code: {ex.NativeErrorCode}): {ex.Message}"));
                        return ExitCodeFailure;
                    }
                }
            }
            #endregion

            #region Generate Engine Configuration
            // Create CLRIE configuration object for XML serialization
            var configuration = new InstrumentationEngineConfiguration();

            configuration.InstrumentationEngine = GenerateEngineConfiguration(
                logLevel,
                logFileLevel,
                logFilePath);

            if (TryParseInstrumentationMethodConfiguration(
                    sourceInfos,
                    isTargetProcess32Bit ? ChipType.x86 : ChipType.x64,
                    out InstrumentationMethodsTypeAddInstrumentationMethod[]? methods) &&
                methods != null)
            {
                configuration.InstrumentationMethods = methods;
            }
            else
            {
                WriteError("Unable to parse Instrumentation Methods from configuration sources.");
                return ExitCodeFailure;
            }

            XmlSerializer engineConfigSerializer = new XmlSerializer(typeof(InstrumentationEngineConfiguration));
            byte[] bytes;
            using (var memStream = new MemoryStream())
            {
                engineConfigSerializer.Serialize(memStream, configuration);
                bytes = memStream.ToArray();
            }
            #endregion

            #region InstrumentationEngine Path
            string enginePath;
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                enginePath = isTargetProcess32Bit ?
                    Path.Combine(rootDirectory, Instrumentation32FolderName, InstrumentationEngineX86WindowsName) :
                    Path.Combine(rootDirectory, Instrumentation64FolderName, InstrumentationEngineX64WindowsName);
            }
            else if (!isTargetProcess32Bit)
            {
                enginePath = Path.Combine(rootDirectory, Instrumentation64FolderName, InstrumentationEngineX64LinuxName);
            }
            else
            {
                WriteError("Only the 64 bit engine is supported on non-Windows platforms.");
                return ExitCodeFailure;
            }

            if (!File.Exists(enginePath))
            {
                WriteError(Invariant($"Engine path '{enginePath}' does not exist."));
                return ExitCodeFailure;
            }

            WriteMessage($"Engine path: {enginePath}");

            #endregion

            #region Profiler Attach
            // CONSIDER: Should the engine be validated before attempting to attach it to the CLR?
            // For example, could check the signature on the assembly (which wouldn't work on Linux).
            DiagnosticsClient client = new DiagnosticsClient(processId);
            try
            {
                client.AttachProfiler(TimeSpan.FromSeconds(10), InstrumentationEngineClsid, enginePath, bytes);
            }
            catch (ServerErrorException ex)
            {
                WriteError(Invariant($"Could not attach engine to process: '{ex.Message}'."));
                return ExitCodeFailure;
            }
            #endregion

            WriteMessage($"Successfully attached the engine.");
            return ExitCodeSuccess;
        }