Skip to content

Instantly share code, notes, and snippets.

@atruskie
Last active July 25, 2024 09:56
Show Gist options
  • Save atruskie/3813175 to your computer and use it in GitHub Desktop.
Save atruskie/3813175 to your computer and use it in GitHub Desktop.
A class for programmatically attaching Visual Studio to debug a process. It can be used to automatically attach Visual Studio to your process. It can dynamically attach any solution to any process.
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AutoAttachVs.cs" company="QutEcoacoustics">
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
// </copyright>
// <summary>
// Example taken from this gist.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
#if DEBUG
namespace Acoustics.Shared.Debugging
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using EnvDTE;
using DTEProcess = EnvDTE.Process;
using Process = System.Diagnostics.Process;
/// <summary>
/// Example taken from <a href="https://gist.github.com/3813175">this gist</a>.
/// </summary>
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Reviewed. Suppression is OK here.", Scope = "class")]
public static class VisualStudioAttacher
{
[DllImport("ole32.dll")]
public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
[DllImport("ole32.dll")]
public static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetFocus(IntPtr hWnd);
public static string GetSolutionForVisualStudio(Process visualStudioProcess)
{
_DTE visualStudioInstance;
if (TryGetVsInstance(visualStudioProcess.Id, out visualStudioInstance))
{
try
{
return visualStudioInstance.Solution.FullName;
}
catch (Exception)
{
}
}
return null;
}
public static Process GetAttachedVisualStudio(Process applicationProcess)
{
IEnumerable<Process> visualStudios = GetVisualStudioProcesses();
foreach (Process visualStudio in visualStudios)
{
_DTE visualStudioInstance;
if (TryGetVsInstance(visualStudio.Id, out visualStudioInstance))
{
try
{
foreach (Process debuggedProcess in visualStudioInstance.Debugger.DebuggedProcesses)
{
if (debuggedProcess.Id == applicationProcess.Id)
{
return debuggedProcess;
}
}
}
catch (Exception)
{
}
}
}
return null;
}
/// <summary>
/// The method to use to attach visual studio to a specified process.
/// </summary>
/// <param name="visualStudioProcess">
/// The visual studio process to attach to.
/// </param>
/// <param name="applicationProcess">
/// The application process that needs to be debugged.
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown when the application process is null.
/// </exception>
public static void AttachVisualStudioToProcess(Process visualStudioProcess, Process applicationProcess)
{
_DTE visualStudioInstance;
if (TryGetVsInstance(visualStudioProcess.Id, out visualStudioInstance))
{
// Find the process you want the VS instance to attach to...
DTEProcess processToAttachTo =
visualStudioInstance.Debugger.LocalProcesses.Cast<DTEProcess>()
.FirstOrDefault(process => process.ProcessID == applicationProcess.Id);
// Attach to the process.
if (processToAttachTo != null)
{
processToAttachTo.Attach();
ShowWindow((int)visualStudioProcess.MainWindowHandle, 3);
SetForegroundWindow(visualStudioProcess.MainWindowHandle);
}
else
{
throw new InvalidOperationException(
"Visual Studio process cannot find specified application '" + applicationProcess.Id + "'");
}
}
}
/// <summary>
/// The get visual studio for solutions.
/// </summary>
/// <param name="solutionNames">
/// The solution names.
/// </param>
/// <returns>
/// The <see cref="Process"/>.
/// </returns>
public static Process GetVisualStudioForSolutions(List<string> solutionNames)
{
foreach (string solution in solutionNames)
{
Process visualStudioForSolution = GetVisualStudioForSolution(solution);
if (visualStudioForSolution != null)
{
return visualStudioForSolution;
}
}
return null;
}
/// <summary>
/// The get visual studio process that is running and has the specified solution loaded.
/// </summary>
/// <param name="solutionName">
/// The solution name to look for.
/// </param>
/// <returns>
/// The visual studio <see cref="Process"/> with the specified solution name.
/// </returns>
public static Process GetVisualStudioForSolution(string solutionName)
{
IEnumerable<Process> visualStudios = GetVisualStudioProcesses();
foreach (Process visualStudio in visualStudios)
{
_DTE visualStudioInstance;
if (TryGetVsInstance(visualStudio.Id, out visualStudioInstance))
{
try
{
string actualSolutionName = Path.GetFileName(visualStudioInstance.Solution.FullName);
if (string.Compare(
actualSolutionName,
solutionName,
StringComparison.InvariantCultureIgnoreCase) == 0)
{
return visualStudio;
}
}
catch (Exception)
{
throw;
}
}
}
return null;
}
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
private static IEnumerable<Process> GetVisualStudioProcesses()
{
Process[] processes = Process.GetProcesses();
return processes.Where(o => o.ProcessName.Contains("devenv"));
}
private static bool TryGetVsInstance(int processId, out _DTE instance)
{
IntPtr numFetched = IntPtr.Zero;
IRunningObjectTable runningObjectTable;
IEnumMoniker monikerEnumerator;
IMoniker[] monikers = new IMoniker[1];
GetRunningObjectTable(0, out runningObjectTable);
runningObjectTable.EnumRunning(out monikerEnumerator);
monikerEnumerator.Reset();
while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
{
IBindCtx ctx;
CreateBindCtx(0, out ctx);
string runningObjectName;
monikers[0].GetDisplayName(ctx, null, out runningObjectName);
object runningObjectVal;
runningObjectTable.GetObject(monikers[0], out runningObjectVal);
if (runningObjectVal is _DTE && runningObjectName.StartsWith("!VisualStudio"))
{
int currentProcessId = int.Parse(runningObjectName.Split(':')[1]);
if (currentProcessId == processId)
{
instance = (_DTE)runningObjectVal;
return true;
}
}
}
instance = null;
return false;
}
}
}
#endif
@atruskie
Copy link
Author

@aakasheth95, I've updated the code so that it works with a newer Visual Studio API.

You can see an example usage here: https://github.com/QutEcoacoustics/audio-analysis/blob/089e1489f209b09ca60de7b4a516a9ae70518c51/src/AnalysisPrograms/Production/MainEntryUtilities.cs#L140-L184

Note: this is not a plugin. This code is embedded into an executable. When the executable runs, it loads a visual studio DLL and starts a debugging process on itself.

There are few occasions for which this code is appropriate.

@libbkmz
Copy link

libbkmz commented Sep 24, 2019

I've found that I need to add this in order to compile this code.

<Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
	<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
	<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>

Source: https://reviews.llvm.org/D31740

@atruskie
Copy link
Author

That's a good point. It depends on the version of Visual Studio you are using. My references (riddled with tech debt) include:

    <Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte.dll</HintPath>
    </Reference>
    <Reference Include="envdte100, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte100.dll</HintPath>
    </Reference>
    <Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte80.dll</HintPath>
    </Reference>
    <Reference Include="envdte90, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte90.dll</HintPath>
    </Reference>
    <Reference Include="envdte90a, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
      <SpecificVersion>False</SpecificVersion>
      <EmbedInteropTypes>True</EmbedInteropTypes>
      <HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte90a.dll</HintPath>
    </Reference>

@msedi
Copy link

msedi commented Nov 20, 2023

@atruskie: Because you are obviously mostly the only one doing the same things as I do, I have to ask you one question about the performance. It seems to me that locating the LocalProcesses takes a very lon time on my machine. Did you experience the same problem?

@atruskie
Copy link
Author

@msedi It has been a long time since I've used or needed this solution. I don't remember it being slow though.

@msedi
Copy link

msedi commented Nov 20, 2023

@atruskie: No problem. I was just asking ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment