1+ param ($Action , $VSProcessId , $ChildProcessId )
2+
3+ # See end of file
4+
5+ Add-Type - Language Csharp - TypeDefinition @"
6+ using System;
7+ using System.Runtime.InteropServices;
8+ using System.Runtime.InteropServices.ComTypes;
9+
10+ namespace VSAutomationHelper
11+ {
12+ public class Main
13+ {
14+ [DllImport("ole32.dll")]
15+ private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
16+
17+ public static object FindDTEInstanceByProcessId(int processId)
18+ {
19+ IBindCtx bindContext = null;
20+ IRunningObjectTable objectTable = null;
21+ IEnumMoniker enumMonikers = null;
22+
23+ try
24+ {
25+ Marshal.ThrowExceptionForHR(CreateBindCtx(0, out bindContext));
26+ bindContext.GetRunningObjectTable(out objectTable);
27+ objectTable.EnumRunning(out enumMonikers);
28+
29+ IMoniker[] moniker = new IMoniker[1];
30+ IntPtr numberFetched = IntPtr.Zero;
31+
32+ while (enumMonikers.Next(1, moniker, numberFetched) == 0)
33+ {
34+ var runningObjectMoniker = moniker[0];
35+
36+ if (runningObjectMoniker == null)
37+ continue;
38+
39+ try
40+ {
41+ string name = null;
42+ runningObjectMoniker.GetDisplayName(bindContext, null, out name);
43+
44+ if (name.StartsWith("!VisualStudio.DTE.")) // Consistent from 2010~2022
45+ {
46+ int idIndex = name.IndexOf(':');
47+
48+ if (idIndex != -1 && int.Parse(name.Substring(idIndex + 1)) == processId)
49+ {
50+ object runningObject = null;
51+ Marshal.ThrowExceptionForHR(objectTable.GetObject(runningObjectMoniker, out runningObject));
52+
53+ return runningObject;
54+ }
55+ }
56+ }
57+ catch (UnauthorizedAccessException)
58+ {
59+ // Inaccessible due to permissions
60+ }
61+ }
62+ }
63+ finally
64+ {
65+ if (enumMonikers != null)
66+ Marshal.ReleaseComObject(enumMonikers);
67+
68+ if (objectTable != null)
69+ Marshal.ReleaseComObject(objectTable);
70+
71+ if (bindContext != null)
72+ Marshal.ReleaseComObject(bindContext);
73+ }
74+
75+ return null;
76+ }
77+ }
78+ }
79+ "@
80+
81+ #
82+ # .\script.ps1 Launch Callback from Visual Studio to launch the game
83+ # .\script.ps1 Attach Callback from the game to attach Visual Studio's debugger
84+ #
85+ # GAME_DEBUGGER_CMDLINE Env var holding a command line expression that launches the game
86+ # GAME_DEBUGGER_REQUEST Env var holding a command line expression that runs when the game is ready for debugger attach
87+ # GAME_DEBUGGER_PROC Env var holding kernel32.dll's CreateProcessA import function string to prevent AV false positives
88+ #
89+ if ($Action -eq ' Launch' ) {
90+ # Visual Studio doesn't necessarily launch the debugee. Keep chasing the parent PID until we find it.
91+ $parentVSProcessId = $PID
92+
93+ while ($true ) {
94+ $wmiProcess = Get-WmiObject Win32_Process - Filter ProcessId= $parentVSProcessId
95+
96+ if ($wmiProcess.ProcessName.ToLower () -eq " devenv.exe" ) {
97+ break ;
98+ }
99+
100+ $parentVSProcessId = $wmiProcess.ParentProcessId
101+ }
102+
103+ # Start process with special environment vars set
104+ $localPSPath = (Get-Process - Id $PID | Get-Item ).FullName
105+ $localScriptPath = $MyInvocation.MyCommand.Path
106+
107+ $env: GAME_DEBUGGER_REQUEST = ' "' + $localPSPath + ' " -ExecutionPolicy Bypass -File "' + $localScriptPath + ' " Attach ' + $parentVSProcessId + ' '
108+ $env: GAME_DEBUGGER_PROC = ' kernel32!CreateProcessA'
109+
110+ & $env: GAME_DEBUGGER_CMDLINE
111+ }
112+ elseif ($Action -eq ' Attach' ) {
113+ # Callback from game DLL side. Tell Visual Studio to attach to its process.
114+ $automationDTE = [VSAutomationHelper.Main ]::FindDTEInstanceByProcessId($VSProcessId )
115+
116+ foreach ($process in $automationDTE.Debugger.LocalProcesses ) {
117+ if ($process.ProcessID -eq $ChildProcessId ) {
118+ $process.Attach ()
119+ break ;
120+ }
121+ }
122+ }
0 commit comments