Setting the OutputPath property of a project via Visual Studio Automation

c#, envdte, msbuild, visual-studio-2012

Solution

Here's what I ended up doing:

The problem I was trying to solve is not repeating myself (D.R.Y.) and specifying a solution-wide output directory (in a solution with a lot of projects) - that is, when compiling a solution, all projects would have their output directory set to something like `$(SolutionDir)bin\Debug` or `$(SolutionDir)bin\Release`. It is worth mentioning that some projects are included across repositories and in more than one solution.

At first, I created an MSBuild file (a `<Project>` XML - called it `MySolution.sln.targets`). In it, I defined a `<PropertyGroup>` that overrode the `<OutputPath>` property to:

$(SolutionDir)bin\$(Platform)\$(Configuration)

Then I added the following import to all the relevant projects, before the build targets import:

<Import Project="$(SolutionPath).targets" />

That way, each solution has an accompanying `.targets` file defining such things that I want to be solution-wide.

This worked well, but then I encountered the following problem: The abovementioned `$(Platform)` and `$(Configuration)` macros refer to the project's properties, not the solution-wide ones. What would happen if my solution's Debug/Any CPU configuration still built some very specific project in its Release configuration? As far as I'm aware, after thoroughly examining the documentation, no such macros are exported that have a solution-wide granularity.

I found ceztko's Visual Studio extension which made Visual Studio export exactly the macros that I was looking for - but after some experimentation and fiddling, I discovered that this extension set them too late - only upon building the solution. This caused issues with Visual Studio's Incremental Build feature - It kept thinking that the projects were out of date because it was looking in the wrong place - it wasn't aware of the variables, but MSBuild.exe was.

I started fiddling with the `IVsUpdateSolutionEvents` interface, tracing when each method is called - and then discovered that `IVsUpdateSolutionEvents.OnActiveProjectCfgChange` is called twice when opening a 1-project solution in a fresh Visual Studio, or when changing the solution's configuration from Debug to Release. Further fiddling revealed that if I set the project to compile in Release in both solution configurations, this method now got called once instead of twice when changing solution configurations.

I forked the extension's repository and amended the issue by moving the macro-setting logic to the above-mentioned method. You can find it here.

Disclaimer: this may not interact so well with batch-build operations from the IDE, and requires you to export these properties yourself when building from MSBuild.exe's command-line.

Good luck in your travels.

Problem

I'm writing a VSIX package to allow the user to bulk-edit the OutputPath property of all the active configurations of projects in the currently loaded solution (see the incredibly annoying step #4 here). I ran into a very specific problem: when setting the property to a value containing macros (e.g. `"$(SolutionDir)\bin\Debug"` the value written into the .csproj is escaped as follows: ``` <OutputPath>%24%28SolutionDir%29\bin\Debug\</OutputPath> ``` Which, rather than letting MSBuild expand the macro, creates an actual physical folder named `$(SolutionDir)`. I'd like to somehow bypass this escaping. The MSDN documentation is unsurprisingly lacking in that area. My initial code is as follows: ``` private void MenuItemCallback(object sender, EventArgs e) { SolutionWideOutputDialogWindow dialog = new SolutionWideOutputDialogWindow(); dialog.ShowModal(); if (!dialog.DialogResult.HasValue || !dialog.DialogResult.Value) { return; } string requestedOutputPath = dialog.outputPathTextBox.Text; Solution2 solution = _dte2.Solution as Solution2; if (solution == null) { return; } Projects projects = solution.Projects; foreach (Project project in projects) { Property outputPath = project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath"); outputPath.Value = requestedOutputPath; project.Save(); } } ``` Greatly appreciate anyone's help.

Original source