Config transformation with Webdeploy, Preview not working - visual-studio

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.

Related

VS 2017 builds code redundantly when switching from msbuild console build over to IDE (the first time)

VS 2017 sets LastActiveSolutionConfig for a web application on its project load, thus triggering subsequent build of that project, because:
The property is set in the respective .csproj.user file, which is created, if needed.
The .csproj.user file is part of project dependencies
So by creating it, VS causes the project to be built the next time.
Imagine building it all on the command line with msbuild after cleaning up the workspace, then switching back to VS and hitting the build button. And it is building again!
So, there are these stupid auto generated CS files related to workflows, that are generated only by VS, not msbuild (TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs and friends) - our msbuild generates them on purpose to satisfy VS. Check.
Next we ensure all the Copy to Output Directory files use PreserveNewest - check.
I already forgot what else we had to do to make sure VS does not rebuild code redundantly when switching from msbuild to IDE. Now this one, which is new.
How can I prevent VS 2017 from adding this property? Is it absolutely necessary to have it?
In the mean-time, I will modify the .csproj files by adding it and see if it helps. Nobody builds Release locally at our place and it is always AnyCPU platform, so I do not care about other configurations, except Debug|AnyCPU.
This is what I would do:
Set the build verbosity to Diagnostic.
Build and look for where the msbuild file is located that generates this file:
TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs
Look for some conditions that you can alter to prevent the file from being
generated.
Set some property to alter the condition and prevent that file from being generated.
My solution is to generate the .csproj.user files if needed with the expected property. Which is incredibly annoying that one has to do it. Here is the build code that can go into your Directory.Build.Targets:
<Target Name="EnsureCSProjUserForWebApplications"
Condition="'$(IsWebApplication)' == True And !Exists('$(MSBuildProjectFullPath).user')">
<ItemGroup>
<CSProjUserContent Include="<?xml version="1.0" encoding="utf-8"?>" />
<CSProjUserContent Include="<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">" />
<CSProjUserContent Include="<PropertyGroup>" />
<CSProjUserContent Include="<LastActiveSolutionConfig>Debug|Any CPU</LastActiveSolutionConfig>" />
<CSProjUserContent Include="</PropertyGroup>" />
<CSProjUserContent Include="</Project>" />
</ItemGroup>
<WriteLinesToFile File="$(MSBuildProjectFullPath).user" Lines="#(CSProjUserContent)" ContinueOnError="true" />
</Target>
IsWebApplication is computed like this:
<IsWebApplication>$(ProjectTypeGuids.Contains('349c5851-65df-11da-9384-00065b846f21'))</IsWebApplication>
Finally, the target is part of a larger series of targets that run at the beginning and validate the csproj matches our requirements or ensure certain conditions:
<PropertyGroup>
<EnsureXyzProjectSettingsDependsOn>
AssertIISExpress;
AssertNoAssemblyInfo;
AssertDebugSymbols;
AssertLocalApplicationHostFile;
AssertImportsDFVersioning;
EnsureSharedBinLink;
AssertSharedBinOutputPath;
AssertHintPaths;
EnsureCSProjUserForWebApplications
</EnsureXyzProjectSettingsDependsOn>
</PropertyGroup>
<Target Name="EnsureXyzProjectSettings"
DependsOnTargets="$(EnsureXyzProjectSettingsDependsOn)"
BeforeTargets="BeforeBuild"
Condition="'$(SuppressStrictXyzTargetsChecks)' != true" />

MSBuild: Copying a list of files into different locations based on file name using the Copy Task

