I have a Silverlight project with multiple configuration files, and am using the transformation approach shown here:
App.Config Transformation for projects which are not Web Projects in Visual Studio 2010?
This approach doesn't work as-is for Silverlight projects though. I've re-written the MSBuild project to look like this:
<ItemGroup>
<None Include="App.config" />
<None Include="App.QABuild.config">
<DependentUpon>App.config</DependentUpon>
</None>
</ItemGroup>
....
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeCompile" Condition="Exists('App.$(Configuration).config')">
<!-- Generate transformed app config in the output directory -->
<Message Importance="high" Text="Transforming 'App.$(Configuration).config' to output config file..." />
<TransformXml Source="App.config" Destination="$(OutputPath)App.config" Transform="App.$(Configuration).config" />
<ItemGroup>
<Content Include="$(OutputPath)App.config" />
</ItemGroup>
</Target>
<Target Name="BeforeCompile" Condition="!Exists('App.$(Configuration).config')">
<Message Importance="high" Text="Using default 'App.config' as output config file..." />
<Copy SourceFiles="App.config" DestinationFiles="$(OutputPath)App.config" />
<ItemGroup>
<Content Include="$(OutputPath)App.config" />
</ItemGroup>
</Target>
This code generates the correct output file for the correct configuration, however it is never included in the XAP file, even though I am putting the output config into the Content item group. All I need to happen is for the output config to get included in the output XAP but I can't get this to happen.
Can anyone tell me what I'm doing wrong? I'm not an MSBuild expert by any means!
Found the solution by digging into the Silverlight 4 targets. Turns out the XAP packager target actually takes an item called XapFilesInputCollection, which is where the input files come from. The Content item looks likes it is copied to this item before my target runs, so modifying the Content item afterwards is the wrong approach.
All I did was add the transformed files directly to the XapFilesInputCollection item and it worked as I expected.
Related
So here's what I want to do:
I want a build script that will xcopy deploy build outputs for a legacy winform app to a given directory. I want to specify a list of files to not overwrite (some config files).
I would rather have the list of files to not overwrite be passed as a parameter than hard code them.
This seems to be really unexpectedly hard. Here's what I have so far:
<!-- A property that is passed a semicolon delimited list of file names -->
<PropertyGroup>
<ProtectedFiles/>
</PropertyGroup>
<--! An ItemGroup to pick up the files>
<ItemGroup>
<FilesToDelete Include=$(DeploymentTargetFolder)\*.* Exclude="#(ProtectedFiles->'$(DeployTargetFolder)\%(identity)')"
<ItemGroup/>
<--! the delete isn't working, so I will stop just with that to keep the code brief -->
<Delete Files="#(FilesToDelete)"/>
The delete just ignores the exclude files and deletes everything
Is there a better way to do this? It doesn't seem too crazy -- I just want to
Delete all files from the target directory, except for the config files
Copy all of the files from the build outputs to the target directory, without overwriting the config files.
The first problem with your particular markup appears to confuse MsBuild $(properties) with MsBuild %(items) and MsBuild #(itemgroups).
ProtectedFiles is a property:
<!-- A property that is passed a semicolon delimited list of file names -->
<PropertyGroup>
<ProtectedFiles/>
</PropertyGroup>
But it's being treated as an Item and wouldn't have any %item.metadata:
<--! An ItemGroup to pick up the files>
<ItemGroup>
<FilesToDelete Include=$(DeploymentTargetFolder)\*.* Exclude="#(ProtectedFiles->'$(DeployTargetFolder)\%(identity)')"
<ItemGroup/>
Save the following markup locally as "foo.xml", then call "msbuild.exe foo.xml" and observe the output:
<Project ToolsVersion="4.0" DefaultTargets="foo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<FilesProp>FileA.txt;FileB.txt</FilesProp>
</PropertyGroup>
<ItemGroup>
<ProtectedFiles Include="FileA.txt" />
<ProtectedFiles Include="FileA.txt" />
</ItemGroup>
<Target Name="foo">
<Message Importance="high" Text="ProtectedFiles ItemGroup: #(ProtectedFiles)" />
<Message Importance="high" Text="ProtectedFiles ItemGroup transform: #(ProtectedFiles->'%(Identity)')" />
<Message Importance="high" Text="FilesProp Property: $(FilesProp)" />
<Message Importance="high" Text="FilesProp Property: #(FilesProp->'%(FilesProp.Identity)')" />
</Target>
</Project>
Will yield the following output:
foo:
ProtectedFiles ItemGroup: FileA.txt;FileA.txt
ProtectedFiles ItemGroup transform: FileA.txt;FileA.txt
FilesProp Property: FileA.txt;FileB.txt
FilesProp Property:
If you're unable to change the design and need to convert a Property comprising a semi-colon delimited list of file paths, use the MsBuild <CreateItem /> task.
Add this markup to foo.xml occurring after the Foo target, then invoke msbuild again, but using the "bar" target (e.g. msbuild.exe foo.xml /t:bar)
<Target Name="bar">
<CreateItem Include="$(FilesProp)">
<Output TaskParameter="Include" ItemName="TheFiles"/>
</CreateItem>
<Message Text="TheFiles ItemGroup: #(TheFiles)" Importance="high" />
<Message Text="Output each item: %(TheFiles.Identity)" Importance="high" />
</Target>
Will yield the following output:
bar:
TheFiles ItemGroup: FileA.txt;FileB.txt
Output each item: FileA.txt
Output each item: FileB.txt
Next you should rethink some of your assumptions. I don't believe the file extension should be the determining factor when deciding which files to update, rather you should rely on MsBuild's ability to build tasks incrementally allowing it to perform a task only if the inputs are newer than the outputs. You can do this by using an MsBuild <Copy /> task configured to skip unchanged files.
Add this markup to the above Xml file, then modify the $(SourceFolder) and $(TargetFolder) to point to a source folder you'd like to copy recursively, and a destination folder to place the files. Build using "msbuild.exe foo.xml /t:Deployment" and observe the output.
<Target Name="Deployment">
<PropertyGroup>
<SourceFolder>c:\sourcefolder\</SourceFolder>
<TargetFolder>c:\destinationfolder\</TargetFolder>
</PropertyGroup>
<CreateItem Include="$(SourceFolder)\**\*.*">
<Output TaskParameter="Include" ItemName="FilesToCopy" />
</CreateItem>
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(TargetFolder)%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
Without modifying any of the source files, run the command again and note that no files were copied.
Modify a file in the source folder, then run the command again. Notice that only the updated files were copied?
I hope this gets you on the right track.
There seems to be an already existing post, similar to this. Please check this Trying to exclude certain extensions doing a recursive copy (MSBuild)
I want to execute an msbuild project which uses batching to determine that one or more csproj projects have been freshly-built, and therefore require fresh nuget packaging. The script I've made so far seems like a reasonable start, but it the incremental-build mechanism isn't working. The MainBuild target executes every time, no matter what.
Here is what I have:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="MainBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Content>content\plugins\</Content>
</PropertyGroup>
<ItemGroup>
<Nuspec Include="$(MSBuildProjectDirectory)\plugins\*\*.nuspec" />
</ItemGroup>
<Target Name="MainBuild"
Inputs="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll"
Outputs="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" >
<ItemGroup>
<Inputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll" />
<Outputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" />
</ItemGroup>
<Message Text="INPUTS: %(Inputs.FullPath)" />
<Message Text="OUTPUTS: #(Outputs->'%(FullPath)')" />
<Copy SourceFiles="#(Inputs)" DestinationFiles="#(Outputs->'%(FullPath)')" />
</Target>
</Project>
The Copy task is just a debugging placeholder for calling-out to nuget and creating a new package.
The idea is that if any files in the bin\Debug directory are newer than the corresponding .nuspec file (found two folders above bin\Debug), then the MainBuild target should execute.
Any ideas?
p.s. The Inputs and Outputs attributes of the Target presumably each create an item. I think it strange that the items created can't be referenced inside the target. In the above example, I had to make a target-interna dynamic ItemGroup to re-create the items, just so that I could access them. Is there a way around that?
I read this in the MSBuild Batching documentation
If a task inside of a target uses batching, MSBuild needs to determine
if the inputs and outputs for each batch of items is up-to-date.
Otherwise, the target is executed every time it is hit.
Which may be the cuprit. Try changing your copy target to use batching instead of an ite transform (I don't think using item metadata in an item group satisfies the above requirement).
<Target Name="MainBuild"
Inputs="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll"
Outputs="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" >
<ItemGroup>
<Inputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll" />
<Outputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" />
</ItemGroup>
<Message Text="INPUTS: %(Inputs.FullPath)" />
<Message Text="OUTPUTS: #(Outputs->'%(FullPath)')" />
<Copy SourceFiles="#(Inputs)" DestinationFiles="%(Outputs.FullPath)" />
</Target>
It looks like the number of inputs may be different than the number of outputs (I suspect there is more than one .dll files in the output directory for each project), which will also cause the target to execute.
In the process of cleaning up the folder/file structure on a project I inherited, I'm running into a problem with organizing the required external libraries. I want to keep them in their own .\dll\ folder, but they're not being copied to the build directory properly. They should be in the root build directory, but they're being moved to a subfolder instead.
My .csproj file contains the following xml:
<Project>
<ItemGroup>
<None Include="dlls\libraryA.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Then, on build, the libraryA.dll file is copied to the bin\Debug\dll\ folder, but I want it in the bin\Debug\ folder.
I tried this and msbuild always wants to copy the files using their directory path, but there is a workaround...
Edit the csproj file and after this line:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Add these lines:
<PropertyGroup>
<PrepareForRunDependsOn>$(PrepareForRunDependsOn);MyCopyFilesToOutputDirectory</PrepareForRunDependsOn>
</PropertyGroup>
<Target Name="MyCopyFilesToOutputDirectory">
<Copy SourceFiles="#(None)" DestinationFolder="$(OutDir)" />
</Target>
The copy of the output files happens in the PrepareForRun target. This adds your own target to the list of targets that are executed as part of PrepareForRun.
This example copies all items in the None item group. You could create your own item group (e.g. MyFiles) and do the copy on that item group if you have other "None" files you don't want copied. When I tried this I had to change the item group name by editing the csproj file directly. Visual Studio did not allow me to set the item group of a file from the UI, but after I edited the csproj and changed it, Visual Studio displayed my custom item group name correctly.
If you only want to change it for one file, it may be easier to use the property:
<None Include="dlls\libraryA.dll">
<Link>%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Including content files in .csproj that are outside the project cone
This approach works
If you need to force copy of a specific file/nuget package into an asp.net core project (2.2), add at the end of your csproj :
<!-- Force copy MathNet because we need it in compilation -->
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="Build">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll'))" />
</Target>
<ItemGroup>
<ContentWithTargetPath Include="..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>MathNet.Numerics.dll</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
In SDK-style csproj you can write something like:
<Target Name="CopyFilesTargetName" AfterTargets="Build">
<Copy SourceFiles="$(OutDir)\dlls\Some.dll;$(OutDir)\dlls\SomeOther.dll" DestinationFolder="$(OutDir)" />
</Target>
You can also use <Move instead of <Copy to move files
I have attempted the following:
<!-- Specify the inputs by type and file name -->
<ItemGroup>
<CSFile Include = "$(MSBuildProjectDirectory)\..\Mine.cs"/>
</ItemGroup>
<Target Name = "Compile">
<!-- Run the Visual C# compilation using input files of type CSFile -->
<Csc Sources="#(CSFile)" />
<!-- Log the file name of the output file -->
<Message Text="The output file is done"/>
</Target>
This does not work as all the namespaces used in the project throw errors. Does anyone know how I can explicitly get the assemblies to pick up from the solution file, as the paths are ok and if loaded in Visual Studio all is fine. I need to script this and the above is not working. Is there an obvious mishap?
Appreciate the inpuT :-)
I have realised that this is not going to work as the file I have has several external dependancies. Hence I would need to use the devenv.exe. Problem is that I get the follwing:
What I get is that the command exits with Code 1? I want to get the project to build all the dependant dlls that it requires without having to open visual studio.
Any ideas?
Thnxes :-)
try this (add you own dlls references)
<ItemGroup>
<CSFile Include = "$(MSBuildProjectDirectory)\..\Mine.cs"/>
<Reference Include="System.dll"/>
<Reference Include="System.Data.dll"/>
<Reference Include="System.Drawing.dll"/>
<Reference Include="System.Windows.Forms.dll"/>
<Reference Include="System.XML.dll"/>
</ItemGroup>
<Target Name = "Compile">
<!-- Run the Visual C# compilation using input files of type CSFile -->
<Csc Sources="#(CSFile)"
References="#(Reference)"
OutputAssembly="$(builtdir)\$(MSBuildProjectName).exe"
TargetType="exe" />
/>
<!-- Log the file name of the output file -->
<Message Text="The output file is done"/>
</Target>
In MSBuild, I would like to call a task that extracts all the files in all the project in a specific solution and hold these files in a property that can be passed around to other tasks (for processing etc.)
I was thinking something along the lines of:
<ParseSolutionFile SolutionFile="$(TheSolutionFile)">
<Output TaskParameter="FilesFound" ItemName="AllFilesInSolution"/>
</ParseSolutionFile>
<Message Text="Found $(AllFilesInSolution)" />
which would output the list of all files in the projects in the solution and I could use the AllFilesInSolution property as input to other analysis tasks. Is this an already existing task or do I need to build it myself? If I need to build it myself, should the task output an array of strings or of ITaskItems or something else?
I don't know about tasks, but there are already properties that hold all items. Just look in your typical project file and you'll see which collection they're being added to.
Note the properties Content, Compile, Folder... any time you add a file to a project, it gets put in one of the main collections like this:
<ItemGroup>
<Content Include="Default.aspx" />
<Content Include="Web.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Default.aspx.cs">
<SubType>ASPXCodeBehind</SubType>
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
<Compile Include="Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
</ItemGroup>
Then you can do stuff like this to put the values from existing properties into your properties (the Condition attribute acts as a filter):
<CreateItem Include="#(Content)" Condition="'%(Extension)' == '.aspx'">
<Output TaskParameter="Include" ItemName="ViewsContent" />
</CreateItem>
Or you can do it manually (the Include attribute uses the existing property OutputPath, but it indicates a path that inclues all files):
<CreateItem Include="$(OutputPath)\**\*">
<Output TaskParameter="Include" ItemName="OutputFiles" />
</CreateItem>
There are more details in the MSDN MSBuild documentation that I read when I was mucking with custom build tasks and stuff that was very helpful. Go read up on the CreateItem task and you'll be able to make more sense out of what I posted here. It's really easy to pick up on.
I use the following for solutions with SSRS projects (which dont build under TFS w/o vs installed on the build box). Basically we require that the RDLs be bundled into a build output so we can mark a build for release.
<Target Name="CopyArtifactstoDropLocation">
<CreateItem Include="$(SolutionRoot)\**\*.*">
<Output TaskParameter="Include" ItemName="YourFilesToCopy" />
</CreateItem>
<Copy
SourceFiles="#(YourFilesToCopy)"
DestinationFiles="#(YourFilesToCopy->'$(DropLocation)\$(BuildNumber)\Release\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
Just replace the usage of the Copy Task with whatever you need to do with your bundle. Granted this is going to get everything in your solution root, but if your using TFS then you should only have buildable artifacts.