How to add files to an SSDT Visual Studio project from Powershell 7

Viewed 157

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
2 Answers

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.

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')
Related