This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

SourceLink and external sources

Description

I published a very similar version of the following article in the DevOps Wiki at <a href="https://dev.azure.com/ustertechnologies">Uster Technologies AG</a>. Since nearly all of that post is general knowledge that I would have been happy to find before I started my investigations, I'm sharing it here. <h>Overview</h> When we think about navigating or debugging our code, we usually focus on the code we've written ourselves—local sources in our file system. IDEs have classically focused on being able to debug and navigate this code. More and more, though, we're also interested in navigating and debugging our versioned and compiled dependencies: <ul> Internal NuGet packages External NuGet packages The Base Class Library (BCL) </ul> Most of these are available as source code. We would ideally like to be able to navigate and debug that code just as easily as we can our own. The following sections define file types and terminology, and then explain how these concepts apply to debugging and navigation for external sources. You can also just jump to the sections on <a href="#producing-packages">producing</a> or <a href="#consuming-packages">consuming</a> packages (especially as relates to <a href="#authentication-fails">authentication for private sources</a>). <h>Diagram</h> The following diagram provides an overview of the process of obtaining external packages, along with their symbols and source files. It looks quite complicated, but accommodates the flexibility required by various stakeholders. <img src="{att_link}symbol_and_source_acquisition_diagram.png" href="{att_link}symbol_and_source_acquisition_diagram.png" align="none" caption="Symbol and source acquisition diagram" scale="35%"> <h>File types</h> There are several types of files associated with debugging and navigation: <dl dt_class="field"> <c>DLL</c> The executable code generally only includes executable code (instructions). It can include debugging information, but this is relatively rare. <c>PDB</c> The "program database" contains the symbol information for the executable code, which allows the debugger to map instructions back to the source code.<br><br>This includes aliases (symbols), file positions, and any other necessary mappings, including references to source code files.<br><br>Although invented by Microsoft, the PDB is an industry-standard, platform-independent, and language-independent format. See, e.g., <a href="https://llvm.org/docs/PDB/index.html">LLVM's The PDB File Format documentation</a> or <a href="https://en.wikipedia.org/wiki/Program_database">the Wikipedia entry</a>. <c>XML</c> The optionally generated XML documentation. Some IDEs can use this file to enhance the developer experience while browsing the source code. <c>*.cs</c> The original source code </dl> <h>Design Considerations</h> It's reasonable to ask why this process is so complex. <h level="3">Why can't the <c>nupkg</c> just include the <c>PDB</c> and the <c>*.cs</c> files?</h> The system was designed for use cases where most sources were closed. That has changed, but the system still reflects the original design choices. The PDB files can also add about 30% to the size of the package. The original use cases preferred to avoid using 30% more space for package downloads that didn't need the debugging information. <h level="3">Why aren't sources included in the PDB?</h> Again, historically, the use cases were for providing improved stack traces with symbols, but not to provide access to closed sources. Even if the sources are partially open, access may be restricted to only some users of the packages or symbols. Having the IDE request the sources separately allows an additional authorization phase. <h level="3">What about open-source?</h> The defaults still reflect the original use cases, which actually represent fewer and fewer packages as time goes on. These answers aren't particularly satisfying if your use case happens to be "make a package that has symbols for excellent stack traces and sources for excellent debugging". At least we now have IDEs that know how to work with this system and there is a lot of automation for <a href="#producing-packages">producing packages</a> with the desired symbol and source-code support. <h>Terminology</h> <h level="3">Debugging</h> A developer <i>debugs</i> source code by interrupting execution of a program—either manually or by setting <i>breakpoints</i>—and then <i>stepping</i> through the instructions, examining the contents of symbols (variables) to investigate the runtime behavior and operation of the system. The debugger uses the <c>PDB</c> to allow <i>source-level debugging</i>, i.e. debugging in the original source code. While debugging in "lower" formats is possible, it's not nearly as reliable as being able to step through the code in the original source code, using the original symbols. How does the debugger obtain the <c>PDB</c> for a given <c>DLL</c>? <ul> First it searches in the same directory. This is by far the most common location where symbols will be found. Next, it searches on all known "symbol servers" in the order that they're declared. All <c>DLLs</c> and <c>PDBs</c> have unique identifiers that make it possible to request and download the correct file. </ul> Once the debugger has the PDB, it has everything it needs—except the source code. <h level="3">Local sources</h> If the PDB was generated locally, then it most likely references the source files that are still in the same locations in the file system as when it was built. In that case, the debugger easily finds the source files because they're just at the paths that are directly referenced by the <c>PDB</c>. If the PDB was not generated locally or the source-code paths do not match, then there are other tricks to find the source files. <i>Visual Studio</i> allows you to set "Directories containing source code" for the "Debug Source Files" <img src="{att_link}solution_debugging_options.png" href="{att_link}solution_debugging_options.png" align="none" caption="Solution Debugging Options" scale="75%"> <h level="3">External sources</h> If the sources aren't available locally, e.g., for a <i>NuGet</i> package, then there is a system called <a href="https://github.com/dotnet/sourcelink">SourceLink</a> that is extremely well-supported in the .NET world that makes it possible to easily download the source files that generated a <c>DLL</c> and that are referenced by its <c>PDB</c>. Things to be aware of: <ul> The package must have been built with <i>SourceLink</i> enabled (see <a href="#producing-packages">producing packages</a>). The sources must be available for download in a known format and structure (e.g., <i>Azure Git Repos</i>). The IDE must know how to download, cache, and use the sources for debugging or navigation. </ul> If the package does not support <i>SourceLink</i>, but the sources are available, then you can download the sources locally and use the solution-level mapping above to tell the debugger where the source files are. You can also just point the debugger to the top-level folder when it asks for the file's location, in which case the debugger makes the entry for you. <h level="3" id="navigation">Navigation</h> A developer <i>navigates</i> by requesting the source code for a symbol. For example, if the declared type of a variable in an open source file is the class <c>Setting</c>, then the developer can ask the IDE to show the source of <c>Setting</c> by <kbd>Ctrl</kbd> + clicking, by pressing <kbd>F12</kbd> in <i>Visual Studio</i>, or by pressing <kbd>Ctrl</kbd> + <kbd>B</kbd> in <i>Rider</i>. As with debugging, navigating local sources is straightforward, since the sources are in the local file system. For symbols in NuGet packages, the IDE has to be clever enough to download, cache, and use the sources. <i>Visual Studio</i> on its own does not support navigating to external sources via <a href="#sourcelink">SourceLink</a>. Instead, it always decompiles external sources, as shown in the example below. <img src="{att_link}visual_studio_decompiles_external_sources_on_navigation.png" href="{att_link}visual_studio_decompiles_external_sources_on_navigation.png" align="none" caption="Visual Studio decompiles external sources on navigation" scale="50%"> If you have <i>ReSharper</i> installed, then the default setting is to try as hard as possible to avoid showing a decompiled version. You can also add "Folder Substitutions" in the "Advanced Symbol options..." for navigating to "External Sources". The option does not seem to be available in Rider. <img src="{att_link}resharper_external_source_options.png" href="{att_link}resharper_external_source_options.png" align="none" caption="ReSharper External Source Options" scale="75%"> <h level="3" id="sourcelink">SourceLink</h> <i>SourceLink</i> is a system that provides source files for external sources like NuGet packages for debugging or navigation. In order for this to work, you must be able to provide external sources or the client is not properly configured for debugging. See <a href="#troubleshooting">below for troubleshooting information</a>, especially as relates to <a href="#authentication-fails">authentication</a> for packages and source code pulled from authenticated locations. <h level="3">Decompiled code</h> A <i>decompiled</i> version of the source code is a reconstruction of the original source from the instructions and information in the <c>DLL</c> and <c>PDB</c>. When sources cannot be located for a given symbol, <i>Visual Studio</i>, <i>ReSharper</i>, and <i>Rider</i> will produce a decompiled version as a fallback. This is often good enough to be able to read the code reasonably well, but it leaves certain common constructs in their "lowered" format. E.g., calls to extension methods appear as static-method calls rather than as targeted on the first parameter. This can make debugging difficult, as the instructions don't match the mapping. <i>Rider</i> has support for patching the PDB on-the-fly to allow more comfortable debugging of decompiled sources. This is, however, a fallback solution for external packages over which you have no control. It's best to configure your packages to publish with symbols and sources available to IDEs that support them, as shown in the next section. <h id="producing-packages">Producing packages</h> The documentation to <a href="https://learn.microsoft.com/en-us/visualstudio/debugger/how-to-improve-diagnostics-debugging-with-sourcelink?view=vs-2022">Enable debugging and diagnostics with Source Link</a> is thorough and tells you all you need to know about all of the options. If you're working with Azure DevOps Services, you should include the following package reference: <code> <itemgroup> <packagereference Include="Microsoft.SourceLink.AzureRepos.Git" Version="8.0.0" PrivateAssets="All"> </itemgroup> </code> With this, you're all set. The package is published to the <i>Azure Artifacts</i>, with a corresponding <c>snupkg</c> available on the Azure symbol server and sources available via the repository URL (subject to authorization; see <a href="#authentication-fails">below</a> for troubleshooting). <h level="3">Additional Properties</h> You can set a few <i>optional</i> properties, detailed below. Most projects won't need to set these, but they are included to spare you the research if you see them in code examples, either in your institution's code or online. As noted, the only line you need is the package reference shown above. <dl> <a href="https://github.com/dotnet/sourcelink/blob/main/docs/README.md#embedallsources">EmbedAllSources</a> Embeds all project source files into the generated PDB <a href="https://github.com/dotnet/sourcelink/blob/main/docs/README.md#embeduntrackedsources">EmbedUntrackedSources</a> Embeds anything that's not included in source control (kind of unclear what they're talking about here, though); included in <c>IncludeSymbols</c> <a href="https://github.com/dotnet/sourcelink/blob/main/docs/README.md#publishrepositoryurl">PublishRepositoryUrl</a> Ensures that the URL of the repository supplied by the CI server or retrieved from source control manager is available in the package information. This is off by default to prevent discovery of private URLS, but it doesn't really matter for packages published from private sources, as they are protected by Azure DevOps (or whatever) authorization. <a href="https://learn.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg">IncludeSymbols</a> Indicates that the PDB should be generated and included either with the package (if <c>DebugType</c> is set to <c>embedded</c>) or in a separate symbol package (if <c>SymbolPackageFormat</c> is set to <c>snupkg</c>). This is implied when the NuGet package <a href="https://www.nuget.org/packages/Microsoft.SourceLink.AzureRepos.Git">Microsoft.SourceLink.AzureRepos.Git</a> is included, as shown below. <a href="https://learn.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg">SymbolPackageFormat</a> Indicates which package format to use. This is set to <c>snupkg</c> when the NuGet package <a href="https://www.nuget.org/packages/Microsoft.SourceLink.AzureRepos.Git">Microsoft.SourceLink.AzureRepos.Git</a> is included, as shown below. </dl> See the <a href="https://github.com/dotnet/sourcelink?tab=readme-ov-file#using-source-link-in-net-projects">SourceLink documentation</a> for more details. Among other details, they also note that projects that target .NET 8 no longer need to include this support explicitly because Azure Repos are supported by default, as detailed in the <a href="https://github.com/dotnet/sourcelink">readme for the SourceLink project</a>. <bq>If your project uses .NET SDK 8+ and is hosted by the above providers (GitHub, Azure Repos, GitLab, BitBucket) it does not need to reference any Source Link packages or set any build properties.</bq> <h level="3">Conditional packaging</h> You can also include the packaging conditionally in the <c>Directory.Build.Targets</c>, as shown below. <code> <itemgroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <packagereference Include="Microsoft.SourceLink.AzureRepos.Git" Version="8.0.0" PrivateAssets="All"> </itemgroup> </code> See the appendix for <a href="#direction-build-props-and-directory-build-targets"><c>Directory.Build.Props</c> and <c>Directory.Build.Targets</c></a> for more information about which variables and directives are respected in which file. <h id="consuming-packages">Consuming packages</h> <h level="3">For debugging</h> If a package has SourceLink enabled and you have access to the online repository from which it was built, then to seamlessly debug into that source code, ensure the following: <ul> <a href="#disable-_just-my-code_">Disable Just My Code</a> <a href="#is-it-available%3F">Check that the PDB is available</a> </ul> <h level="3">For navigation</h> As <a href="#navigation">noted above</a>, Visual Studio doesn't support navigating via <a href="#sourcelink">SourceLink</a>. To browse external sources with <i>JetBrains</i> tools, ensure the following: <ul> <a href="#is-it-available%3F">Check that the PDB is available</a> <a href="#authentication-fails">Set up Authentication</a> </ul> <h level="3" id="troubleshooting">Troubleshooting</h> <h level="4">Symbols not loaded</h> <h level="5" id="disable-_just-my-code_">Disable <i>Just My Code</i></h> Once you're sure that the <a href="/Documentation/Tools/NuGet/NuGet-FAQ#why-doesn%27t-sourcelink-work%3F">package supports SourceLink</a>, then you should also make sure that the <a href="https://learn.microsoft.com/en-us/visualstudio/debugger/just-my-code?view=vs-2022">Just My Code</a> setting is <i>disabled</i>. When <i>Just My Code</i> is enabled, the debugger skips over any code that doesn't correspond to source code in one of the local projects. <h level="5" id="is-it-available%3F">Is it available?</h> <ul> Does the package you've downloaded actually include symbols (a <c>.pdb</c> file next to the <c>.dll</c> file)? If the <c>PDB</c> is not included with the package, is it available on a <a href="https://learn.microsoft.com/en-us/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2022#where-the-debugger-looks-for-symbols">Symbol Server</a>? If it is included, is it being copied into the output folder with the <c>DLL</c>? </ul> If it's available in the package, but is not being copied to the output folder, then if you're using .NET 7.0 SDK or higher, you can use the build property named <a href="https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#copydebugsymbolfilesfrompackages"> CopyDebugSymbolFilesFromPackages</a>. <code> <propertygroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <copydebugsymbolfilesfrompackages>true</copydebugsymbolfilesfrompackages> </propertygroup> </code> <h level="5">Manually load the module</h> Verify that the symbols for the module you're trying to debug have been loaded. If they aren't loaded, you can try to <a href="https://learn.microsoft.com/en-us/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2022#load-symbols-while-debugging">load symbols while debugging</a>. For more details and a screenshot, see <a href="https://learn.microsoft.com/en-us/visualstudio/debugger/just-my-code?view=vs-2022#just-my-code-debugging">Just My Code debugging</a>. <h level="4">Decompiling rather than downloading</h> If you're trying to navigate in code, but ReSharper or Rider keeps decompiling instead of getting the sources from <a href="#sourcelink">SourceLink</a>, then check your <a href="https://www.jetbrains.com/help/resharper/Reference__Options__Tools__External_Sources.html">External Sources</a> settings in ReSharper or Rider. Verify that the tool is configured to check for external sources <i>before</i> it tries decompiling. If the IDE is having trouble <a href="#authentication-fails">authenticating</a>, then you will usually see a decompiled version instead. Sometimes the code is so close to the original that it's hard to tell; scroll to the top to see if it includes the "decompiled by JetBrains..." header. Once the IDE has decompiled a source file, it will continue to use this cached copy until you close the tab, or sometimes you have to close and re-open the project. If you're troubleshooting your way through this setup, then you can temporarily disable decompilation as a fallback, which avoids producing the unwanted source-code variant in the first place. <h level="4" id="authentication-fails">Authentication fails</h> <i>Visual Studio</i> uses the authentication associated with the logged-in user that you use to enable the IDE. This can be in a weird state if you've recently changed your password or your authentication token is stale or in a non-refreshable state. Try logging out and back in. <i>JetBrains</i> tools (<i>Rider</i>, <i>ReSharper</i>, <i>DotPeek</i>, etc.), on the other hand, need to be given a <i>token</i>. <h level="5">Configure from the notification</h> If the tool shows a notification indicating that authentication has failed, then do the following: <ul> Click <c>Configure</c> on the notification to show a dialog In the resulting dialog, set:<ul> <i>User name</i> to your Azure login, e.g., <c>john.doe@example.com</c> <i>Token</i> to an <a href="https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows">Azure PAT</a> (click for instructions on how to create one) Press the <c>Test</c> button to verify that it works (you should see <c>OK 200</c>) Press <c>Ok</c> to save the credentials</ul> </ul> <h level="5">Bug in <i>JetBrains</i> tools</h> However, there is a bug whereby <i>JetBrains</i> tools fail to show a notification or offer a way to enter credentials.<fn> That's going to look something like this: <img src="{att_link}download_never_completes.png" href="{att_link}download_never_completes.png" align="none" caption="Download never completes" scale="75%"> It claims that it can download the source, but it never completes. You have to cancel the dialog. If you then look at the ReSharper Output, then you'll see something like this: <img src="{att_link}non-ok_http_status_code.png" href="{att_link}non-ok_http_status_code.png" align="none" caption="Non-OK HTTP status code" scale="75%"> The relevant text is at the end of the third line, which indicates that the request for the source file returned a "Non-OK HTTP status code". <code> PdbNavigator: Searching for 'Example.Core.AppConfig.AppConfigKeyAttribute' type sources in C:\Users\john.doe\.nuget\packages\example.core.appconfig\4.1.0\lib\netstandard2.0\Example.Core.AppConfig.pdb PdbNavigator: File names (1) are inferred for type Example.Core.AppConfig.AppConfigKeyAttribute PdbNavigator: Downloader: https://dev.azure.com/example/example.Core/_apis/git/repositories/Example.Core.LabInstruments/items?api-version=1.0&versionType=commit&version=8b34c2aa672facd47e835c27152f695fa796a408&path=/Example.Core/DotNetStandard/Example.Core.AppConfig/AppConfigKeyAttribute.cs -> Non-OK HTTP status code </code> <h level="5">Configure from the <i>Credentials Manager</i></h> The most reliable way to fix this is to create the credentials in the <i>Credential Manager</i>. Be aware that you will need to <a href="https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows">create an Azure PAT (personal access token)</a>. <ul> Open <i>Credentials Manager</i> Switch to <c>Windows Credentials</c> Scroll until you see <c>JetBrains SourceLink https://dev.azure.com/exampleOrganization</c> </ul> <img src="{att_link}jetbrains_entry_in_credentials_manager.png" href="{att_link}jetbrains_entry_in_credentials_manager.png" align="none" caption="JetBrains entry in Credentials Manager" scale="75%"> If you don't have this entry, then that's the problem. If you have it, but you still can't get the sources, then edit the entry to have valid credentials. To create or edit the record, do the following from the <i>Credentials Manager</i>: <ul> <div>Press <c>Add a generic credential</c> <img src="{att_link}create_generic_credentials.png" href="{att_link}create_generic_credentials.png" align="none" scale="75%"></div> In the resulting form, set:<ul> <i>Internet or network address</i> to <c>JetBrains SourceLink https://dev.azure.com/exampleOrganization</c> <i>User name</i> to your Azure login, e.g., <c>john.doe@example.com</c> <i>Password</i> to an <a href="https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows">Azure PAT</a> (click for instructions on how to create one)</ul> </ul> <info> 💡 There is no need to restart the <i>JetBrains</i> tools. You will immediately be able to load sources from <a href="#sourcelink">SourceLink</a> once valid credentials exist.</info> <h>Alternative: referencing projects, not packages</h> As you can see above, although publishing a package is relatively straightforward, there are quite a few stumbling blocks on the way to consuming the package for navigation and debugging. Once you have everything set up and working, it's great, but ... there is still one other drawback. You can't edit the code for packages. This is not optimal. Optimally, we'd like to <i>quickly</i> verify that change to an upstream code would address an issue in downstream code without having to generate new packages. It would be great to just edit the upstream code as if it were part of your downstream solution until you're sure that the change would address your downstream issue. At that point, you can copy the changes back to the upstream solution (where the dependency is produced), add tests, and produce a new version, being pretty certain that the change is effective. The shortest possible developer-feedback loop with code in external packages is: <ul> Determine that you need to make a change to code in an external package Open the solution for that package Make the change Build the assembly Drop it into your output folder (along with the <c>PDB</c>) Build and run your solution with the updated code </ul> If your package has dependencies or your change in the external package's solution touches multiple packages, then you can do the following: <ul> Build packages for the solution locally Set up a NuGet source that points to that folder Update to the newer versions of the packages and restore from that source Build and run your solution with the updated code </ul> If it get too complicated to do locally, then you can always commit, push, and have the CI generate new versions of your packages (hopefully with a prerelease version, e.g., <c>3.2.4-preview2</c>) The solutions outlined above have a reasonable turnaround time, but sometimes you want to pretend that the external packages are just internal projects instead. This basically entails: <ul> Downloading the project or projects corresponding to the packages that you want to be able to edit Including those projects into your solution Replacing the external package-references with project references </ul> At that point, you can edit, debug, and navigate the code as if it were your own. See the "Project Munging with Tools & PowerShell" section of <a href="https://blog.inedo.com/nuget/how-to-debug-nuget-packages-the-painless-way/">How to Debug NuGet Packages with Symbols and Source Link Painlessly</a> for a PowerShell script that can help you automate part of this. <h id="directory-build-props-and-directory-build-targets">Directory.Build.Props and Directory.Build.Targets</h> MSBuild supports including common configuration in project files. While earlier versions required all configuration to be included explicitly, modern versions include configuration files with special names automatically, greatly simplifying common configuration <i>and</i> reducing clutter in project files. <h level="3">Mechanics</h> If the file is named <c>Directory.Build.Props</c> or <c>Directory.Build.Targets</c>, it is picked up automatically and included for all projects in that folder or any subfolder. If you use a different name, then you have to explicitly reference that file from a project or from another <c>*.props</c> or <c>*.targets</c> file. If you choose your own name, you don't have to use the <c>Build.Properties</c> or <c>Build.Targets</c> convention, but it's strongly recommended, to avoid confusion. <h level="4">Directory.Build.Props</h> You can use a <a href="https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022#directorybuildprops-and-directorybuildtargets">Directory.Build.Properties</a> file to include settings for all projects in a folder or set of subfolders. For example, the following package reference can and should be included in <c>Directory.Build.Props</c>: <code> <packagereference Include="Microsoft.SourceLink.AzureRepos.Git" Version="8.0.0" PrivateAssets="All"> </code> <h level="4">Directory.Build.Targets</h> If you want to include settings conditionally based on build configuration (e.g., <c>Configuration</c> or <c>Platform</c>), then you'll have to use the <a href="https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022#directorybuildprops-and-directorybuildtargets">Directory.Build.Targets</a> file, which has access to those variables. <code> <itemgroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <packagereference Include="Microsoft.SourceLink.AzureRepos.Git" Version="8.0.0" PrivateAssets="All"> </itemgroup> </code> <h level="3">Rules-of-thumb</h> <ul> Once you're using the SDK-style format for project files<fn>, you can aggressively consolidate common settings in a <c>Directory.Build.Props</c> file at the root of the solution. If you have groups of projects to which different settings should be applied, then consider splitting those off into corresponding subfolders (e.g., "Tests") so that you can apply those common settings with a configuration file that applies only to that folder. If you can't or don't want to move projects into subfolders, then you can create a custom `props` file and manually include it in the project files that need it. </ul> <hr> <ft>After having figured out a workaround, I felt well-equipped enough to file a bug with JetBrains: <a href="https://youtrack.jetbrains.com/issue/RSRP-495517/ReSharper-does-not-ask-for-authentication-when-browsing-to-source-from-symbol-files-in-assembly-explorer">ReSharper does not ask for authentication when browsing to source from symbol files in assembly explorer</a>. After a couple of days, the responsible developer changed the status from "triage" to "open" and he <i>linked a two-year-old bug report to it</i>: <a href="https://youtrack.jetbrains.com/issue/RIDER-61280/Pdb-files-cannot-be-downloaded-from-Azure-DevOps-Symbol-Server">Pdb files cannot be downloaded from Azure DevOps Symbol Server</a>. Would you like to guess who wrote that bug report? Yours truly. I knew I'd had trouble in this area before, but I'd completely forgotten that I'd reported the bug in such detail. It's still open. Maybe they'll finally address it.</ft> <ft>This also works for the older project format, but it's hard to keep <i>Visual Studio</i> from repopulating properties in that format. You can use the SDK-style format for nearly all projects these days. The conversion is worth it.</ft>