How to use DTE in PowerShell?
Asked Answered
S

3

14

I am trying to use PowerShell to automate the process of creating an n-tier solution based on a seed (think EDMX file or DbContext) configuration. I want to be able to open a skeleton solution, get the active instance, and populate project files with auto-generated code.

I'm trying to transcode the example provided here to powershell, however, I am getting errors.

Here is the PowerShell code I am testing:

First, I execute a little function to reference the DTE assemblies.

$libs = "envdte.dll", "envdte80.dll", "envdte90.dll", "envdte100.dll"
function LoadDTELibs {
    param(
        $path = "\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies"
    )

    Process {
        $libs |
            ForEach {
                $dll = Join-Path "$env:ProgramFiles\$path" $_

                if(-not (Test-Path $dll)) {
                    $dll = Join-Path "${env:ProgramFiles(x86)}\$path" $_
                }

                Add-Type -Path $dll -PassThru | Where {$_.IsPublic -and $_.BaseType} | Sort Name
            }
    }
}


LoadDTELibs

Then, I try to create a object to reference the result of calling [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.11.0")

PS> $dte = New-Object -ComObject EnvDTE80.DTE2

New-Object : Retrieving the COM class factory for component with CLSID {00000000-0000-0000-0000-000000000000} failed due to the following error: 80040154 
Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).
At line:1 char:8
+ $dte = New-Object -ComObject EnvDTE80.DTE2
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [New-Object], COMException
    + FullyQualifiedErrorId : NoCOMClassIdentified,Microsoft.PowerShell.Commands.NewObjectCommand

or:

PS> $dte = New-Object EnvDTE80.DTE2

New-Object : Constructor not found. Cannot find an appropriate constructor for type EnvDTE80.DTE2.
At line:1 char:8
+ $dte = New-Object EnvDTE80.DTE2
+        ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (:) [New-Object], PSArgumentException
    + FullyQualifiedErrorId : CannotFindAppropriateCtor,Microsoft.PowerShell.Commands.NewObjectCommand

Finally, this does not work either:

PS> [EnvDTE80.DTE2]$dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.11.0")

Cannot convert the "System.__ComObject" value of type "System.__ComObject#{04a72314-32e9-48e2-9b87-a63603454f3e}" to type "EnvDTE80.DTE2".
At line:1 char:1
+ [EnvDTE80.DTE2]$dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject( ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

So, my question is, how do you use DTE from PowerShell? More specifically, how do you cast the result of calling GetActiveObject to type EnvDTE.DTE2?

Suprematism answered 4/3, 2013 at 20:45 Comment(4)
I believe NuGet's TypeWrapper class works around this same issue. (Caution: Apache-licensed code owned by the Outercurve Foundation)Klute
This is a great suggestion, and, lots of insight was provided by reviewing this code. However, it seems I have found a simple alternative. As you will see in my follow up answer, PowerShell handles the process a bit differently, so, casting as described on MSDN is not required.Suprematism
If you are using the package manager console in VS, the current instance's EnvDTE is already provided by the $dte variable.Minstrel
@Klute github.com/NuGet/NuGet2/blob/2.14/src/VsConsole/PowerShellHost/…Apostrophize
S
17

I found a simple answer by playing with the idea in ISE for a little while.

Basically, the call to GetActiveObject returns a COM object, which can be used directly in PowerShell. After executing LoadDTELibs, you can get an instance of DTE by calling GetActiveObject and then refer to the result directly.

So...

PS> $dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.11.0")

Then:

PS> $dte.solution.Create("D:\Testing", "Acme.sln")
PS> $dte.solution.SaveAs("D:\Testing\Acme.sln")

I'm not 100% sure, because I don't know PowerShell or COM all that well, but I think you don't really have to worry about releasing the COM instance.

Suprematism answered 5/3, 2013 at 0:3 Comment(3)
It was this article which provided the clues which were helpful in finding the answer.Suprematism
"After executing LoadDTELibs" - How did you execute this?Linker
With this code I can't reach the part of the api to do remote debugging though, like Debugger2.TransportsSpokesman
L
2

For VS 2017 it is as follows:

$dte = [System.Runtime.InteropServices.Marshal]::GetActiveObject("VisualStudio.DTE.15.0")
Limnetic answered 20/11, 2018 at 15:7 Comment(0)
V
2

Running Visual Studio 2019, I've been able to start the debugger with the 'VisualStudio.DTE' COM interface (without the version):

#Get the ProcessID from an AppPool's worker process: 
[int] $ProcessId = ([xml] (& "$env:SystemRoot\system32\inetsrv\appcmd.exe" list wp /xml /apppool.name:"DefaultAppPool")).appcmd.WP."WP.NAME"

# Start the debugger, attached to that ProcessID
[Runtime.InteropServices.Marshal]::GetActiveObject('VisualStudio.DTE').Debugger.LocalProcesses | 
       ? {$_.ProcessID -eq $ProcessId} | %{$_.Attach()}

Previously it was necessary to specify the version.

Vins answered 26/6, 2020 at 21:53 Comment(1)
This is a great tip for getting a specific IIS App Pool by nameTabular

© 2022 - 2024 — McMap. All rights reserved.