Modifying the project file by direct file access

Apr 15, 2011 at 5:31 PM
Edited Apr 15, 2011 at 5:32 PM

 Hello,

To implement a Nuget package for PostSharp, I need to add an Import at the end of the project file. I'm doing that by editing the project file directly using an XmlDocument. To ensure that the IDE reloads the project, I use the following code: 

project.Save();

string fileName = project.FullName;
((IVsSolution4)solution).UnloadProject(ref projectGuid, (uint)_VSProjectUnloadStatus.UNLOADSTATUS_LoadPendingIfNeeded);

try
{
    converter.ProcessProject( fileName, true, !install );
}
finally
{
    log.WriteLine( "Reloading the project." );

    ((IVsSolution4)solution).ReloadProject(ref projectGuid);
}

 

 This works fine for INSTALL.PS1. However, for UNINSTALL.PS1, the following error message gets printed:

 Executing script file 'C:\Users\Gael.SHARPCRAFTERS\Documents\Visual Studio 2010\Projects\TestNuGet\packages\PostSharp.2.1.0\tools\uninstall.ps1'.
Project unavailable.

Is there a way to avoid the "Project unavailable" error on uninstall? Or is there a better way to edit the project file?

Thank you.

-gael

 

Apr 19, 2011 at 12:16 AM
I'm fixing this issue as part of adding support for .targets files natively.


I found this during my work, and it's caused when caching too aggressively the DTE objects that can change under the hood (they become unavailable at that point). Project is the worse because it always becomes unavailable when you make changes to the project file (regardless of whether you use plain XML manipulation or the MSBuild APIs).

Fortunately, this was very isolated in NuGet and easy to fix. Sending pull request soon. Working on the uninstall story right now.

/kzu

--
Daniel Cazzulino | Developer Lead | MS MVP | Clarius Consulting | +1 425.329.3471


On Fri, Apr 15, 2011 at 14:31, gfraiteur <notifications@codeplex.com> wrote:

From: gfraiteur

Hello,

To implement a Nuget package for PostSharp, I need to add an Import at the end of the project file. I'm doing that by editing the project file directly using an XmlDocument. To ensure that the IDE reloads the project, I use the following code:

project.Save();

string fileName = project.FullName;
((IVsSolution4)solution).UnloadProject(ref projectGuid, (uint)_VSProjectUnloadStatus.UNLOADSTATUS_LoadPendingIfNeeded);

try
{
    converter.ProcessProject( fileName, true, !install );
}
finally
{
    log.WriteLine( "Reloading the project." );

    ((IVsSolution4)solution).ReloadProject(ref projectGuid);
}

This works fine for INSTALL.PS1. However, for UNINSTALL.PS1, the following error message gets printed:

Executing script file 'C:\Users\Gael.SHARPCRAFTERS\Documents\Visual Studio 2010\Projects\TestNuGet\packages\PostSharp.2.1.0\tools\uninstall.ps1'.
Project unavailable.

Is there a way to avoid the "Project unavailable" error on uninstall? Or is there a better way to edit the project file?

Thank you.

-gael

Read the full discussion online.

To add a post to this discussion, reply to this email (nuget@discussions.codeplex.com)

To start a new discussion for this project, email nuget@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Developer
Apr 19, 2011 at 1:04 AM

Don't send a pull request for this change until you go through a code review on reviewboard. Also please make sure you follow the coding guidelines:

http://nuget.codeplex.com/documentation?title=Contributing%20to%20NuPack

Unrelated:

You never need to modify the xml directly or unload the project file. You can use the msbuild APIs to do all of this and then use the DTE project to save those changes so you don't cause a reload. Also, we're locked down for 1.3 so this change will have to go into the next version.

Apr 19, 2011 at 1:26 AM

Have you tried doing what you suggest here?

'cause it breaks in this exact way using the MSBuild APIs too :P (not the first time for me, so I knew what to look for -VsProjectSystem specifically)

re:unrelated: sure

/kzu from galaxy tab

Developer
Apr 19, 2011 at 3:07 AM
Edited Apr 19, 2011 at 3:08 AM

Try doing what exactly? Tweaking the xml? We do it in the AddReference call in VSProjectSystem.

Apr 19, 2011 at 3:08 AM
Changing the project via MSBuild APIs and not reloading? (or VS automagically reloading your changes, or whatever you meant with your phrase :P)


/kzu

--
Daniel Cazzulino | Developer Lead | MS MVP | Clarius Consulting | +1 425.329.3471


On Tue, Apr 19, 2011 at 00:07, dfowler <notifications@codeplex.com> wrote:

From: dfowler

Try doing what exactly?

Read the full discussion online.

To add a post to this discussion, reply to this email (nuget@discussions.codeplex.com)

To start a new discussion for this project, email nuget@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Developer
Apr 19, 2011 at 3:13 AM

http://nuget.codeplex.com/SourceControl/changeset/view/729b257354be#src%2fVisualStudio%2fProjectSystems%2fVsProjectSystem.cs

AddReference

Developer
Apr 19, 2011 at 3:16 AM

If thatt's not convincing enough:

http://nuget.codeplex.com/SourceControl/network/Forks/dfowler/nugetfutures/changeset/view/c1cb68701055#src%2fVsConsole%2fPowerShellCmdlets%2fNewSpecCmdlet.cs

Apr 19, 2011 at 3:22 AM
MSBuild.Evaluated instead of MSBuild.Construction.

Ah... "MSBuild APIs" meant only one thing :P

/kzu

--
Daniel Cazzulino | Developer Lead | MS MVP | Clarius Consulting | +1 425.329.3471


On Tue, Apr 19, 2011 at 00:16, dfowler <notifications@codeplex.com> wrote:

From: dfowler

Read the full discussion online.

To add a post to this discussion, reply to this email (nuget@discussions.codeplex.com)

To start a new discussion for this project, email nuget@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Apr 19, 2011 at 3:36 AM

*if* "MSBuild APIs" meant only one thing :P

Developer
Apr 19, 2011 at 3:39 AM

Hahah :) true statement. One other thing I realized (maybe we need to consolidate these) IVsPackageInstaller should have the same behavior. It's not deprecated, it's just the NoPIA API that's available for installing packages. 

Apr 19, 2011 at 3:45 AM
Giving this a shot in a plain install.ps1:

param($installPath, $toolsPath, $package, $project)

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Build") | out-null
$msbuild = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName)
$msbuild.Xml.AddImport($toolsPath + 'Funq.Build.targets')
$project.Save()


Getting an error:
"You cannot call a method on a null-valued expression."

the $msbuild.Xml is null :(. That looks like it should work! Any ideas if this might be a timing issue or something?
Don't recall anymore, but it might be the reason why I started using MSBuild.Construction quite some time ago instead of Evaluation... not sure...

/kzu

Apr 19, 2011 at 4:22 AM
Ok, here's a working one, FINALLY!!!!

param($installPath, $toolsPath, $package, $project)
$targetsFile = [System.IO.Path]::Combine($toolsPath, 'Funq.Build.targets')

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Build") | out-null
$msbuild = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
$projectUri = new-object Uri('file://' + $project.FullName)
$targetUri = new-object Uri('file://' + $targetsFile)
$relativePath = $projectUri.MakeRelativeUri($targetUri).ToString().Replace([System.IO.Path]::AltDirectorySeparatorChar, [System.IO.Path]::DirectorySeparatorChar)
$msbuild.Xml.AddImport($relativePath) | out-null
$project.Save()


(replace $targetsFile with your own .targets).

tomorrow I'll try to get the uninstall one working too.
Developer
Apr 19, 2011 at 4:23 AM

This doesn't matter but:
You don't need LoadWithPartialName powershell has:

Add-Type -AssemblyName Microsoft.Build
That looks about right :). Others might appreciate that code.

Apr 19, 2011 at 4:36 AM
PM> Add-Type -AssemblyName Microsoft.Build
Add-Type : Cannot add type. The assembly 'Microsoft.Build' could not be found.


Blogged at http://goo.gl/fb/7uX9t

/kzu

--
Daniel Cazzulino | Developer Lead | MS MVP | Clarius Consulting | +1 425.329.3471


On Tue, Apr 19, 2011 at 01:24, dfowler <notifications@codeplex.com> wrote:

From: dfowler

This doesn't matter but:
You don't need LoadWithPartialName powershell has:

Add-Type -AssemblyName Microsoft.Build
That looks about right :). Others might appreciate that code.

Read the full discussion online.

To add a post to this discussion, reply to this email (nuget@discussions.codeplex.com)

To start a new discussion for this project, email nuget@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Apr 19, 2011 at 4:37 AM
This does work:

Add-Type -AssemblyName 'Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

:)
Apr 19, 2011 at 10:39 AM

Thank you very much!

I did not know that the MSBuild project object was read-write.

-gael

Dec 2, 2011 at 12:06 AM
dfowler wrote:

Don't send a pull request for this change until you go through a code review on reviewboard. Also please make sure you follow the coding guidelines:

http://nuget.codeplex.com/documentation?title=Contributing%20to%20NuPack

Unrelated:

You never need to modify the xml directly or unload the project file. You can use the msbuild APIs to do all of this and then use the DTE project to save those changes so you don't cause a reload. Also, we're locked down for 1.3 so this change will have to go into the next version.

Can someone clarify this? "Never" is a mean, ugly word. See below:

http://www.jondavis.net/techblog/post/2011/11/16/Adding-Compiled-ResX-Resources-To-NuGet-Packages.aspx

Adding further changes I've found to be unresolvable via the API, I'm now getting the error message described, but during Install.ps1 ("Install-Package : Project unavailable").

Jon

Dec 12, 2011 at 9:18 PM
Edited Dec 12, 2011 at 9:21 PM

Reading the previous responses, does MSBuild provide a full layer of access to all of these details I am attempting to automate in the XML directly, such that ENVDTE did not, or does MSBuild provide a still small subset? If I use MSBuild as described in this thread, where MSBuild loads by filename ([Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName)) and then saves, does this indeed save to the existing memory structure already loaded? I presume that "GetLoadedProjects()" infers that "Loaded" specifically means projects currently loaded into VS, hence, yes.

I will attempt to move forward with these resources, hopefully someone can confirm I am on the right path:

http://www.odewit.net/ArticleContent.aspx?id=MsBuildApi4&lang=en

http://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.aspx

I am still waiting for a follow-up to the previous comment. Modifying the project file seemed to be necessary for setting the build mode for .resx's so that the project will immediately build, and subsequently for also prepopulating the deployment package settings for each build mode. We are using NuGet packages internally, using them quite frequently, and we want to automate all of these details internally rather than set everything up by hand.

Unfortunately, modifying the file directly causes all kinds of obviously anticipated problems. Recently we started seeing attempted rollbacks by NuGet (because of "Project unavailable"), although the package still installed and did not roll back. The latest problem, though, is that if Ankh is installed then when the "Reload project?" dialog appears Ankh will not monitor the project file change as it writes its own changes to the user options file, or somesuch, so the user gets this: "A project with that name is already opened in the solution", and the project gets unloaded. Reloading the project fails. The only way to fix this one is to close Visual Studio and delete the .suo file.

All of this goes away if we can figure out how to apply these changes without writing to the csproj file. 

May 25, 2012 at 5:49 PM

Just following up on my last post: we did, months ago, determine that my understanding on this was correct. We replaced our XML modification code with code that modified the XML via the MSBuild API as suggested earlier in this thread. Sadly, documentation was indeed sparse but we got it figured out. Unfortunately I don't think we have time to write up tutorial documentation. However, to those who are attempting to determine and validate this matter, consider it validated that MSBuild is indeed the correct path to directly manipulate the actual project XML. You would be manipulating a memory structure in an objects graph that ultimately saves to XML.

Apr 28, 2015 at 12:22 AM
Edited Apr 28, 2015 at 12:25 AM
Now that I've had subsequent need (with a completely different company and project) to manipulate the project, I am still looking for clarification on this.
never need to modify the xml directly or unload the project file. You can use the msbuild APIs to do all of this and then use the DTE project to save those changes so you don't cause a reload
I have poked at the MSBuild API myself and found it to be surprisingly straightforward,
  var prjRoot = Microsoft.Build.Construction.ProjectRootElement.Open(project.FullName);
but it looks to be the same in effect as using XmlDocument in that this API is in isolation and must still save the csproj which will still trigger the reload prompt. I thought the clarification on Microsoft.Build.Evaluation vs Microsoft.Build.Construction inferred that the .Evaluation lib would pull the in-memory objects instead of load an isolated memory structure, but then what did you mean, @dfowler, with this:
and then use the DTE project to save those changes
?????

The dte project provides various properties and it exposes .Save() but there is no handshaking mechanism between the MSBuild object structure and the DTE project. There is no DTE project property, for example, that says something like dteProject.Xml = msbuildProject.Xml; dteProject.Save(). If I'm manipulating the XML via the MSBuild API (in my case to add an Imports directive), how do I push that change into the the dte project object in-memory so that it does not trigger a reload?

Perhaps Microsoft.Build.Evaluation has its DTE-bound equivalent to Microsoft.Build.Construction.ProjectRootElement.Open(project.FullName) but as of yet I haven't found what that is.

I realize that this is out of the scope of NuGet but this is the ONLY thread in the entirety of the Googlze that seems to directly discuss the topic of manipulating the csproj via automation. (I don't appreciate the years-long continued silence. sniff )
Apr 28, 2015 at 5:04 PM
Edited Apr 28, 2015 at 5:05 PM
The earlier links into nuget code changesets are no longer working

I'm guessing it was
Microsoft.Build.Evaluation.Project targetProject = ProjectCollection.GlobalProjectCollection.LoadedProjects.FirstOrDefault((p) => p.FullPath == projectPath);
.. but as of VS 2013 that won't work anymore.

http://stackoverflow.com/questions/22378444/changing-project-metadata-in-visual-studio-2013-c-sharp