4

I'd like to automate adding/removing files to/from a SSDT (Database) Project in Powershell 7.

My Solution has 3 SSDT Projects in it and is running in a VS 2019 instance.

I can get the DTE object with help from # Is there a substitue for System.Runtime.InteropServices.Marshal.GetActiveObject() in .NET Core? along with the EnvDTE, EnvDTE80, EnvDTE90 and EnvDTE100 v16.10.31320.204 libraries from Nuget

It feels like I need some more libraries though as I get all these impenetrable System.__ComObject's - but I cannot work out how to discover the libraries that I need.

Here are the main sights I've seen on the journey so far:

> $dte
Name                   : Microsoft Visual Studio
FileName               : C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\devenv.exe
Version                : 16.0
CommandBars            : System.__ComObject
Windows                : System.__ComObject
Events                 : System.__ComObject
AddIns                 :
MainWindow             : System.__ComObject
ActiveWindow           : System.__ComObject
DisplayMode            : 2
Solution               : System.__ComObject
Commands               : System.__ComObject
SelectedItems          : System.__ComObject
CommandLineArguments   : "C:\Users\bgerhardi\source\Vega\VegaDW\src\VegaDW-DBs.sln"
DTE                    : System.__ComObject
LocaleID               : 1033
WindowConfigurations   : System.__ComObject
Documents              : System.__ComObject
ActiveDocument         : System.__ComObject
Globals                : System.__ComObject
StatusBar              : System.__ComObject
FullName               : C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\devenv.exe
UserControl            : True
ObjectExtenders        : System.__ComObject
Find                   : System.__ComObject
Mode                   : 1
ItemOperations         : System.__ComObject
UndoContext            : System.__ComObject
Macros                 :
ActiveSolutionProjects : {}
MacrosIDE              :
RegistryRoot           : Software\Microsoft\VisualStudio\16.0_cd071aac
Application            : System.__ComObject
ContextAttributes      : System.__ComObject
SourceControl          : System.__ComObject
SuppressUI             : False
Debugger               : System.__ComObject
Edition                : Enterprise

looking in the $env.Solution I can see a nice object and another bunch of System.__ComObject's

> $dte.Solution
System.__ComObject
System.__ComObject
System.__ComObject

Name                 : Miscellaneous Files
FileName             :
IsDirty              : False
Collection           :
DTE                  : System.__ComObject
Kind                 : {66A2671D-8FB5-11D2-AA7E-00C04F688DDE}
ProjectItems         : System.__ComObject
Properties           :
UniqueName           : <MiscFiles>
Object               :
ExtenderNames        : {}
ExtenderCATID        : {610d4612-d0d5-11d2-8599-006097c68e81}
FullName             :
Saved                : True
ConfigurationManager :
Globals              :
ParentProjectItem    :
CodeModel            :

However I don't see this Miscellanous Files item in the VS UI.

If I try to go into these, strangely there is nothing in 0 but just these impenetrable System.__ComObject's

> $dte.Solution[0]
OperationStopped: Value does not fall within the expected range.

> $dte.Solution[1]
System.__ComObject

> $dte.Solution[1] | gm 

>

I've tried casting to a few differnet things but all tell me a (COM?) GUID that I can't find anywhere in my registry or on the internet

> [EnvDTE.Project]$dte.Solution[1]
InvalidArgument: Cannot convert the "System.__ComObject" value of type "System.__ComObject#{2aa7b615-65c8-356e-8652-98f68ab7548e}" to type "EnvDTE.Project".

What is the right way to navigate my way through these __ComObject's and find the correct Types that I need?

To create a repro for this on your own machine, simply

  • create a new "SQL Server Database Project" in Visual Studio 2019. It behaves the same way for me - impenetrable System.__ComObject's

