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

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>

Related

MSBuild ZipDirectory Output Different when Building Solution and Building Individual Project

I'm having an issue with MSBuild ZipDirectory command, where the zipped output file differs between when built by individual project and when built for entire solution.
For example,
The expected zip file looks like below:
A.txt
B.txt
C.txt
The zipped file looks as expected when built as individual project.
However,
When solution is build as a whole,
The zipped file looks like below, missing some files:
A.txt
B.txt
What is causing such issue??
MSBuild ZipDirectory Output Different when Building Solution and
Building Individual Project
I wonder if you use ZipDirectory task to compress some certain files in your project. I have tested the command in different projects in the same solution and did not face the same issue as you said. So please check this:
1) If you just compress some content files in the project, try to create a new folder in Solution Explorer called resource and then put any files you want to compress into this. And remember to set this target executes after build process.
<Target Name="ZipOutputPath" AfterTargets="Build">
<ZipDirectory
SourceDirectory="$(MSBuildProjectDirectory)\resource"
DestinationFile="$(MSBuildProjectDirectory)\output.zip" />
</Target>
2) If you want to compress the files of the output folder, please make sure that you have set Copy to Output Directory of the specific files to Copy if newer.
Note :please check if there are some extra targets which will delete some files like C.txt or there is some extra condidtions to limit this.
Build Solution means that it can build all the projects at the same time so I wonder if some extra projects have some configuration which will cause this behavior in the first project.
3) Besides, you can also try zip task to realize this:
<Target Name="zipfiles" AfterTargets = "Build">
<ItemGroup>
<ZipFiles Include="xxxxx\A.txt" />
<ZipFiles Include="xxxxx\B.txt" />
<ZipFiles Include="xxxxx\C.txt" />
</ItemGroup>
<Zip OutputFilename="$(OutputPath)Project.zip" Files="#(ZipFiles)" />
</Target>
In addition, if all of these did not help, there might be a situation where you might have a problem with your VS environment. Due to it, you can follow these steps:
A) close VS instance, delete .vs hidden folder under the solution path,bin and obj folder. Then restart your solution and then build again to see whether the issue persists.
B) use devenv /safemode to start VS and then test whether the issue is caused by third party extensions, packages.
C) do a repair in VS Installer.
If I misunderstand your issue, please share more detailed info and feel free to let us know.

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.

Visual studio 2017 Excludefoldersfromdeployment but include some subfolder

I was wondering if it's possible to include some sub-folder on publication, of an excluded folder.
Let's assume i have this structure:
/Folder1/
/Folder1/FileX
/Folder1/FileY
/Folder1/SubFolder1/
/Folder1/SubFolder2/
What i want is to exclude the whole Folder1 content, but include only a specific set of sub-folder (in my example SubFolder1).
The Folder1 folder is excluded with the ExcludeFoldersFromDeployment in the .pubxml:
<ExcludeFoldersFromDeployment>Folder1</ExcludeFoldersFromDeployment>
You haven't told us what this .pubxml file is, so there is a limit to what we can help you with.
But in general:
The construct that deals with files and folders in MSBuild is Items.
You want an Item here, not an MSBuild property.
So you could easily use an item to point to a specific sub folder in your build environment like this:
<ItemGroup>
<!-- This will grab all files in SubFolder1 but not recursively -->
<DeployThese Include="/Folder1/SubFolder1/*.*" />
</ItemGroup>
You can then do anything you want with that Item. You could copy the files in it somewhere else, or anything else with them.
The files are accessed later by using #(DeployThese)
I was wondering if it's possible to include some sub-folder on
publication, of an excluded folder.
Yes, it’s possible.
Note: One point we should know, as you’ve used “ExcludeFoldersFromDeployment” element in .pubxml to exclude the entire Folder1 directory. Some deploy actions in .xxproj file may be overwritten or affected by it.
So, to achieve your goal we have to follow two steps:
1. Copy the SubFolder to a new folder (A new folder in $(ProjectDir) can be better)
2. Add the content of SubFolder to publish, and choose the structure you want
Here’s a workaround:
1: Add a PreBuildEvent prorerty in .csproj file.
<PropertyGroup>
<PreBuildEvent>xcopy "$(ProjectDir)/Folder1/SubFolder1" "$(ProjectDir)/NewFolder" /E /Y /I</PreBuildEvent>
</PropertyGroup>
2: Add following markup to .csproj file, it helps publish extra files to publish folder in Asp.net:
<PropertyGroup>
<PipelineCollectFilesPhaseDependsOn>
CustomCollectFiles;
$(PipelineCollectFilesPhaseDependsOn);
</PipelineCollectFilesPhaseDependsOn>
</PropertyGroup>
<Target Name="CustomCollectFiles">
<ItemGroup>
<_CustomFiles Include="NewFolder\**\*" />
<FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
<DestinationRelativePath>MyStructureUnderPublishFolder\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
For this markup, we only need to change two paths to meet our needs.
First:The CustomFiles refers to the folder whose content will be published.
Second:The MyStructureUnderPublishFolder refers to the structure you want under publish folder. If you want a Folder1 which only has a SubFolder in it after deployment, change it to Folder1/SubFolder1, or change it to SubFolder1 if you want a simple Subfolder1 under Publish folder.
More information about adding extra files to publish see here.

