I just started using batching in one of my MSBuild scripts in order to deploy a project one time for each customer in a list. Everything seemed to to go according to the plan, but then I discovered a strange issue: At the end of each iteration the task is supposed to make a copy of an MSI file and put it in a customer-specific directory, with a customer-specific file name. What happens is that the MSI file is given the appropriate name, BUT both of the MSI files are copied into the same folder (belonging to "Customer2").
When I look at the build logs I can see that both of the copy tasks are done at the end of the build. Can someone explain why this is happening? What I want is for the entire "Deploy" target to run before proceeding to the next customer.
Here is the MSBuild code. I have snipped out some things that should not be relevant:
<PropertyGroup>
<Customers>Customer1;Customer2</Customers>
</PropertyGroup>
<ItemGroup>
<Customer Include="$(Customers)"/>
</ItemGroup>
<Target Name="Deploy">
<PropertyGroup>
<DeploymentDirectory>$(Root)MyApplication_%(Customer.Identity)_ci</DeploymentDirectory>
<SolutionDir>../MyApplication</SolutionDir>
<ProjectFile>$(SolutionDir)/MyApplication/MyApplication.csproj</ProjectFile>
</PropertyGroup>
<MSBuild Projects="web_application_deploy.msbuild" Properties="
ProjectFile=$(ProjectFile);
SolutionFile=$(SolutionDir)\MyApplication.sln;
AppName=MyApplication_%(Customer.Identity)_ci;
TestDll=$(SolutionDir)/MyApplication.Tests/bin/Release/MyApplication.Tests.dll" />
<!-- Build WIX project-->
<MSBuild Condition="$(BuildWix) == true"
Projects="$(SolutionDir)\MyApplication.Wix\MyApplication.Wix.wixproj"
Properties="DeploymentDirectory=$(DeploymentDirectory);VersionNumber=$(BUILD_NUMBER)" />
<!-- Copying the installer file to correct path, and renaming with version number -->
<Exec Condition="$(BuildWix) == true"
Command="copy "$(SolutionDir)\MyApplication.Wix\bin\$(Configuration)\MyApplication.msi" "$(DeploymentDirectory)\MyApplication-%(Customer.Identity)-v$(BUILD_NUMBER).MSI""></Exec>
</Target>
Update: It works if I reference the Iterator %(Customer.Identity) directly instead of using the $(DeploymentDirectory) property in the "Exec" call. Like this:
<Exec Condition="$(BuildWix) == true"
Command="copy "$(SolutionDir)\DataGateway.Wix\bin\$(Configuration)\DataGateway.msi" "$(CiRoot)DataGateway_%(Customer.Identity)_ci\DataGateway-%(Customer.Identity)-v$(BUILD_NUMBER).MSI""></Exec>
So it seems like the property called "DeploymentDirectory" is not updated with the correct customer when it is referenced. Is there something else I can do to make sure the property is "refreshed" in each iteration of the loop?
I think your doing something like this:
<Target Name="DeployNotBatching" >
<Message Text="Deployment to server done here. Deploying to server: %(Customer.Identity)" />
<Message Text="Also called" />
</Target>
Which gives:
Deployment to server done here. Deploying to server: Customer1
Deployment to server done here. Deploying to server: Customer2
Also called
When you really want to do this?
<Target Name="Deploy" Inputs="#(Customer)" Outputs="%(Identity)">
<Message Text="Deployment to server done here. Deploying to server: %(Customer.Identity)" />
<Message Text="Also called" />
</Target>
That results in :
Deploy:
Deployment to server done here. Deploying to server: Customer1
Also called
Deploy:
Deployment to server done here. Deploying to server: Customer2
Also called
So the whole target is iterated rather than the individual command?
Related
MSBuild quite powerful and important tool, but sometimes configuration of it is the same hard as making a spaceship or flying to Mars. Answers on next questions can help makes it a little bit more usefull and developer friendly:
- How the MSBuild create a Package(zip)?
- Does it use some temporary folders or direct from builds output ?
- Why can the output folder content differ from a package content
- What events can be used to add some custom logic?
The answers to this question could help me with the next long story short:
I'm working with a .Net Core solution, that has the dependency on some other solutions DLLs files( that are not referenced directly, but required to be in the solution folder). On post-build event CL there is the option added as an entry point "\TaskEP" that is a starting point of the next pipeline :
<PropertyGroup>
<PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
TaskEP;
Task2;
Task3;
$(PipelineCopyAllFilesToOneFolderForMsdeployDependsOn);
</PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
<RunPostBuildEvent>Always</RunPostBuildEvent>
Tasks are mostly done for add extra DLLs to package and in general looks like :
<Target Name="TaskEP" Condition="$(SomeConditions) == 'True'">
<Message Importance="high" Text="TaskEP: Copying some files..." />
<ItemGroup>
<ItemsName Include="$(MSBuildProjectDirectory)\..\..somepath..\Extra.dll;....dll" />
<FilesForPackagingFromProject Include="%(ItemsName.Identity)">
<DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
Basically, for me, this instruction to MSBuild to add "Extra.dll" to output package.
Pubxml is quite usual :
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<_PackagePathShortened Condition="'$(_PackagePathShortened)' == ''">SuperWebSite</_PackagePathShortened>
<ExcludeApp_Data>False</ExcludeApp_Data>
<PackageLocation>..\..\..\..\BIN\WEB\Release\Publish\Super.WebApplication.zip</PackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<TargetFramework>netcoreapp2.1</TargetFramework> ....
And one instruction that replaces the long long path in the package to Short and defined one:
<Target Name="AddReplaceRuleForAppPath" BeforeTargets="BeforePublish">
<Message Text="Adding replace rules for application path '$(PublishIntermediateOutputPath)' replace with '$(_PackagePathShortened)'" Importance="high" />
<EscapeTextForRegularExpressions Text="$(PublishIntermediateOutputPath)">
<Output PropertyName="_PackagePathRegex" TaskParameter="Result" />
</EscapeTextForRegularExpressions>
<!-- Add a replace rule for VSMSDeploy resp. MSdeploy to update the path -->
<ItemGroup>
<MsDeployReplaceRules Include="replaceFullPath">
<Match>$(_PackagePathRegex)</Match>
<Replace>$(_PackagePathShortened)</Replace>
</MsDeployReplaceRules>
</ItemGroup>
I also duplicated the same "Copy" tasks here in .pubxml file , play with different events "AfterBuild", "BeforePublish" but seems that all of the just ignoring. I can see the "Extra" DLLs files in the build output directory and all Info Messages on publishing, but not in the final "Super.WebApplication.zip" file !
The solution I found to copy extra files in .Net Core is taken from this documentation
<ItemGroup>
<DotnetPublishFiles Include="$(MSBuildProjectDirectory)\..\..somepath..\Extra.dll;....dll" >
<DestinationRelativePath>bin\x86\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</DotnetPublishFiles>
</ItemGroup>
I'm currently writing an msbuild script to build a solution I've been working on, as well as run its tests. On my development machine, this works as expected. However, when I try to run the same build script on our build server, I get several failures. I've tracked the source of the problem down to the fact that my build script appears to be trying to run the .exe file associated with my application. This line during the script execution tipped me off, since it doesn't run that command on my dev box:
MSIAuthoring:
Building MSI
"C:\Program Files (x86)\Jenkins\workspace\Test Build\BuildArtifacts\MsiBuildTool.exe" "/MBSBUILD:MsiBuildTool"
I'm fairly new to build scripting, but my understanding is that the build script shouldn't be trying to run my program unless I explicitly tell it to do so. Does anyone know what might be causing this?
For reference, here is my build script:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="RunTests"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<BuildArtifactsDir Include="BuildArtifacts\"/>
<SolutionFile Include="MsiBuildTool.sln"/>
<NUnitConsole Include="C:\Program Files (x86)\NUnit 2.6.4\bin\nunit-console.exe"/>
<UnitTestsDll Include="BuildArtifacts\MsiBuildToolUnitTests.dll"/>
<TestResultsPath Include="BuildArtifacts\TestResults.xml"/>
</ItemGroup>
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Release</Configuration>
<Platform Condition="'$(Platform)' == ''">Any CPU</Platform>
</PropertyGroup>
<Target Name="Init" DependsOnTargets="Clean">
<MakeDir Directories="#(BuildArtifactsDir)"/>
</Target>
<Target Name="Clean">
<RemoveDir Directories="#(BuildArtifactsDir)"/>
</Target>
<Target Name ="Compile" DependsOnTargets="Init">
<MSBuild Projects="#(SolutionFile)"
Targets ="Build"
Properties ="OutDir=%(BuildArtifactsDir.FullPath);Configuration=$(Configuration);Platform=$(Platform)"/>
</Target>
<Target Name="RunTests" DependsOnTargets="Compile">
<Exec Command='"#(NUnitConsole)" #(UnitTestsDll) /xml=#(TestResultsPath)'/>
</Target>
</Project>
Update:
After some digging through the output, I found that "MSIAuthoring" step was the result of the Wix# library that I'm using. As described by this thread: https://wixsharp.codeplex.com/discussions/644609#
I disabled the MSIAuthoring step by removing this line in my .csproj files:
<Import Project="..\packages\WixSharp.1.0.22.3\build\WixSharp.targets" Condition="Exists('..\packages\WixSharp.1.0.22.3\build\WixSharp.targets')" />
You're building solution file, thus MSBuild will generate msbuild-xml script first and then will build it. To find why it's being called on build machine but not on your dev machine - follow this advice and obtain generated MSBuild scripts from your dev environment and your build server. Then compare it.
Also enable diagnostic logging (/verbosity:diag in the command line) as Lex Li advised, and you'll see detailed decisions why each target being run or not - grep logs for something like "Conditions A, B, C on target BuildMSI evaluated to False" and this will show you the difference between environments.
It might be some type of post-build script on one of the projects which builds MSI only if it's being run not on dev environment - check actual build script to find where it comes from. Also check that it's really related to your build script, and it's not an extra build step in your Jenkins build configuration.
Would really like help with this.
I'm running Team City, have setup a build config, it builds fine. I added a deploy parameter, and that functions almost correctly. The thing is it's trying to deploy my entire c-drive!
This is the config for it:
<Target Name="DeployApp">
<Message Text="Copying application files..." />
<ItemGroup>
<ApplicationFiles Include="$(ApplicationOutputDirectory)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(ApplicationFiles)" DestinationFolder="$(DestinationPath)\% (RecursiveDir)"/>
</Target>
Any help on how to make it only copy the built project to the server?
If $(ApplicationOutputDirectory) isn't set, the Include statement will resolve to \**\*.*, which is the entire current drive (in this case, your C drive)
I am deploying some files on the server. But when I am doing this, build is deleting all the files and folder which are residing at that location. I don't want to delete all the files from the server. I want to exclude one folder (folder name is Temp) from the destination folder. Temp folder should not get deleted while deleting other files. How to do that?
Here is TFS Build Definition
<PropertyGroup Condition=" '$(DeployEnvironment)' == 'Dev' ">
<DeployPath>\\server1\D$\temp\reports</DeployPath>
</PropertyGroup>
<Target Name="CoreCompileSolution" />
<Target Name="AfterCompile">
<Message Importance ="high" Text="Solution Root: $(SolutionRoot)" />
<Message Importance ="high" Text="Out Dir: $(OutDir)" />
<Copy SourceFiles="#(RPTFiles)" DestinationFolder="$(OutDir)_PublishedWebsites\Reports\" />
</Target>
<Target Name="AfterDropBuild" >
<CreateItem Exclude="$(DeployPath)\Temp*.*">
<Output ItemName="PreviousDeployment" TaskParameter="Include" />
</CreateItem>
</Target>
Why are you using a Copy task? I think it is intended to be used for local manipulations during build, rather than deployment (because it does not give you a chance to easily configure behaviour).
I suggest that instead of copy tsak you use one of the following options
Non-web applications - use Robocopy:
/XD dirs [dirs]... : eXclude Directories matching given names/paths.
XF and XD can be used in combination e.g.
ROBOCOPY c:\source d:\dest /XF *.doc *.xls /XD c:\unwanted /S
see this link for usage guide. You either run it from the command line (using <Exec Command="" > task, or employ MBuiild Community Tasksproject which has a nice wrapper.
Web applications: you should use Web Deploy for your deployments. You an either use MSBuild integration (VS 2010 and later, see this blog series for guidance on setup and configure on VS2010 NB: it has been much simplified in VS 2012, but I don't have a link to share at the moment) or run it from command line (prior to VS 2010):
<Exec Command=""$(WebDeployToolPath)" -verb:sync - source:dirPath='$(MSBuildProjectDirectory)\Published\' -dest:dirPath='$(DeployDirectoryLocalPath)',computerName=$(DeployTargetURL),userName='$(DeployUserName)',password='$(Password)',authType='Basic' -skip:skipaction='Delete',objectname='filePath',absolutepath='app_offline.htm' -skip:skipaction='Delete',objectname='filePath',absolutepath='logs\\.*' -skip:skipaction='Delete',objectname='dirPath',absolutepath='logs\\.*' -skip:skipaction='Delete',objectname='filePath',absolutepath='UserFiles\\.*' -skip:skipaction='Delete',objectname='dirPath',absolutepath='UserFiles\\.*' -verbose -allowUntrusted" />
NB using skip:skipaction='Delete.. to skip removing files and folders.
Update
It looks like I've undestood this a bit incorrect (I supposed, deployment happenned in AfterCompile target, however, as I see now, TFS uses CoreDropBuild target to do the deployment.
So I think, what you need is to override CoreDropBuild target as described: here. (although, I've never tried this).
You can either use Copy task as the author of the thread, or go with Robocopy/webdeploy based on your personal preference.
I'm trying to make a batch file to publish the few ClickOnce application we have in one click. I'm using msbuild for that, and as an example the below command line shows how I'm doing it:
msbuild
MyApp.sln
/t:Publish
/p:Configuration=Release
/p:PublishUrl="C:\Apps\"
/v:normal > Log.txt
(wrapped for easier reading)
when I run the above command it builds and publish the application in the release directory, i.e. bin\release! Any idea why msbuild doesn't respect PublishUrl property in my example above?
PS: I tried also different combinations including remove 'Configuration', use 'Rebuild' and 'PublishOnly' as targets, and remove the the quotation marks but without any success.
You are setting the wrong property. Try PublishDir instead.
You can pass it into MSBuild as you are or you can set it in the project file (or maybe the sln file too, not sure I always use the project file.) like this
<PropertyGroup>
<PublishDir>C:\Dev\Release\$(BuildEnvironment)\</PublishDir>
</PropertyGroup>
I've just done a few blog posts on MsBuild and ClickOnce stuff, check it out you 'should' find them useful...
Some features are done by Visual-Studio and not by the MSBuild-script. So the click-once-deployment behaves differently when it's executed from the command-line.
The ApplicationRevision isn't increased with every build. This works only when is exectued from Visual Studio
In in somecases, the PublishUrl isn't used. Quote from MSDN:
For example, you could set the PublishURL to an FTP path and set the InstallURL to a Web URL. In this case, the PublishURL is only used in the IDE to transfer the files, but not used in the command-line builds. Finally, you can use UpdateUrl if you want to publish a ClickOnce application that updates itself from a separate location from which it is installed.
I've created a special MSBuild-file which does this things. It runs the publish-target and copies then the files to the right location.
An example of the build-file, as requested by alhambraeidos. It basically runs the regular VisualStudio-build and then copies the click-once data to the real release folder. Note that removed some project-specific stuff, so it's maybe broken. Furthermore it doesn't increase the build-number. Thats done by our Continues-Build-Server:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Publish" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- the folder of the project to build -->
<ProjLocation>..\YourProjectFolder</ProjLocation>
<ProjLocationReleaseDir>$(ProjLocation)\bin\Release</ProjLocationReleaseDir>
<ProjPublishLocation>$(ProjLocationReleaseDir)\app.publish</ProjPublishLocation>
<!-- This is the web-folder, which provides the artefacts for click-once. After this
build the project is actually deployed on the server -->
<DeploymentFolder>D:\server\releases\</DeploymentFolder>
</PropertyGroup>
<Target Name="Publish" DependsOnTargets="Clean">
<Message Text="Publish-Build started for build no $(ApplicationRevision)" />
<MSBuild Projects="$(ProjLocation)/YourProject.csproj" Properties="Configuration=Release" Targets="Publish"/>
<ItemGroup>
<SchoolPlannerSetupFiles Include="$(ProjPublishLocation)\*.*"/>
<SchoolPlannerUpdateFiles Include="$(ProjPublishLocation)\Application Files\**\*.*"/>
</ItemGroup>
<Copy
SourceFiles="#(SchoolPlannerSetupFiles)"
DestinationFolder="$(DeploymentFolder)\"
/>
<Copy
SourceFiles="#(SchoolPlannerUpdateFiles)"
DestinationFolder="$(DeploymentFolder)\Application Files\%(RecursiveDir)"
/>
<CallTarget Targets="RestoreLog"/>
</Target>
<Target Name="Clean">
<Message Text="Clean project:" />
<MSBuild Projects="$(ProjLocation)/YourProject.csproj" Properties="Configuration=Release" Targets="Clean"/>
</Target>
</Project>
I'll put in my 2 cents, this syntax seems to work (right or wrong):
/p:publishUrl="C:\\_\\Projects\\Samples\\artifacts\\Web\\"
For me, the soultion was to escape the path.
Instead of:
/p:PublishUrl="C:\Apps\"
Put:
/p:PublishUrl="C:\\Apps\\"