Create new project

  • Run PS7 instance
  • run the following
    
     $methoddef = 'public static object GetActiveObject(string progId, bool throwOnError = false)
     {
         if (progId == null)
             throw new ArgumentNullException(nameof(progId));
    
         var hr = CLSIDFromProgIDEx(progId, out var clsid);
         if (hr < 0)
         {
             if (throwOnError)
                 Marshal.ThrowExceptionForHR(hr);
    
             return null;
         }
    
         hr = GetActiveObject(clsid, IntPtr.Zero, out var obj);
         if (hr < 0)
         {
             if (throwOnError)
                 Marshal.ThrowExceptionForHR(hr);
    
             return null;
         }
         return obj;
     }
    
     [DllImport("ole32")]
     private static extern int CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] string lpszProgID, out Guid lpclsid);
    
     [DllImport("oleaut32")]
     private static extern int GetActiveObject([MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out object ppunk);'
    
     $blah = add-type -MemberDefinition $methoddef -Name "Blah" -Namespace "Blah" -PassThru
     $dte = $blah::GetActiveObject('VisualStudio.DTE.16.0')
    
    
  • notice that $dte.ActiveSolutionProjects[0] returns nothing useful, just System.__ComObject
Brett
  • 719
  • 1
  • 7
  • 19
  • never worked with that `envdte`, but you can parse out all guids from sqlproj/sln file. Additionally you can add remove files manually by editing xml file. – Dmitrij Kultasev Oct 18 '21 at 08:02
  • Thanks for the thoughts. That ComObject Guid doesn't exist in the project/sln file either. Pretty sure that this is saying that it is a com object of that GUID that powershell doesn't have available - it isn't on my machine or in my registry. I do understand that editing the xml proj file is a potential workaround however it isn't as good as 1. it requires VS to do a full reload of the whole project each time it is edited 2. the vs project system seems to randomly create/pick ItemGroup to put Files in, which makes this a bit more complex to parse and manipulate as they aren't all in one place – Brett Oct 18 '21 at 11:18
  • Can you post your *.sln file and an example project file? (Minimised / obfuscated if poss…) – mclayton Oct 19 '21 at 12:55
  • Nothing special about my sln file I've tested with a brand new sln/dbproj and I get the same behaviour. StackOverflow doesn't allow uploading files but you can just create your own in one step in VS 2019 (I've added full details on how to repro). Hope this helps. – Brett Oct 20 '21 at 18:14
  • I've just noticed that I can actually get to properties inside the System.__ComObject, they just don't show like normal objects do. So ```$dte.Solution``` just returns System.__ComObject but ```$dte.Solution.Filename``` does return the correct filename. Is it not possible to explore these com objects interactively with powershell for some reason? – Brett Oct 20 '21 at 20:12

2 Answers2

0

First of all, you can try searching for the GUID mentioned in the exception message in the Windows registry. Thus, you will find the COM object which corresponds to the property name.

Also take a look at the EnvDTE Namespace and other related namespaces that come from the EnvDTE's related names.

Eugene Astafiev
  • 47,483
  • 3
  • 24
  • 45
  • Thanks for the response. I mentioned that I searched my registry for that guid "2aa7b615-65c8-356e-8652-98f68ab7548e" and it found no results. I also searched the internet for it. I have spent a lot of time already looking through the EnvDTE docs and it doesn't help me understand which libraries I'm missing and whether this __ComObject behaviour is normal in Powershell or not for this object model. Would appreciate some more specific advice. Thanks – Brett Oct 17 '21 at 23:42
  • The remarks say "Changes and new functionality are contained in the EnvDTE80, EnvDTE90, EnvDTE90a, and EnvDTE100 namespaces. When you add an assembly reference to EnvDTE.dll, you must also set the Embed Interop Types property of the assembly to false." Anyone know how to set the "Embed Interop Types" to false with Powershell - I guess some trick in Add-Type? – Brett Oct 18 '21 at 12:31
0

Using the PowerShell ISE can be helpful in exploring the $dte object as it provides a kind of intellisense.

Some examples of what you can do to get you started.

List the active project(s):

$dte.ActiveSolutionProjects.ForEach({$_.Name})

List the projects in the solution:

$dte.Solution.Projects

Get the Solution Folder:

$dte.Solution.Projects | Where-Object {$_.Name -match 'Solution Files'} 

List subfolders in the Solution Folder:

$subFolders = $dte.Solution.Projects | Where-Object {$_.Name -match 'Solution Files'} | Select-Object ProjectItems
$subFolders.ProjectItems

Get a Project:

$dte.Solution.Projects | Where-Object {$_.Name -eq 'MyProject'} 

Add a file to the project:

$Project = $dte.Solution.Projects | Where-Object {$_.Name -eq 'MyProject'} 
$Project.ProjectItems.AddFromFile($MyPath)

The Package Manager Console window in Visual Studio already has an active EnvDTE object in the $dte variable if you need to try a one-liner. However it does not appear to provide intellisense, so exploring is a bit more tedious.

The Developer Powershell console in Visual Studio will provide a form of intellisense, but does not give you the reference to a EnvDTE object. You can create one easily (if Visual Studio is running):

$dte = [Runtime.InteropServices.Marshal]::GetActiveObject('VisualStudio.DTE')
K J
  • 602
  • 1
  • 9
  • 18