EnvDTE is an assembly-wrapped COM library containing the objects and members for Visual Studio core automation. This library is probably one of the most under-utilized - considering its capabilities. One of its best features is the ability to attach a debugger to a process - meaning event-driven debugging is a lot easier.
When you add an assembly reference to EnvDTE.dll, you must also set the Embed Interop Types property of the assembly to false.
First, let's start with a way to start or get a running process.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Linq;
static System.Diagnostics.Process LoadIfNotRunning(string processName, string exe = null, string args = null, bool hidden = false)
{
var processes = System.Diagnostics.Process.GetProcessesByName(processName);
if (processes.Any()) { return processes.First(); }
if (exe == null)
{
exe = processName;
}
System.Diagnostics.Process p = null;
if (!exe.ToLowerInvariant().EndsWith(".exe"))
{
exe += ".exe";
}
if (!exe.Contains(@":\"))
{
exe = Path.Combine(Environment.CurrentDirectory, exe);
}
try
{
var pi = new ProcessStartInfo(
exe,
args);
if (hidden)
{
pi.UseShellExecute = false;
pi.CreateNoWindow = true;
pi.WindowStyle = ProcessWindowStyle.Hidden;
}
p = System.Diagnostics.Process.Start(pi);
#if DEBUG
Console.WriteLine("Starting " + p.ProcessName + "...");
#endif
} catch { }
return p;
}
The above code will return a process if it exists, and will run the executable if it does not. Now, we need a way to attach the debugger. For this, we'll use the EnvDTE library.
using EnvDTE;
/// <summary>
/// Attaches Visual Studio to the specified process.
/// </summary>
/// <param name="process">The process.</param>
/// <param name="maxTries">The number of tries to get the process.</param>
public static void Attach(this System.Diagnostics.Process process, int maxTries = 5)
{
// Reference visual studio core
DTE dte = null;
int version = 9;
while (version < 17 && dte == null)
{
try
{
version++;
dte = (DTE)Marshal.GetActiveObject(String.Format("VisualStudio.DTE.{0}.0", version));
}
catch (COMException)
{
#if DEBUG
Console.WriteLine(String.Format("Visual studio {0} not found.", version));
#endif
}
}
if (dte == null)
{
Console.WriteLine("No debugger found, nothing attached...");
return;
}
// Try loop - visual studio may not respond the first time.
// We also don't want it to stall the main thread
new System.Threading.Thread(() =>
{
while (maxTries-- > 0)
{
try
{
Processes processes = dte.Debugger.LocalProcesses;
foreach (EnvDTE.Process proc in processes)
{
try
{
if (proc.Name.Contains(process.ProcessName))
{
proc.Attach();
#if DEBUG
Console.WriteLine(String.Format("Attatched to process {0} successfully.", process.ProcessName));
#endif
return;
}
}
catch { }
}
}
catch { }
// Wait for debugger and application and debugger to find application
System.Threading.Thread.Sleep(1500);
}
}).Start();
}
Okay, now it's time to put it all together in a console application that will just start "MyProgram".
public static class Program
{
static void Main(string[] args)
{
List<System.Diagnostics.Process> processes = new List<System.Diagnostics.Process>();
var myProgram = LoadIfNotRunning("MyProgram", exe: @"C:\Program Files (x86)\MyProgram\bin\MyProgram.exe");
processes.Add(myProgram);
#if DEBUG
Attach(myProgram);
#endif
Console.WriteLine("Press any key to shut down...");
Console.ReadKey();
foreach (var p in processes)
{
try
{
Console.WriteLine("Killing " + p.ProcessName);
p.Kill();
}
catch (Exception ex)
{
try
{
Console.WriteLine(ex.Message);
Console.WriteLine("Closing " + p.ProcessName);
p.Close();
}
catch (Exception ex2)
{
Console.WriteLine(ex2.Message);
}
}
}
Console.WriteLine("Goodbye!");
Console.ReadKey();
}
}
And that's it! "MyProgram" should be attached to the debugger when you debug run the console application.