I have a set of customer-specific configuration files that are located in a single Release folder at the time of the build. The file names are something like:
CustomerA_InstanceConfigurationX.config
CustomerA_InstanceConfigurationY.config
CustomerB_InstanceConfigurationX.config
CustomerB_InstanceConfigurationY.config
... etc.
During the build, I want to copy the customer-specific configuration files into a customer-specific Binaries folder:
$(BuildDirectory)\Binaries\Installers\CustomerA\ProductName\
$(BuildDirectory)\Binaries\Installers\CustomerB\ProductName\
So CustomerA_InstanceConfigurationX.config and CustomerA_InstanceConfigurationY.config would go into go into $(BuildDirectory)\Binaries\Installers\CustomerA\ProductName\ and so on.
How can I set the SourceFiles and the DestinationFolder properties to make this happen?
I have the list of Customers as a meta of an Instance property and set the SourceFiles and DestinationFiles around it:
<ItemGroup>
<ConfigFilesToCopy Include="$(BuildDirectory)\stage\InstallerDev\%(Instance.Customer)\Setup\bin\Release\%(Instance.Customer)_*.*" />
<DestionationsForConfigFiles Include="$(BuildDirectory)\Binaries\Installers\%(Instance.Customer)\InstallerDev\" />
</ItemGroup>
<Copy SourceFiles="#(ConfigFilesToCopy)" DestinationFolder="%(DestionationsForConfigFiles.FullPath)" />
That just copies all the customer .config files into all the customer-specific Binaries folder though.
Using the message task, %(Instance.Customer) outputs:
CustomerA
CustomerB
CustomerC.
%(ConfigFilesToCopy.Identity) outputs:
"C:\Builds\AgentX\YYY\INSTALLER_DEV Build\stage\InstallerDev\CustomerA\Setup\bin\Release\CustomerA_InstanceX.config"
"C:\Builds\AgentX\YYY\INSTALLER_DEV Build\stage\InstallerDev\CustomerA\Setup\bin\Release\CustomerA_InstanceY.config"
"C:\Builds\AgentX\YYY\INSTALLER_DEV Build\stage\InstallerDev\CustomerB\Setup\bin\Release\CustomerB_InstanceX.config"
"C:\Builds\AgentX\YYY\INSTALLER_DEV Build\stage\InstallerDev\CustomerB\Setup\bin\Release\CustomerB_InstanceY.config"
Etc.
%(DestionationsForConfigFiles.Identity) outputs:
C:\Builds\Agent8\Five0\INSTALLER_DEV Build\Binaries\Installers\CustomerA\InstallerDev\
C:\Builds\Agent8\Five0\INSTALLER_DEV Build\Binaries\Installers\CustomerB\InstallerDev\
C:\Builds\Agent8\Five0\INSTALLER_DEV Build\Binaries\Installers\CustomerC\InstallerDev\
Etc.
If someone could offer some help on achieving this or had a alternative approach for it, that'd be great. (E.g., I could re-organize the customer-specific configuration files into a customer-specific folders or something.) Thanks a lot in advance!
[** Update Note **: For now, I hard-coded each customer name into a ConfigFilesToCopy list item as well as a DestinationsForConfigFiles item.
<ConfigFilesToCopy Include="$(BuildDirectory)\stage\InstallerDev\CustomerA\Setup\bin\Release\CustomerA_*.*">
<DestionationsForConfigFiles>$(BuildDirectory)\Binaries\Installers\CustomerA\InstallerDev\</DestionationsForConfigFiles>
</ConfigFilesToCopy>
<ConfigFilesToCopy Include="$(BuildDirectory)\stage\InstallerDev\CustomerB\Setup\bin\Release\CustomerB_*.*">
<DestionationsForConfigFiles>$(BuildDirectory)\Binaries\Installers\CustomerB\InstallerDev\</DestionationsForConfigFiles>
</ConfigFilesToCopy>
This works, but I am basically wondering if it's possible to do the same thing without explicitly using the customer name so that I don't have to maintain this list every time we add a new customer.
Make the destination metadata of the files (I changed the paths so it better fits in SO's code box, but the idea is the same):
<Target Name="Copy">
<ItemGroup>
<ConfigFilesToCopy Include="$(SomeDir)\%(Instance.Customer)\%(Instance.Customer)_*.*">
<DestionationsForConfigFiles>$(SomeDir)\Binaries\%(Instance.Customer)</DestionationsForConfigFiles>
</ConfigFilesToCopy>
</ItemGroup>
<Message Text="Source=%(ConfigFilesToCopy.Identity) Dest=%(ConfigFilesToCopy.DestionationsForConfigFiles)" />
</Target>

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.

Checking a file out (TFS) for a pre-build action

I've added a pre-build action for an ASP.NET web control (server control) project, that runs jsmin.exe on a set of Javascript files. These output files are part of the source control tree and are embedded into the assembly.
The problem is when the pre-build runs, jsmin can't write the file as it's readonly. Is it possible to check the file out before hand? Or am I forced to set the file's attributes in the command line.
Any improved solution to the problem is welcome.
Update
One small issue with Mehmet's answer -you need to prepend the VS directory:
"$(DevEnvDir)tf" checkout /lock:none "$(ProjectDir)myfile"
If you're using Team Foundation Server, you can use team foundation command line utility (tf.exe) to check out the file(s) during pre-build and then check them back in during post-build. If you're using something else for source control, you can check if they have a command line tool like tf.exe.
If you do not want to check the files in as part of the build (which you normally wouldn't for this sort of thing) then I would simply set the attributes of the .js files before running jsmin on them. The easiest way of setting the files read-writeable is to use the the Attrib task provided by the MSBuild community extensions. The same community extensions also provide a JSCompress task for easily calling JSMin from MSBuild.
Therefore you'd have something like the following (not tested):
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<!-- rest of TFSBuild.proj file -->
<Target Name="AfterGet">
<Message Text="Compressing Javascript files under "$(SolutionRoot)"." />
<CreateItem Include="$(SolutionRoot)\**\*.js">
<Output TaskParameter="Include" ItemName="JsFiles"/>
</CreateItem>
<Attrib Files="#(JsFiles)" ReadOnly="false"/>
<JSCompress Files="#(JsFiles)" />
</Target>
Note that by modifying the files after getting them may well cause issues if you tried to move to an incremental build.

Resources