Can MSBuild exclude "Hidden" Web Deploy parameters from the generated SetParameters.xml? - visual-studio-2010

In my Parameters.xml file, I have a couple of parameters that use the Web Deploy "variable" syntax to refer to other parameters, like this one that refers to the IIS Web Application Name parameter:
<parameter name="MyParam"
defaultValue="{IIS Web Application Name}/Web.config"
tags="Hidden"/>
My problem is that VS automatically imports this parameter into my SetParameters.xml file when I build the deployment package in spite of it being tagged as hidden. When it is passed to msdeploy via setParamFile, Web Deploy literally interprets the value of the parameter as
{IIS Web Application Name}/Web.config
rather than substituting the IIS application name.
If I remove the parameter from the auto-generated SetParameters.xml file, the variable works as expected. Is there any way to prevent VS from including that parameter in the first place, either by name or by tag?

This was actually far easier than I thought, given the answer to my earlier question.
I just needed to add a Hidden tag in the target that follows AddIisAndContentDeclareParametersItems. This apparently sets the tag in the source manifest prior to the package being built. It ends up looking something like this:
<Target Name="DeclareCustomParameters"
AfterTargets="AddIisAndContentDeclareParametersItems">
<ItemGroup>
<MsDeployDeclareParameters Include="Foo">
<!-- <snip> -->
<!-- the following elements are the important ones: -->
<Tags>Hidden</Tags>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>
That was it!

