private static ProcessModuleCollection GetModules(int processId, bool firstModuleOnly)
        {
            // preserving Everett behavior.
            if (processId == SystemProcessID || processId == IdleProcessID)
            {
                // system process and idle process doesn't have any modules
                throw new Win32Exception(HResults.E_FAIL, SR.EnumProcessModuleFailed);
            }

            SafeProcessHandle processHandle = SafeProcessHandle.InvalidHandle;

            try
            {
                processHandle = ProcessManager.OpenProcess(processId, Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION | Interop.Advapi32.ProcessOptions.PROCESS_VM_READ, true);

                bool succeeded = Interop.Kernel32.EnumProcessModules(processHandle, null, 0, out int needed);

                // The API we need to use to enumerate process modules differs on two factors:
                //   1) If our process is running in WOW64.
                //   2) The bitness of the process we wish to introspect.
                //
                // If we are not running in WOW64 or we ARE in WOW64 but want to inspect a 32 bit process
                // we can call psapi!EnumProcessModules.
                //
                // If we are running in WOW64 and we want to inspect the modules of a 64 bit process then
                // psapi!EnumProcessModules will return false with ERROR_PARTIAL_COPY (299).  In this case we can't
                // do the enumeration at all.  So we'll detect this case and bail out.
                if (!succeeded)
                {
                    if (!Interop.Kernel32.IsWow64Process(Interop.Kernel32.GetCurrentProcess(), out bool sourceProcessIsWow64))
                    {
                        throw new Win32Exception();
                    }

                    if (!Interop.Kernel32.IsWow64Process(processHandle, out bool targetProcessIsWow64))
                    {
                        throw new Win32Exception();
                    }

                    if (sourceProcessIsWow64 && !targetProcessIsWow64)
                    {
                        // Wow64 isn't going to allow this to happen, the best we can do is give a descriptive error to the user.
                        throw new Win32Exception(Interop.Errors.ERROR_PARTIAL_COPY, SR.EnumProcessModuleFailedDueToWow);
                    }

                    EnumProcessModulesUntilSuccess(processHandle, null, 0, out needed);
                }

                int      modulesCount  = needed / IntPtr.Size;
                IntPtr[] moduleHandles = new IntPtr[modulesCount];
                while (true)
                {
                    int size = needed;
                    EnumProcessModulesUntilSuccess(processHandle, moduleHandles, size, out needed);
                    if (size == needed)
                    {
                        break;
                    }

                    if (needed > size && needed / IntPtr.Size > modulesCount)
                    {
                        modulesCount  = needed / IntPtr.Size;
                        moduleHandles = new IntPtr[modulesCount];
                    }
                }

                var modules = new ProcessModuleCollection(firstModuleOnly ? 1 : modulesCount);

                const int StartLength =
#if DEBUG
                    1; // in debug, validate ArrayPool growth
#else
                    Interop.Kernel32.MAX_PATH;
#endif
                char[]? chars = ArrayPool <char> .Shared.Rent(StartLength);

                try
                {
                    for (int i = 0; i < modulesCount; i++)
                    {
                        if (i > 0)
                        {
                            // If the user is only interested in the main module, break now.
                            // This avoid some waste of time. In addition, if the application unloads a DLL
                            // we will not get an exception.
                            if (firstModuleOnly)
                            {
                                break;
                            }
                        }

                        IntPtr moduleHandle = moduleHandles[i];
                        Interop.Kernel32.NtModuleInfo ntModuleInfo;
                        if (!Interop.Kernel32.GetModuleInformation(processHandle, moduleHandle, out ntModuleInfo))
                        {
                            HandleLastWin32Error();
                            continue;
                        }

                        var module = new ProcessModule()
                        {
                            ModuleMemorySize  = ntModuleInfo.SizeOfImage,
                            EntryPointAddress = ntModuleInfo.EntryPoint,
                            BaseAddress       = ntModuleInfo.BaseOfDll
                        };

                        int length = 0;
                        while ((length = Interop.Kernel32.GetModuleBaseName(processHandle, moduleHandle, chars, chars.Length)) == chars.Length)
                        {
                            char[] toReturn = chars;
                            chars = ArrayPool <char> .Shared.Rent(length * 2);

                            ArrayPool <char> .Shared.Return(toReturn);
                        }

                        if (length == 0)
                        {
                            module.Dispose();
                            HandleLastWin32Error();
                            continue;
                        }

                        module.ModuleName = new string(chars, 0, length);

                        while ((length = Interop.Kernel32.GetModuleFileNameEx(processHandle, moduleHandle, chars, chars.Length)) == chars.Length)
                        {
                            char[] toReturn = chars;
                            chars = ArrayPool <char> .Shared.Rent(length * 2);

                            ArrayPool <char> .Shared.Return(toReturn);
                        }

                        if (length == 0)
                        {
                            module.Dispose();
                            HandleLastWin32Error();
                            continue;
                        }

                        const string        NtPathPrefix = @"\\?\";
                        ReadOnlySpan <char> charsSpan    = chars.AsSpan(0, length);
                        if (charsSpan.StartsWith(NtPathPrefix))
                        {
                            charsSpan = charsSpan.Slice(NtPathPrefix.Length);
                        }
                        module.FileName = charsSpan.ToString();

                        modules.Add(module);
                    }
                }
                finally
                {
                    ArrayPool <char> .Shared.Return(chars);
                }

                return(modules);
            }
            finally
            {
                if (!processHandle.IsInvalid)
                {
                    processHandle.Dispose();
                }
            }
        }