How to use DTE in PowerShell?

a_arias picture a_arias · Mar 4, 2013 · Viewed 7k times · Source

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?

Answer

a_arias picture a_arias · Mar 5, 2013

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.