diff --git a/ILSpy/Commands/FileCommands.cs b/ILSpy/Commands/FileCommands.cs
index d7e64420d..f683dcf6c 100644
--- a/ILSpy/Commands/FileCommands.cs
+++ b/ILSpy/Commands/FileCommands.cs
@@ -129,12 +129,8 @@ namespace ILSpy.Commands
// DEBUG-only DisassembleAllCommand also moved to DecompileAllCommand.cs to keep all
// three parallel-decompile/disassemble stress-test commands in one place.
- [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)]
- [Shared]
- sealed class Pdb2XmlCommand : SimpleCommand
- {
- public override void Execute(object? parameter) => NotImplementedDialog.Show(Resources.DEBUGDumpPDBAsXML);
- }
+ // DEBUG-only Pdb2XmlCommand moved to its own file with `#if DEBUG && WINDOWS` gating.
+ // On non-Windows or non-Debug builds the entry simply isn't compiled.
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._RemoveAssembliesWithLoadErrors), MenuCategory = nameof(Resources.Remove), MenuOrder = 2.6)]
[Shared]
diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs
new file mode 100644
index 000000000..090c2ae58
--- /dev/null
+++ b/ILSpy/Commands/Pdb2XmlCommand.cs
@@ -0,0 +1,115 @@
+// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+#if DEBUG && WINDOWS
+using System.Collections.Generic;
+using System.Composition;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+using ICSharpCode.ILSpy.Properties;
+
+using ILSpy.AssemblyTree;
+using ILSpy.Docking;
+using ILSpy.TextView;
+using ILSpy.TreeNodes;
+
+using Microsoft.DiaSymReader.Tools;
+
+namespace ILSpy.Commands
+{
+ ///
+ /// DEBUG-only main-menu entry: Dump every selected assembly's PDB as XML in the active
+ /// decompiler tab via . Windows-only because
+ /// Microsoft.DiaSymReader uses native COM interop. WPF gates this command
+ /// identically (#if DEBUG && WINDOWS).
+ ///
+ [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.7)]
+ [Shared]
+ sealed class Pdb2XmlCommand : SimpleCommand
+ {
+ readonly AssemblyTreeModel assemblyTreeModel;
+ readonly DockWorkspace dockWorkspace;
+
+ [ImportingConstructor]
+ public Pdb2XmlCommand(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace)
+ {
+ this.assemblyTreeModel = assemblyTreeModel;
+ this.dockWorkspace = dockWorkspace;
+ }
+
+ public override bool CanExecute(object? parameter)
+ {
+ var selected = assemblyTreeModel.SelectedItems;
+ return selected?.Count > 0
+ && selected.All(n => n is AssemblyTreeNode asm && !asm.LoadedAssembly.HasLoadError);
+ }
+
+ public override void Execute(object? parameter)
+ {
+ var nodes = assemblyTreeModel.SelectedItems.OfType().ToArray();
+ if (nodes.Length == 0)
+ return;
+ _ = ExecuteAsync(nodes);
+ }
+
+ async Task ExecuteAsync(AssemblyTreeNode[] nodes)
+ {
+ try
+ {
+ var options = PdbToXmlOptions.IncludeEmbeddedSources
+ | PdbToXmlOptions.IncludeMethodSpans
+ | PdbToXmlOptions.IncludeTokens;
+ var output = await dockWorkspace.RunWithCancellation(token => Task.Run(() => {
+ var output = new AvaloniaEditTextOutput { Title = "PDB as XML", SyntaxExtensionOverride = ".xml" };
+ var writer = new TextOutputWriter(output);
+ foreach (var node in nodes)
+ {
+ var pdbFileName = Path.ChangeExtension(node.LoadedAssembly.FileName, ".pdb");
+ if (!File.Exists(pdbFileName))
+ continue;
+ using var pdbStream = File.OpenRead(pdbFileName);
+ using var peStream = File.OpenRead(node.LoadedAssembly.FileName);
+ PdbToXmlConverter.ToXml(writer, pdbStream, peStream, options);
+ }
+ return output;
+ }, token), "Dumping PDB as XML…");
+ dockWorkspace.ShowText(output);
+ }
+ catch (System.OperationCanceledException) { }
+ }
+ }
+
+ ///
+ /// Adapter that lets 's TextWriter API write into our
+ /// sink.
+ ///
+ internal sealed class TextOutputWriter(ICSharpCode.Decompiler.ITextOutput output) : System.IO.TextWriter
+ {
+ public override System.Text.Encoding Encoding => System.Text.Encoding.UTF8;
+ public override void Write(char value) => output.Write(value);
+ public override void Write(string? value)
+ {
+ if (value != null)
+ output.Write(value);
+ }
+ public override void WriteLine() => output.WriteLine();
+ }
+}
+#endif
diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index 76b1878ee..6dd2031b2 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -77,6 +77,18 @@
+
+
+
+
+
+
+