I'm trying to go through all projects of our Visual Studio 2008 solution programmatically in order to ensure correct values of certain project settings.
I'm trying to do it with VBS and DTE but I don't insist on using a particular language/API/framework, except that I wouldn't like to make adjustments to project settings at XML level.
The problem with the following VBS code is that it doesn't encounter any projects (as all of them are contained in Solution Folders) - instead, I'm just getting a list of the Solution Folders.
Example msgbox output:
NAME:Gateways TYPE: {66A26720-8FB5-11D2-AA7E-00C04F688DDE}
The above GUID is actually the same for all solution folders.
solutionfile = CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) + "\big_solution.sln"
msgbox(solutionfile)
Set dte = CreateObject("VisualStudio.DTE.9.0")
dte.MainWindow.Visible = True
Call dte.solution.open(solutionfile)
Set prjs = DTE.Solution.Projects
For i = 1 To prjs.Count
Set p = prjs.Item(i)
msgbox("NAME:" & p.Name & " TYPE: " & p.Kind & vbCr)
Next
Call dte.solution.SaveAs(dte.solution.FileName)
dte.solution.close
msgbox solutionfile + " successfully updated."
dte.UserControl = True
Thanks for any hints!
To work with VS2008 solutions, you need first of all
c:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies\EnvDTE.dll
but it does not expose all properties of all kinds of projects (e.g. the property "InheritedPropertySheets" in C++ projects is not accessible). To access C++ projects properly you will also need:
c:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.VCProjectEngine.dll
Example in C# (.NET 3.5) which traverses a solution, making sure each C++ project inherits our custom Property Sheet and then in the Post-Build Step uses the macro "THETARGETDIR" which is defined centrally in our Property Sheet.
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using EnvDTE;
using Microsoft.VisualStudio.VCProjectEngine;
namespace FixSolution
{
class Program
{
private class Stats
{
public int n_pbs_adjusted = 0;
public int n_projects = 0;
public int n_projects_cpp = 0;
public int n_ps_adjusted = 0;
public int n_consistency_errors = 0;
}
private static Stats _stats = new Stats();
static void ProcessSolutionFolder(Project p)
{
foreach (ProjectItem pi in p.ProjectItems)
{
Project pp = pi.Object as Project;
if (pp == null)
{
continue; // solution item but not a project (e.g. big_solution_explained.txt)
}
if (pp.Kind == FixSolution.Properties.Settings.Default.SF_GUID)
ProcessSolutionFolder(pp);
else
ProcessProject(pp);
}
}
private static int NextHardcoded(string cmd)
{
return cmd.ToLower().IndexOf(FixSolution.Properties.Settings.Default.STD_TARGET);
}
private static bool ProcessVCPostBuildEventTool(VCConfiguration c)
{
IVCCollection cc = c.Tools as IVCCollection;
if (cc == null) return false;
bool adjusted = false;
foreach (Object tool in cc)
{
VCPostBuildEventTool pbs = tool as VCPostBuildEventTool;
if (pbs == null || pbs.CommandLine == null) continue;
int pos = NextHardcoded(pbs.CommandLine);
var before_adjustment = pbs.CommandLine;
while (pos != -1)
{
adjusted = true;
string CMD = pbs.CommandLine;
int tl = FixSolution.Properties.Settings.Default.STD_TARGET.Length;
pbs.CommandLine = CMD.Substring(0, pos) + "$(THETARGETDIR)" + CMD.Substring(pos + tl, CMD.Length - pos - tl);
pos = NextHardcoded(pbs.CommandLine);
}
if (adjusted)
{
VCProject p = c.project as VCProject;
Console.WriteLine("\nWARNING: project " + p.ProjectFile
+ "\nConfiguration " + c.ConfigurationName + " contains hardcoded sophis directory in PBS:"
+ "\n" + before_adjustment);
Console.WriteLine("REPLACED BY:");
Console.WriteLine(pbs.CommandLine);
}
}
return adjusted;
}
private static void ProcessProject(Project pp)
{
++_stats.n_projects;
VCProject p = pp.Object as VCProject;
if (p == null)
return; // not a C++ project
++_stats.n_projects_cpp;
string vsprops_path = Util.GetRelativePath(p.ProjectDirectory, FixSolution.Properties.Settings.Default.AbsoluteVspropsPath);
bool adjusted_ps = false;
bool adjusted_pbs = false;
foreach (VCConfiguration c in (IVCCollection)p.Configurations)
{
if (ProcessVCPostBuildEventTool(c))
adjusted_pbs = true;
if (c.InheritedPropertySheets == vsprops_path)
{
continue;
}
if (c.InheritedPropertySheets.Length > 0)
{
Console.WriteLine("WARNING: project " + pp.FullName + " config " + c.Name + " has unusual InheritedPropertySheets: " + c.InheritedPropertySheets);
++_stats.n_consistency_errors; // counting the unexpected, per configuration
}
adjusted_ps = true;
c.InheritedPropertySheets = vsprops_path;
}
if (adjusted_ps)
++_stats.n_ps_adjusted;
if (adjusted_pbs)
++_stats.n_pbs_adjusted;
p.Save();
}
static DTE GetDTE(string solutionfile)
{
DTE dte = null;
try
{
dte = (DTE)System.Runtime.InteropServices.Marshal.GetActiveObject(
FixSolution.Properties.Settings.Default.ROT_PROG_ID);
}
catch (Exception)
{
Console.WriteLine("Could not get an instance of an already running Visual Studio. Trying to start a new one");
}
if (dte == null)
{
Type visualStudioType = Type.GetTypeFromProgID(
FixSolution.Properties.Settings.Default.ROT_PROG_ID);
dte = Activator.CreateInstance(visualStudioType) as DTE;
}
if (dte == null)
{
Console.WriteLine("Unable to get an instance of Visual Studio");
}
else
{
dte.MainWindow.Visible = true;
dte.Solution.Open(solutionfile);
}
return dte;
}
static void ProcessSolution(string solutionfile)
{
DTE dte = GetDTE(solutionfile);
foreach (Project pp in dte.Solution.Projects)
{
if (pp.Kind == FixSolution.Properties.Settings.Default.SF_GUID)
ProcessSolutionFolder(pp);
else
ProcessProject(pp);
}
Console.WriteLine(string.Format("\nn_projects={0}, n_projects_cpp={1}, n_pbs_adjusted={2}, n_ps_adjusted={3}; n_consistency_errors={4}",
_stats.n_projects, _stats.n_projects_cpp, _stats.n_pbs_adjusted, _stats.n_ps_adjusted, _stats.n_consistency_errors));
dte.Solution.Close();
}
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine(@"Usage: FixSolution c:\full\path\to\YourSolution.sln");
}
var solutionfile = args[0];
ProcessSolution(solutionfile);
}
}
}
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="FixSolution.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<FixSolution.Properties.Settings>
<setting name="AbsoluteVspropsPath" serializeAs="String">
<value>c:\svn\mainline\ourbuild.vsprops</value>
</setting>
<setting name="SF_GUID" serializeAs="String">
<value>{66A26720-8FB5-11D2-AA7E-00C04F688DDE}</value>
</setting>
<setting name="STD_TARGET" serializeAs="String">
<value>c:\apps\ourapp</value>
</setting>
<setting name="ROT_PROG_ID" serializeAs="String">
<value>VisualStudio.DTE.9.0</value>
</setting>
</FixSolution.Properties.Settings>
</applicationSettings>
</configuration>
ourbuild.vsprops
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioPropertySheet
ProjectType="Visual C++"
Version="8.00"
Name="build"
>
<UserMacro
Name="THETARGETDIR"
Value="c:\apps\ourapp"
PerformEnvironmentSet="true"
/>
</VisualStudioPropertySheet>