Last active
January 11, 2021 14:48
-
-
Save astrohart/54e96e513ec0ac8ee346bc1772289d7c to your computer and use it in GitHub Desktop.
LINQPad Query Boilerplate to Access VS 2019 via EnvDTE from LINQpad
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Query Kind="Program"> | |
<Reference><RuntimeDirectory>\System.Windows.Forms.dll</Reference> | |
<NuGetReference>EnvDTE</NuGetReference> | |
<NuGetReference>Microsoft.VisualStudio.Setup.Configuration.Interop</NuGetReference> | |
<Namespace>EnvDTE</Namespace> | |
<Namespace>Microsoft.VisualStudio.Setup.Configuration</Namespace> | |
<Namespace>System</Namespace> | |
<Namespace>System.Collections.Generic</Namespace> | |
<Namespace>System.Diagnostics</Namespace> | |
<Namespace>System.IO</Namespace> | |
<Namespace>System.Linq</Namespace> | |
<Namespace>System.Runtime.InteropServices</Namespace> | |
<Namespace>System.Runtime.InteropServices.ComTypes</Namespace> | |
<Namespace>System.Threading</Namespace> | |
<Namespace>System.Windows.Forms</Namespace> | |
</Query> | |
// boilerplate: clone this query and then customize to your liking | |
// make sure the NuGets and query properties get cloned. | |
// | |
// Thank you https://docs.microsoft.com/en-us/visualstudio/extensibility/launch-visual-studio-dte?view=vs-2019 | |
void Main() | |
{ | |
// Launch Visual Studio, and then find the reference to the DTE interface | |
// that is attached to the newly-spawned VS instance. | |
System.Diagnostics.Process vsProcess = null; // handle to running VS2019 process | |
// Create a new instance of the IDE. | |
EnvDTE.DTE dte = LaunchVsDte(isPreRelease: false, out vsProcess); | |
// Register the IOleMessageFilter to handle any threading | |
// errors. | |
MessageFilter.Register(); | |
/* Play with the launched Visual Studio through the dte variable */ | |
// Display the Visual Studio IDE. | |
dte.MainWindow.Activate(); | |
// ===================================== | |
// ==Insert your automation code here.== | |
// ===================================== | |
Solution soln = (Solution)dte.Solution; | |
MessageBox.Show | |
("Solution count: " + soln.Count); | |
// ===================================== | |
/* Close/ | |
exit Visual Studio */ | |
dte.Quit(); | |
/* Dispose the process object */ | |
vsProcess?.Dispose(); | |
vsProcess = null; | |
} | |
private static EnvDTE.DTE LaunchVsDte(bool isPreRelease, out System.Diagnostics.Process process) | |
{ | |
ISetupInstance setupInstance = GetSetupInstance(isPreRelease); | |
string installationPath = setupInstance.GetInstallationPath(); | |
string executablePath = Path.Combine(installationPath, @"Common7\IDE\devenv.exe"); | |
System.Diagnostics.Process vsProcess = System.Diagnostics.Process.Start(executablePath); | |
process = vsProcess; | |
string runningObjectDisplayName = $"VisualStudio.DTE.16.0:{vsProcess.Id}"; | |
IEnumerable<string> runningObjectDisplayNames = null; | |
object runningObject; | |
for (int i = 0; i < 60; i++) | |
{ | |
try | |
{ | |
runningObject = GetRunningObject(runningObjectDisplayName, out runningObjectDisplayNames); | |
} | |
catch | |
{ | |
runningObject = null; | |
} | |
if (runningObject != null) | |
{ | |
return (EnvDTE.DTE)runningObject; | |
} | |
System.Threading.Thread.Sleep(millisecondsTimeout: 1000); | |
} | |
throw new TimeoutException($"Failed to retrieve DTE object. Current running objects: {string.Join(";", runningObjectDisplayNames)}"); | |
} | |
private static object GetRunningObject(string displayName, out IEnumerable<string> runningObjectDisplayNames) | |
{ | |
IBindCtx bindContext = null; | |
NativeMethods.CreateBindCtx(0, out bindContext); | |
IRunningObjectTable runningObjectTable = null; | |
bindContext.GetRunningObjectTable(out runningObjectTable); | |
IEnumMoniker monikerEnumerator = null; | |
runningObjectTable.EnumRunning(out monikerEnumerator); | |
object runningObject = null; | |
List<string> runningObjectDisplayNameList = new List<string>(); | |
IMoniker[] monikers = new IMoniker[1]; | |
IntPtr numberFetched = IntPtr.Zero; | |
while (monikerEnumerator.Next(1, monikers, numberFetched) == 0) | |
{ | |
IMoniker moniker = monikers[0]; | |
string objectDisplayName = null; | |
try | |
{ | |
moniker.GetDisplayName(bindContext, null, out objectDisplayName); | |
} | |
catch (UnauthorizedAccessException) | |
{ | |
// Some ROT objects require elevated permissions. | |
} | |
if (!string.IsNullOrWhiteSpace(objectDisplayName)) | |
{ | |
runningObjectDisplayNameList.Add(objectDisplayName); | |
if (objectDisplayName.EndsWith(displayName, StringComparison.Ordinal)) | |
{ | |
runningObjectTable.GetObject(moniker, out runningObject); | |
if (runningObject == null) | |
{ | |
throw new InvalidOperationException($"Failed to get running object with display name {displayName}"); | |
} | |
} | |
} | |
} | |
runningObjectDisplayNames = runningObjectDisplayNameList; | |
return runningObject; | |
} | |
private static ISetupInstance GetSetupInstance(bool isPreRelease) | |
{ | |
return GetSetupInstances().First(i => IsPreRelease(i) == isPreRelease); | |
} | |
private static IEnumerable<ISetupInstance> GetSetupInstances() | |
{ | |
ISetupConfiguration setupConfiguration = new SetupConfiguration(); | |
IEnumSetupInstances enumerator = setupConfiguration.EnumInstances(); | |
int count; | |
do | |
{ | |
ISetupInstance[] setupInstances = new ISetupInstance[1]; | |
enumerator.Next(1, setupInstances, out count); | |
if (count == 1 && | |
setupInstances != null && | |
setupInstances.Length == 1 && | |
setupInstances[0] != null) | |
{ | |
yield return setupInstances[0]; | |
} | |
} | |
while (count == 1); | |
} | |
private static bool IsPreRelease(ISetupInstance setupInstance) | |
{ | |
ISetupInstanceCatalog setupInstanceCatalog = (ISetupInstanceCatalog)setupInstance; | |
return setupInstanceCatalog.IsPrerelease(); | |
} | |
private static class NativeMethods | |
{ | |
[DllImport("ole32.dll")] | |
public static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc); | |
[DllImport("ole32.dll")] | |
public static extern void GetRunningObjectTable(int reserved, out IRunningObjectTable prot); | |
} | |
public class MessageFilter : IOleMessageFilter | |
{ | |
// | |
// Class containing the IOleMessageFilter | |
// thread error-handling functions. | |
// Start the filter. | |
public static void Register() | |
{ | |
IOleMessageFilter newFilter = new MessageFilter(); | |
IOleMessageFilter oldFilter = null; | |
CoRegisterMessageFilter(newFilter, out oldFilter); | |
} | |
// Done with the filter, close it. | |
public static void Revoke() | |
{ | |
IOleMessageFilter oldFilter = null; | |
CoRegisterMessageFilter(null, out oldFilter); | |
} | |
// | |
// IOleMessageFilter functions. | |
// Handle incoming thread requests. | |
int IOleMessageFilter.HandleInComingCall(int dwCallType, | |
System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr | |
lpInterfaceInfo) | |
{ | |
//Return the flag SERVERCALL_ISHANDLED. | |
return 0; | |
} | |
// Thread call was rejected, so try again. | |
int IOleMessageFilter.RetryRejectedCall(System.IntPtr | |
hTaskCallee, int dwTickCount, int dwRejectType) | |
{ | |
if (dwRejectType == 2) | |
// flag = SERVERCALL_RETRYLATER. | |
{ | |
// Retry the thread call immediately if return >=0 & | |
// <100. | |
return 99; | |
} | |
// Too busy; cancel call. | |
return -1; | |
} | |
int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, | |
int dwTickCount, int dwPendingType) | |
{ | |
//Return the flag PENDINGMSG_WAITDEFPROCESS. | |
return 2; | |
} | |
// Implement the IOleMessageFilter interface. | |
[DllImport("Ole32.dll")] | |
private static extern int | |
CoRegisterMessageFilter(IOleMessageFilter newFilter, out | |
IOleMessageFilter oldFilter); | |
} | |
[ComImport(), Guid("00000016-0000-0000-C000-000000000046"), | |
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] | |
interface IOleMessageFilter | |
{ | |
[PreserveSig] | |
int HandleInComingCall( | |
int dwCallType, | |
IntPtr hTaskCaller, | |
int dwTickCount, | |
IntPtr lpInterfaceInfo); | |
[PreserveSig] | |
int RetryRejectedCall( | |
IntPtr hTaskCallee, | |
int dwTickCount, | |
int dwRejectType); | |
[PreserveSig] | |
int MessagePending( | |
IntPtr hTaskCallee, | |
int dwTickCount, | |
int dwPendingType); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment