If you’ve ever created an MSDeploy web package using Visual Studio you may have noticed that the generated package has the folder structure of where the application was packaged. Since MSDeploy parameters are used when installing the package in most cases the structure of the web package doesn’t matter. In some cases this causes problems and it would be desired to have a flatter package structure.
Today on twitter @ashic contacted me asking basically “How can I update the package folder structure without modifying the project?” OK it’s possible, but I’ll first explain how you can easily solve this problem in a few ways and then move on to his actual question.
Option 1: Add the PackageWeb NuGet package
The easiest way to fix this problem is to add a the PackageWeb NuGet package into your project (that’s a package that I’ve authored you can see the sources here). This will update the path to a flat structure and add a .ps1 file when creating the package. You can ignore the .ps1 file if you like.
Option 2: Add package.wpp.targets to your project
In web projects when you build it will import any files in the same folder as the .csproj/.vbproj file matching the following file pattern *.wpp.targets. To fix the issue drop the following contents in to that file. Note: you can find the latest version of this in this gist.
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <PackagePath Condition=" '$(PackagePath)'=='' ">website</PackagePath> <EnableAddReplaceToUpdatePacakgePath Condition=" '$(EnableAddReplaceToUpdatePacakgePath)'=='' ">true</EnableAddReplaceToUpdatePacakgePath> <PackageDependsOn> $(PackageDependsOn); AddReplaceRuleForAppPath; </PackageDependsOn> </PropertyGroup> <Target Name="AddReplaceRuleForAppPath" Condition=" '$(EnableAddReplaceToUpdatePacakgePath)'=='true' "> <PropertyGroup> <_PkgPathFull Condition=" '$(WPPAllFilesInSingleFolder)'!='' ">$([System.IO.Path]::GetFullPath($(WPPAllFilesInSingleFolder)))</_PkgPathFull> <_PkgPathFull Condition=" '$(_PkgPathFull)' == '' ">$([System.IO.Path]::GetFullPath($(_PackageTempDir)))</_PkgPathFull> </PropertyGroup> <!-- escape the text into a regex --> <EscapeTextForRegularExpressions Text="$(_PkgPathFull)"> <Output TaskParameter="Result" PropertyName="_PkgPathRegex" /> </EscapeTextForRegularExpressions> <ItemGroup> <MsDeployReplaceRules Include="replaceFullPath"> <Match>$(_PkgPathRegex)</Match> <Replace>$(PackagePath)</Replace> </MsDeployReplaceRules> </ItemGroup> </Target> </Project>
Then when you build this project the package path will be consist of Content\website and all content files are under that.
Now on to his question, “How can I update the package path for a project without modifying the project/project files when building from the command line”"?”
Now that we have the MSBuild .targets file to do the work for us the only thing we need to figure out is how to add this .targets file into the build process when calling msbuild.exe myproject.csproj /t:Package. It’s pretty easy actually. You can take the package.wpp.targets file and drop it in a well known location (let’s say c:\msbuild\package.targets for this example). Then when you build your project you can pass a property to get that file imported. The command is below.
msbuild myproject.csproj /t:Package /p:CustomAfterMicrosoftCommonTargets=c:\msbuild\package.targets
In Microsoft.Common.targets (which is imported by most project types) contains a property CustomAfterMicrosoftCommonTargets which defaults to a folder under Program Files. You can override that value via MSBuild parameters to override it which is what we are doing here. Note: if you have a common targets file in the default shared location then this obviously will not work for you. You'd have to add another conditional import with a new property for that case.
Sayed Ibrahim Hashimi