This answer is for anyone else looking for a more complete example of substitution via targets. This example shows substituting a variable "database server name" into a connection string.
The ExcludeFromSetParameter element appears to be the key to making substitution work as it keeps the param out of the SetParameters.xml file (as the OP mentioned he did manually). Unfortunately, I don't think that ExcludeFromSetParameter can be set from a parameters.xml file, so this is the only option...
<Target Name="DeclareCustomParameters" BeforeTargets="Package">
<ItemGroup>
<MsDeployDeclareParameters Include="DatabaseServer">
<Description>Location of the database server hosting the user database</Description>
<Value>localhost</Value>
<DefaultValue>localhost</DefaultValue>
<Tags>DBServer, SQL</Tags>
</MsDeployDeclareParameters>
<MsDeployDeclareParameters Include="DB Connection String">
<Kind>XmlFile</Kind>
<Scope>Web.config</Scope>
<Match>/configuration/connectionStrings/add[#name='Database']/#connectionString</Match>
<Description>The connection string to the Database</Description>
<DefaultValue>Data Source={DatabaseServer};Initial Catalog=MyDatabase;Integrated Security=true;MultipleActiveResultSets=true;</DefaultValue>
<Tags>Hidden</Tags>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>

Related

Config transformation with Webdeploy, Preview not working

I want to use config transformation for custom files, if I replace ??? with MSDeployPublish, the Preview functionality is not working and it replaces the local files in solution. However the publish works without using Preview keeping local files untouched.
Config files:
AppSettings.config
└AppSettings.Test.config
└AppSettings.Stage.config
└AppSettings.Release.config
ConnectionString.config
└ConnectionString.Test.config
└ConnectionString.Stage.config
└ConnectionString.Release.config
What is the correct name of the target to use transformation for preview (without changing the files in soulution)? The way that web.config works when making preview with webDeploy
<Target Name="???">
<TransformXml Source="App_Config\AppSettings.config" Destination="App_Config\AppSettings.config" Transform="App_Config\AppSettings.$(Configuration).config" />
<TransformXml Source="App_Config\ConnectionStrings.config" Destination="App_Config\ConnectionStrings.config" Transform="App_Config\ConnectionStrings.$(Configuration).config" />
</Target>
I don't want to use any extensions like SlowCheetah, just build in functionalities.
<Target Name="Build"> also works
UPDATE 1
I've succeeded to publish without change local config files:
Destination="$(_PackageTempDir)\App_Config\ConnectionStrings.config"
But the Preview still not transforming the configs (also when I define a message for output it is not appear). What black magic happens when I click "Preview", in the publish screen ?
I've tried with targets: TransformWebConfigCore,CopyAllFilesToSingleFolderForPackage ,GatherAllFilesToPublish
I've made it.
The Target name should be a custom name that doesn't exist. Then AfterTargets attribute should be specified with value of Package. This target happens almost at the end of the chain and ensures that the $(_PackageTempDir) directory is created.
The transformations are done in the package directory. This way ensures when making a preview with webdeploy it will compare files correctly and without change local files in source control.
Here is the definition:
<Target Name="CustomConfigTransform" AfterTargets="Package">
<TransformXml Source="App_Config\AppSettings.config" Destination="$(_PackageTempDir)\App_Config\AppSettings.config" Transform="App_Config\AppSettings.$(Configuration).config" />
<TransformXml Source="App_Config\ConnectionStrings.config" Destination="$(_PackageTempDir)\App_Config\ConnectionStrings.config" Transform="App_Config\ConnectionStrings.$(Configuration).config" />
</Target>
Of course there could be a better way: in the target CollectWebConfigsToTransform from Microsoft.Web.Publishing.targets, it should know somehow that there are more config files for transform. But currently no idea.

Setting CultureInfo before compilation and always resetting after

I am trying to execute a task which changes the locale/culture used at compile time for a XNA content pipeline project, and restores the original after the compilation ended. The intention is to allow proper parsing of floats in non-English machines.
So far I am using BeforeBuild and AfterBuild like this:
<UsingTask TaskName="PressPlay.FFWD.BuildTasks.SetLocale" AssemblyFile="PressPlay.FFWD.BuildTasks.dll" />
<Target Name="BeforeBuild">
<SetLocale> <!-- By default, set to 'en-US' -->
<Output TaskParameter="PrevLocale" ItemName="OrigLocale" />
</SetLocale>
</Target>
<Target Name="AfterBuild">
<SetLocale Locale="#(OrigLocale)" />
</Target>
It works properly, except when an error occurs during compilation (an invalid XML or ContentSerializer error), after which the locale will not be reset.
Answers in SO are contradictory, since some say AfterBuild always executes (not in my case) and others say there's no way to ensure a target is always ran after build. I haven't found precise info regarding this around google.
I know there is the option of using PostBuildEvent and setting it to always run, but it'd use Exec to run the command and I suspect it would run in a separate thread, defeating its purpose (I set CurrentThread.CultureInfo to change the locale).
So, is there a way to ensure the target is always ran? Alternatively, is there any other way to tell VS2010 to run a compilation with a specific culture?
Links to documentation explicitly clarifying the issue would be very appreciated.
-- Final solution, following Seva's answer --
XNA's content pipeline does not declare PreBuildEvent nor PostBuildEvent. Other required properties (RunPostBuildEvent, PreBuildEventDependsOn and PostBuildEventDependsOn) aren't defined, either. However, if you define them, the content pipeline will make good use of them as in any other project.
So, the changes I had to make to the contentcsproj file were:
<!-- Added to ensure the locale is always restored -->
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<!-- Reference includes, project references and other stuff -->
<!-- ... -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\$(XnaFrameworkVersion)\Microsoft.Xna.GameStudio.ContentPipeline.targets" />
<!-- Customizations to change locale before compilation and restore it after -->
<!-- Needed to properly treat dots in the XMLs as decimal separators -->
<UsingTask TaskName="PressPlay.FFWD.BuildTasks.SetLocale" AssemblyFile="PressPlay.FFWD.BuildTasks.dll" />
<!-- Apparently ContentPipeline.targets does not define PreBuildEvent and PostBuildEvent -->
<!-- However, they are still used if defined -->
<Target Name="PreBuildEvent" DependsOnTargets="$(PreBuildEventDependsOn)"/>
<Target Name="PostBuildEvent" DependsOnTargets="$(PostBuildEventDependsOn)"/>
<PropertyGroup>
<PreBuildEventDependsOn>
$(PreBuildEventDependsOn);
EstablishUSLocale
</PreBuildEventDependsOn>
</PropertyGroup>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
RestoreOriginalLocale
</PostBuildEventDependsOn>
</PropertyGroup>
<Target Name="EstablishUSLocale">
<SetLocale Locale="en-US">
<Output TaskParameter="PrevLocale" ItemName="OrigLocale" />
</SetLocale>
</Target>
<Target Name="RestoreOriginalLocale">
<SetLocale Locale="#(OrigLocale)" />
</Target>
With this solution another problem is indirectly taken care of, which is the potential issues that could arise if another project redefined BeforeBuild or AfterBuild, resulting in one of the definitions overriding the other.
You can use PostBuildEvent, because you can configure to execute it always after the build. However as you correctly noticed, using Exec task will not work here. However PostBuildEvent is actually extendable through a property called $(PostBuildEventDependsOn). You will need to define this property:
<PropertyGroup>
<PostBuildEventDependsOn>RestoreOriginalLocale</PostBuildEventDependsOn>
</PropertyGroup>
The target RestoreOriginalLocale is what you had in your AfterBuild target:
<Target Name="RestoreOriginalLocale">
<SetLocale Locale="#(OrigLocale)" />
</Target>
Your BeforeBuild target is still needed, it remains as what you wrote in your question.
To ensure PostBuildEvent is executed on failure (and thus require RestoreOriginalLocale to be executed), you need to set property RunPostBuildEvent to Always. You can do it through IDE, or by manually editing your .csproj file.

Can Web Deploy's setAcl provider be used on a sub-directory?

I'm trying to make a subdirectory in an MS Deploy package writable to the application pool user. Thanks to a helpful post about the setAcl provider by Kevin Leetham I was able to get most of what I need into my project file:
<MsDeploySourceManifest Include="setAcl"
Condition="$(IncludeSetAclProviderOnDestination)">
<Path>$(_MSDeployDirPath_FullPath)\doc\public</Path>
<setAclAccess>Read,Write,Modify</setAclAccess>
<setAclResourceType>Directory</setAclResourceType>
<AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
</MsDeploySourceManifest>
Note that I've added "\doc\public" to the root deployment directory. In the resulting manifest that VS2010 builds, I see the following setAcl element:
<sitemanifest>
<contentPath path="C:\Source\...\obj\Debug\Package\PackageTmp" />
<setAcl path="C:\Source\...\obj\Debug\Package\PackageTmp"
setAclResourceType="Directory" />
<setAcl path="C:\Source\...\obj\Debug\Package\PackageTmp"
setAclUser="anonymousAuthenticationUser"
setAclResourceType="Directory" />
<setAcl path="C:\Source\...\obj\Debug\Package\PackageTmp\doc\public"
setAclResourceType="Directory"
setAclAccess="Read,Write,Modify" />
</sitemanifest>
That last line looks good: it's appended the subdirectory I want to be writable, and the access modifiers all seem to have transferred over well enough.
However, when I deploy this package I receive an error:
Error: A value for the 'setAclUser' setting must be specified when the
'setAcl' provider is used with a physical path.
This is a confusing error because I'm not trying to set an ACL on a physical path, exactly, but a subdirectory of a web application. Looking at the output of MS Deploy, it's easy to see the problem:
Info: Adding setAcl (REST Services\1.0.334).
Info: Adding setAcl (REST Services\1.0.334).
Info: Adding setAcl (C:\...\obj\Release\Package\PackageTmp\doc\public).
MS Deploy is apparently substituting the web application name for my absolute path "C:...\obj\Release\Package\PackageTmp", but when I append "\doc\public" to that absolute path it no longer recognizes it as a web application directory. This exact problem is described by another victim over on the ASP.NET forums without any resolution.
Does anyone know how to set an ACL on a particular subdirectory of a web application via Web Deploy without manually identifying the physical path and application pool user on the target host?
OK let me first say that this is way harder than it should be!
I think the reason why it is failing is because when you are publishing it cannot recognize the folder as being a folder in the IIS Application. The reason this is happening is because the full path is being transferred to the destination when the SetAcl provider is invoked. Instead of that we need an path which is relative to the IIS Application. For instance in your case it should be something like : "REST SERVICES/1.0.334/doc/public". The only way to do this is to create an MSDeploy parameter which gets populated with the correct value at publish time. You will have to do this in addition to creating your own SetAcl entry in the source manifest. Follow the steps below.
In the same directory as your project create a file with the name {ProjectName}.wpp.targets (where {ProjectName} is the name of your Web application project)
Inside the file paste the MSBuild content which is below this list
Reload the project in Visual Studio (VS caches the project files in memory so this cache needs to be cleared).
{ProjectName}.wpp.targets
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="SetupCustomAcls" AfterTargets="AddIisSettingAndFileContentsToSourceManifest">
<!-- This must be declared inside of a target because the property
$(_MSDeployDirPath_FullPath) will not be defined at that time. -->
<ItemGroup>
<MsDeploySourceManifest Include="setAcl">
<Path>$(_MSDeployDirPath_FullPath)\doc\public</Path>
<setAclAccess>Read,Write,Modify</setAclAccess>
<setAclResourceType>Directory</setAclResourceType>
<AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<Target Name="DeclareCustomParameters" AfterTargets="AddIisAndContentDeclareParametersItems">
<!-- This must be declared inside of a target because the property
$(_EscapeRegEx_MSDeployDirPath) will not be defined at that time. -->
<ItemGroup>
<MsDeployDeclareParameters Include="DocPublicSetAclParam">
<Kind>ProviderPath</Kind>
<Scope>setAcl</Scope>
<Match>^$(_EscapeRegEx_MSDeployDirPath)\\doc\\public$</Match>
<Value>$(_DestinationContentPath)/doc/public</Value>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>
</Project>
To explain this a bit, the target SetupCustomAcls will cause a new SetAcl entry to be placed inside of the source manifest used during publishing. This target is executed after the AddIisSettingAndFileContentsToSourceManifest target executes, via the AfterTargets attribute. We do this to ensure that the item value is created at the right time and because we need to ensure that the property _MSDeployDirPath_FullPath is populated.
The DeclareCustomParameters is where the custom MSDeploy parameter will be created. That target will execute after the AddIisAndContentDeclareParametersItems target. We do this to ensure that the property _EscapeRegEx_MSDeployDirPath is populated. Notice inside that target when I declare the value of the parameter (inside the Value element) that I use the property _DestinationContentPath which is the MSBuild property containing the path to where your app is being deployed, i.e. REST Services/1.0.334.
Can you try that out and let me know if it worked for you or not?
FYI - this does work for a root website if you follow the convention specified in the post here:
http://forums.iis.net/p/1176955/1977169.aspx#1977169
<Match>^$(_EscapeRegEx_MSDeployDirPath)\\#(CustomDirAcl)$</Match>
<DefaultValue>{$(_MsDeployParameterNameForContentPath)}/#(CustomDirAcl)</DefaultValue>
<Value>$(_DestinationContentPath)/#(CustomDirAcl)</Value>
This post also has the benefit of being able to specify a block of subdirectories in a single ItemGroup.

Visual Studio 2010: How to publish an ASP.NET web app to a target folder with MSBUILD?

In Visual Studio 2010, you know how you can change your configuration (debug, release, etc), right-click a project in the solution explorer, click publish, and have all the important web app project files for the selected configuration copied to a target folder along with an xdt-transformed web.config? Well, I am looking for the MSBUILD equivalent of exactly that.
My challenge to you: Provide the ONE LINE that I need to execute at my command prompt in order to accomplish this. No third party programs. No tutorial videos. Just a single, straight-up command that I can literally copy from your response, paste into a command window, modify as necessary to support my directory structure, and then hit enter.
If not, then perhaps someone could provide a link to a complete MSBUILD reference showing every command, switch, and value I can use at the command line.
Put the below to ProjectPublish.MSBuild.xml file (change PropertyGroup as needed):
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Publish">
<PropertyGroup>
<ProjectFile>Path\To\Web.csproj</ProjectFile>
<PublishDir>Path\For\Publish\Output</PublishDir>
<TempDir>Path\To\Temp\Folder</TempDir>
<BuildConfig>Release|Debug</BuildConfig>
</PropertyGroup>
<Target Name="Publish">
<MSBuild Projects="$(ProjectFile)"
Properties="Configuration=$(BuildConfig);WebProjectOutputDir=$(PublishDir);OutDir=$(TempDir)\;BuildingProject=true"
Targets="ResolveReferences;_CopyWebApplication" />
</Target>
</Project>
Calling this from command line (or .bat file) should do the trick:
%windir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe ProjectPublish.MSBuild.xml
I found the solution I was looking for after all these months here
In case the above link goes bad, here's the skinny of what it says:
Unload then edit your project file. Look for the line where it's importing Microsoft.WebApplication.targets. Will look like:
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
Beneath that line, paste in this XML:
<Target Name="PublishToFileSystem" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
<Error Condition="'$(PublishDestination)'==''" Text="The PublishDestination property must be set to the intended publishing destination." />
<MakeDir Condition="!Exists($(PublishDestination))" Directories="$(PublishDestination)" />
<ItemGroup>
<PublishFiles Include="$(_PackageTempDir)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(PublishFiles)" DestinationFiles="#(PublishFiles->'$(PublishDestination)\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="True" />
</Target>
Now, run this in a command prompt within the same folder as your project file:
msbuild TestWebApp.csproj "/p:Platform=AnyCPU;Configuration=Debug;PublishDestination=C:\pub" /t:PublishToFileSystem
Remember to specify the path to MSBUILD in the command or add the path to your global path environmental variable (which is what I did). On my machine, it was here:
C:\Windows\Microsoft.NET\Framework\v4.0.30319
To test this, I put a config transform in my Web.Release.config to add an AppSetting key (if you do this, make sure the AppSettings node is present in your base config file or you will get an error). When I used the above command to build the debug configuration, the key was not present in the published config file as expected. However, when I used the release config, the key was successfully added to the file.
I really wish Microsoft hadn't obfuscated the heck out of this. At any rate, this is the simplest solution I have found anywhere on the internet. I hope it helps the rest of you.

Ignore file from delete during WebDeploy

I'm using TeamCity to build and deploy a collection of MVC Applications via msbuild and WebDeploy.
In a step previous to my solution build/deploy, I copy an app_offline.htm to the deploy directory so that I can perform SQL updates and other web/solution management steps including the build.
One of the setting in the WebDeploy is to delete files that aren't included in the project, or not needed to run the site. This deletes my app_offline.htm file each time. While I understand this is kind of the desired result, is there a way to exclude this file from being deleted from the deployment directory upon the deploy?
I've tried adding an ItemGroup with the ExcludeFromPackageFiles option, with no results.
I had a similar problem, wanting to keep minified javascript files in the deployment package even though they're not part of the project.
I added a custom MSBuild target for this, that works for me:
<!-- ====== Package the minify files ===== -->
<PropertyGroup>
<CopyAllFilesToSingleFolderForPackageDependsOn>
CustomCollectFiles1;
$(CopyAllFilesToSingleFolderForPackageDependsOn);
</CopyAllFilesToSingleFolderForPackageDependsOn>
</PropertyGroup>
<PropertyGroup>
<AfterAddIisSettingAndFileContentsToSourceManifest>
MakeEmptyFolders
</AfterAddIisSettingAndFileContentsToSourceManifest>
</PropertyGroup>
<Target Name="CustomCollectFiles1">
<ItemGroup>
<!-- =====Controls\Javascript folder ==== -->
<_CustomFilesForRootFolder Include=".\Controls\Javascript\*.min.js">
<DestinationRelativePath>%(RecursiveDir)%(Filename)%(Extension) </DestinationRelativePath>
</_CustomFilesForRootFolder>
<FilesForPackagingFromProject Include="%(_CustomFilesForRootFolder.Identity)">
<DestinationRelativePath>.\Controls\Javascript\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
This other question " Custom app_offline.htm file during publish " suggests one possible way for the final result you describe:
I use my own
app_offline.htm_
file in the solution, which gets
published. My deployment script then
renames it (removing the trailing _)
to make it active.
I can then run my db scripts/do
whatever then rename the file bringing
the site back.

Resources