NuGet silently overwrites some files

On package update NuGet silently overwrites some files. Consider You have something like this in Your nuspec file:

    <file src="MyPackage\App.xaml.cs.pp" target="content\App.xaml.cs.pp" />
    <file src="MyPackage\App.xaml.pp" target="content\App.xaml.pp" />

Consider the user changed App.xaml.cs but didnt changed App.xaml. In this case when user updates Your package the App.xaml.cs file will be silently overwritten!

After a short search session on the net I found a same problem without any solutions. So here is mine install.ps1 with a workaround:

param($installPath, $toolsPath, $package, $project)
$appXamlFileName = $item.Properties.Item("FullPath").Value
Add-Content $appXamlFileName "<!-- this comment added by nuget package's install script to modify this file to workaround bug which overwrites App.xaml.cs file on next package update if this one isnt modified -->" 

On update NuGet will detect that App.xaml changed and will skip it AND App.xaml.cs file too!

Howto debug NuGet package’s install.ps1

While searching for solution around install.ps1 I quickly realised that the edit file -> create package -> publish -> update target cycle isnt very handly. There are step-by-step instructions on the net how to debug install.ps1, but when I tried to use them I run into new problems about incompatibility between my packages and nuget powershell cmdlets.

So I used a poor mans solution instead:

  1. Add some dummy content file to You package.
  2. Install Your package.
  3. Edit that dummy file (each next package install cycle will ask You about overwriting and that is what we need!)
  4. Open Package Manager Console (PMC) in VS
  5. Execute “Set-PsDebug -trace 2”. It helps You later.
  6. Open the install.ps1 in some editor (I used PowerShell ISE) from target project’s packages directory.
  7. In PMC run “update-package _yourpackagename_ -reinstall”. The installation will stop because file overwriting.
  8. In the PS editor edit and save install.ps1, but dont close it.
  9. Back to PMC and answer the overwriting question (with ‘L’ or ‘N’ to keep the modified file).
  10. See the results.
  11. Go to 7.

Not to smart, didnt uses tools and libraries but it works in acceptable cycle time.

Setting project item’s BuildAction from NuGet package

I created a package for internal use which has an App.xaml file in content. Naturally I would like to find it in target project with BuildAction set to “ApplicationDefinition”, but Visual Studio treats it as “Page”, because it is the xaml extension default.

I found a hopeful solution here promising a fast track. My first version of install.ps1 was this:

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

$item = $project.ProjectItems.Item("App.xaml")
$item.Properties.Item("BuildAction").Value = ???

The problem raised when I didnt found the “ApplicationDefinition” value in prjBuildAction enumeration on MSDN…

I found some similar examples on the net. Some of them have comment with a question: what to do if someone wants to set a value not in the enumeration? Neither of those comments has an answer which distressed me a bit.

Here I found a clue so I tried to enumerate those undefined prjBuildAction values with this code:

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

Add-Type -AssemblyName 'Microsoft.Build.Engine'
$msbuildproject = new-object Microsoft.Build.BuildEngine.Project
$msbuildproject.Load($project.FullName)

[System.Collections.ArrayList]$buildActions = "None", "Compile", "Content","EmbeddedResource"

$msbuildproject.ItemGroups | Where-Object { $_.Name -eq "AvailableItemName" } | Select-Object -Property "Include" | ForEach-Object {
  $act = $_
  $buildActions.Add($act)
}

$item = $project.ProjectItems.Item("App.xaml")
$item.Properties.Item("BuildAction").Value = [int]$buildActions.IndexOf("ApplicationDefinition")

Dont know why but the enumeration of the ItemGroups didnt worked. When I did a

Write-Host ($msbuildproject.ItemGroups | Format-List | Out-String)

it showed me a nice list of BuildItems, but when I run with a Where-Object against it I found nothing. The problem should be in PS syntax or the object instances. I dont maintain my PS knowledge which is based on my .Net and Linux scripting practice combined with looking for snippets from code examples around. I simply dont want to go more deeply into it because I feel PS is something “created” and not “born” if You understand what I mean.

I rewrite the script to get the available BuildAction values as follows:

...
$msbuildproject.ItemGroups | ForEach-Object {   
    $ig = $_
    @($ig.GetEnumerator()) | ForEach-Object { 
        $i = $_
        if ($i.Name -eq "AvailableItemName")
        {
            $buildActions.Add($i.Include);
        }
    }
...

And voilá, I got a nice list of values in $buildActions:

None
Compile
Content
EmbeddedResource
CodeAnalysisDictionary
ApplicationDefinition
Page
Resource
SplashScreen
DesignData
DesignDataWithDesignTimeCreatableTypes
EntityDeploy
XamlAppDef

My $idx becomes: 5, I checked value in Solution Explorer… and found “CodeAnalysisDictionary” there. No problem, it should be some 0/1 based indexing problem, lets set $idx+1, run, check, value okay! Lets try something other just for to be sure… again bad value! Unfortunately it seems the order of values collected with this algorithm isnt good as the link I mentioned above imply. Back to the start line.

While looking for solutions I somewhere found something else about ProjectItem’s “ItemType” property, so I tried to play with it. And suddently the Sun raised, the sky becomes blue, etc.:

param($installPath, $toolsPath, $package, $project)
$item = $project.ProjectItems.Item("App.xaml")
$item.Properties.Item("ItemType").Value = "ApplicationDefinition"

So simple and it works!