MSBuild ItemGroup empty after Clean

I am seeing some strange behavior with the ItemGroup tag.
My application depends on several DLLs, and I am copying these DLLs as well as the executable to a deploy directory, which is used by NSIS to create an install package from a fresh build. However, I am running into an odd issue with this.
I define my ItemGroup as follows (at the top of the file before I define my build target:
<MyAppFiles Include="$(ProjectRoot)\$(OutputPath)\*.dll;$(ProjectRoot)\$(OutputPath)\MyApp.exe" />
So, this grabs all the DLLs in the directory as well as the binary MyApp.exe. But, if the project has been cleaned (i.e., there are no files in $(OutputPath)). The DLLs are not included in the ItemGroup list of files. Now, if I follow this up with another build, (i.e., there are files from the previous build in $(OutputPath)) the ItemGroup contains all the files I want.
Also, I have checked the output of the build script and the DLLs are copied to $(OutputPath) whether a clean happened or not.
So, my question is, how do I correct my build script such that ItemGroup always contains the DLLs? It seems like the ItemGroup populates with files before the build happens, so if the files aren't there before the build, they aren't included in the list, but if they exist before the build then they are.
For reference, here is the build target that I am using:
<PropertyGroup>
<MyAppRoot>..\MyApp</MyAppRoot>
<MyAppProject>$(MyAppRoot)\MyApp.csproj</MyAppProject>
<PropertyGroup>
<Target Name="BuildProject">
<Message Text="BEFORE: #(ProjectFiles)" />
<MSBuild Projects="$(MyAppProject)" Targets="Build" Properties="Configuration=$(Configuration)">
<Output TaskParameter="TargetOutputs" ItemName="MyAppAssembly"/>
</MSBuild>
<Message Text="AFTER: #(ProjectFiles)" />
</Target>
Presumably your item array is declared at the root level in your project, in XML as a child of the <Project> element. This means that MSBuild will evaluate the membership in the item array when the project file is first loaded. Whether or not the existance of those files changes during execution is irrelevant. If you want to populate the item array at a particular point in your build, you need to change the declaration from a static item array to a dynamic one, which means moving it inside a target, to the exact spot where you want to gather the files, as:
<Target Name="BuildProject">
...before message
...msbuild task
<ItemGroup>
<ProjectFiles Include="$(ProjectRoot)\$(OutputPath)\*.dll" />
<ProjectFiles Include="$(ProjectRoot)\$(OutputPath)\MyApp.exe" />
</ItemGroup>
...after message
</Target>
(excerpted from trick #62 in the book "MSBuild Trickery")

How to set Visual Studio to Publish pdf files automatically

Is there a way to set visual studio to publish all pdf files?
I know that you can set each individual pdf file in a project with the Build Action
"Content" property.
But that means doing the same thing 100's of times for my current project, is there a way to change a global setting to do the same thing?
there is an easier way, you have to make sure your file is included in the project first, then right-click on the file go to properties, there will be an option "copy to output directory", choose "copy always"
Good luck
Just right click on the file you want to include, choose properties, in the properties window change build action to content. This will include the file during publish.
Add a post build event with the following command:
xcopy "$(ProjectDir)myPdfs\*.pdf" "$(TargetDir)myPdfs\" /S /Y
Note in the above command myPdfs is just a subfolder of your project directory that contains all the PDF files. If you have more than one of these subfolders you need to run the command for each.
Hope this works!!
Suppose you had the PDFs you wish to deploy outside the project in c:\PDFs, modify the .csproj
<ItemGroup>
<Content Include="c:\PDFs\**\*.pdf" />
</ItemGroup>
If they're in a folder "MyPdfs" relative to the root of the project
<ItemGroup>
<Content Include="MyPdfs\**\*.pdf" />
</ItemGroup>
Some further details about this can be found on: https://stackoverflow.com/a/12202917/37055
Open the csproj file and change :
<None Include="my.pdf">
to:
<Content Include="my.pdf">
You could edit your project file directly to add the required <CopyToOutputDirectory>Always</CopyToOutputDirectory> elements to the PDF files. (If your project isn't under source control, test on a copy first and keep backups in case it all goes wrong)
CopyToOutputDirectory will copy the files to the bin folder when you publish. Setting "Build Action" to "Content" will copy the files without the need of CopyToOutputDirectory setting. But this is still needs to be done on each file. You could make a regex replace in project file from <None Include="XXX.pdf" /> to <Content Include="XXX.pdf" />.

Resources