Wednesday 11 January 2017

Read VS2012 SSDT project properties using DTE

Introduction 

This post is to explain how to read read Visual Studio 2012 SSDT project properties programmatically using a DTE project.

Problem Domain - Visual Studio 2010

In Visual Studio 2010, the SSDT projects are loaded using the Microsoft.VisualStudio.Data.Schema.Package.Project namespace.  this namespace contains a public API where you can read and write properties of the loaded SSDT project. the Visual Studio SSDT projects featuring the .dbproj project types which integrated with the Microsoft.VisualStudio.Data.Schema.* namespace. You need to cast the project in to a IDatabaseProjectNode type and read the configurations using the IDatabaseProjectConfiguration interface type.

The IDatabaseProjectConfiguration interface exposes public methods such as GetConfigurationProperty and SetConfigurationProperty where you can read and write SSDT configuration properties programmatically.


The Problem - Visual Studio 2012

When it comes to the Visual Studio 2012 SSDT projects, the project type is converted into the  .sqlproj types. The project is featured using the Microsoft.VisualStudio.Data.Tools.Package.Project instead of Microsoft.VisualStudio.Data.Schema.Package.Project. Unfortunately, the Microsoft.VisualStudio.Data.Tools.Package.Project  name space removed public APIs where we can read and write SSDT properties. I do not know Microsoft's rationale behind this. Hence there is no straight forward way to read and write properties in VS2012 SSDT projects.

The Solution 

First thing I did was decompile the Microsoft.VisualStudio.Data.Schema.Package.Project dll. The same GetConfigurationProperty and SetConfigurationProperty methods are still existing but not as  public methods.







This is why we cannot access these methods directly.

So the solution is use reflection to access these non public properties and methods.
Following is the sample code you can use to achieve this objective.  

Dim dte As EnvDTE.DTE
Dim configuration As EnvDTE.Configuration
Dim projectConfigProperties As Microsoft.VisualStudio.Data.Tools.Package.Project.Internal.ProjectConfigProperties
Dim databaseProjectConfig As Microsoft.VisualStudio.Data.Tools.Package.Project.DatabaseProjectConfig

configuration = project.ConfigurationManager.ActiveConfiguration

Now we need to load the Microsoft.VisualStudio.Data.Tools.Package.Project assembly and load its types.


Dim name As AssemblyName = New AssemblyName()
name.CodeBase = "file://C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\Microsoft\SQLDB\Microsoft.VisualStudio.Data.Tools.Package.dll"
Dim assembly As Assembly = AppDomain.CurrentDomain.Load(name)


There are enums that we need to pass in to the GetConfigurationProperty and SetConfigurationProperty methods. But these enums are also not public. Hence we need to acquire them using reflection as well as follows.

Dim fields As FieldInfo() = dbProjectType.GetFields((BindingFlags.Public Or BindingFlags.Static Or BindingFlags.NonPublic))

 '' Get Internal enum DatabaseProjectEventPropagation using reflection 
Dim dbeventPropagationType As Type = assembly.GetType("Microsoft.VisualStudio.Data.Tools.Package.Project.DatabaseProjectEventPropagation", True, True)
Dim dbEventPropgationFields As FieldInfo() = dbeventPropagationType.GetFields((BindingFlags.Public Or BindingFlags.Static Or BindingFlags.NonPublic))

Dim readTypes As Type() = New Type() {GetType(String), GetType(Boolean), dbProjectType}
 Dim writeTypes As Type() = New Type() {GetType(String), GetType(String), dbeventPropagationType, GetType(Microsoft.VisualStudio.Shell.Interop._PersistStorageType)}

projectConfigProperties = CType(configuration.Object, Microsoft.VisualStudio.Data.Tools.Package.Project.Internal.ProjectConfigProperties)
projectConfigObject = projectConfigProperties.GetType().GetProperty("ProjectConfig", System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance).GetValue(projectConfigProperties)
databaseProjectConfig = CType(projectConfigObject, Microsoft.VisualStudio.Data.Tools.Package.Project.DatabaseProjectConfig)

Dim getPropertyMethod As MethodInfo = databaseProjectConfig.GetType().GetMethod("GetConfigurationProperty", BindingFlags.NonPublic Or BindingFlags.Instance, Nothing, readTypes, Nothing)
Dim setPropertyMethod As MethodInfo = databaseProjectConfig.GetType().GetMethod("SetConfigurationProperty", BindingFlags.NonPublic Or BindingFlags.Instance, Nothing, writeTypes, Nothing)

Please note that if we do not pass in exact types which are compatible to method signatures, the MethodInfo objects returns as Null.

Next thing is we need to Invoke the reflected method and access properties.

Dim val = getPropertyMethod.Invoke(projectConfigObject, New Object() {"AnsiNulls", False, fields(0).GetValue(Nothing)})
setPropertyMethod.Invoke(projectConfigObject, New Object() {"AnsiNulls", "False", dbEventPropgationFields(0).GetValue(Nothing), Microsoft.VisualStudio.Shell.Interop._PersistStorageType.PST_PROJECT_FILE})