WiX Heat: Pre-build event fires too early on build server - msbuild

I'm harvesting a directory for my Visual Studio solution.
It works on my local system so far probably because the project build order is being respected.
When I run the installer on a build server it finds the right directory but it has not been created at the time of building the setup file. It throws a HEAT5052 error saying The directory 'a:\b\c' could not be found.
Is there any way to "wait" until or to execute the heat command after all project references are built?

OK so I've spent hours to figure out how to fire Heat AFTER all references are resloved. I only found solutions for the <PreBuildEvent> and <PostBuildEvent> using the Heat command line and the BeforeBuild and AfterBuild targets.
So I found all kind of targets inside the wix2010.targets file located in my
Program files (x86)\MSBuild\Microsoft\Wix\ folder. It contains a target called AfterResolveReferences and it does exactly that. So here's my code I ended up with (in case someone is interested):
<Target Name="AfterResolveReferences">
<HeatDirectory
ToolPath="$(WixToolPath)"
OutputFile="Product.Binaries.wxs"
SuppressFragments="$(HarvestDirectorySuppressFragments)"
Transforms="Filter.xslt"
Directory="$(HarvestFolder)"
DirectoryRefId="MY_FOLDER"
ComponentGroupName="Binaries"
GenerateGuidsNow="true"
SuppressRootDirectory="true"
SuppressRegistry="true"
PreprocessorVariable="var.App.TargetDir">
</HeatDirectory>
</Target>

I had the same problem and it was solved by combining the accepted answer and this answer to ensure that my post build event, which was copying files, always runs:
<RunPostBuildEvent>Always</RunPostBuildEvent>
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
Also, my build server was using the MSBuild command line, and I used this answer to locate my local MSBuild.exe so I could test my changes locally without having to push to the build server.

Related

Autogenerate WXI file in WiX Build process

I have a batch file that creates a WXI file that is included in the final build process of a MSI.
If I use the "Pre-build Event" in the project setting of VS-2019 this command is always executed even if there is no need to compile the WiX project.
I tried to add
<Target Name="UpdateVersionNumber" Inputs="$(ProjectDir)..\..\..\Bin\Infile.dll" Outputs="$(ProjectDir)output.wxi">
<Exec Command=""%COMSPEC%" /c UpdateVersion.bat "$(ProjectDir)..\..\..\Bin\Infile.dll" "$(ProjectDir)$output.wxi"" />
</Target>
The batch file creates the output.wxi file depending on the DLL. The output.wxi is also part of the project.
But it is never executed. If I add BeforeTargets="Compile" it is again always executed.
I only want this batch file executed if any build is required.
In C# projects the "Pre-Build Events" are only executed when there is need for a build an any file is out of date. This isn't the case for WiX projects in VS-2019.
How to achieve that this batch file is only executed when the Infile.dll is newer than the output.wxi
Edit: The infile.dll is not created by this build process. The build process for the infile.dll is completely a separate project.
First, I think that there is some problems with your Infile.dll and your needs about this issue.
How to achieve that this batch file is only executed when the
Infile.dll is newer than the output.wxi
To answer this question, actually, what you did(use input and output) meets this.
If the files from the inputs node are newer than the files from outputs node, the target will be executed. See this official document.
So in your side, the Infile.dll is always built rather than up to date, and it is always newer than output.wxi which depends on it. And the batch file will generate the output.wxi based on Infile.dll. All of these cause that behavior and the target will always be executed.
BeforeTargets="Compile" just specify this target into the build process.
In C# projects the "Pre-Build Events" are only executed when there is
need for a build an any file is out of date. This isn't the case for
WiX projects in VS-2019.
There won't be any problems with this method on the surface. The real problem is that why the Infile.dll is always newer than output.wxi.
<Target Name="UpdateVersionNumber" Inputs="$(ProjectDir)..\..\..\Bin\Infile.dll" Outputs="$(ProjectDir)output.wxi" BeforeTargets="Compile">
<Exec Command=""%COMSPEC%" /c UpdateVersion.bat "$(ProjectDir)..\..\..\Bin\Infile.dll" "$(ProjectDir)$output.wxi"" />
</Target>
==============================
First, if the Infile.dll is one of the output file of the wix project, then it can be explained clearly. Since wix project does not have incremental build feature and always
The execute order
UpdateVersionNumber(compare Infile.dll and output.wxi )--->Compare-->Build(generate the new output file Infile.dll)
For the first build time, the target will be executed and then generated the output.wxi file based on Infile.dll as expected. Then it executes the build process, it will update
Infile.dll.
However when you execute it second time, the Infile.dll is newer than output.wxi. And the target will always be executed and stuck in a loop.
In this situation, you have nothing to do.
=============================
If Infile.dll is an content from somewhere else, you should check why the Infile.dll is always new(perhaps the wix project has a dependency relationship with the other project) rather than up-to-date.Or it could be always be updated by something else.
If Infile.dll is the output file from another type of the project, you can enter that project to check its detailed output build output log to find where the issue is.
In addition, you should check the other project very carefully and pay attention to your requirements.

Wix Build Order - msbuild & Heat

I am trying to extend our existing Wix Project with Heat to include all files from the output directory of another project in the installer. Somehow I`m missing something that should be very easy since I guess this is a very common task.
Here is what the solution looks like:
SomeDependencyA.csproj
SomeDependencyB.csproj (Depends on SomeDependencyA)
WindowsService.csproj (Depends on SomeDependencyB)
Installer.wixproj(Depends on WindowsService.csproj)
Wix should basically grep anything from WindowsService Output folder and build the msi package from it. The process should be:
Build all Dependencies
Build WindowsService project
Generate a Fragement using Heat
Build the Installer
And here is my question: Where/how do I have to include the call to Heat? Pre-Build Step and BeforeBuild Target does not work, since at this time the WindowsService and the Dependencies are not Build yet. AfterBuild wouldn´t help, since at that point the generated Fragment would not be cosidered anymore. I was looking for some build target that is calles AFTER all dependencies have been build but BEFORE the Installer Project itself has been build. Should be easy, but I couldn´t figure it out yet.
The BeforeBuild target will happen potentially before a dependant project has completed its build.
A solution I've used for this type of build ordering in the past is to add a DependsOnTargets for the BeforeBuild target;
For csproj:
<Target Name="BeforeBuild" DependsOnTargets="ResolveReferences">
For vcxproj:
<Target Name="BeforeBuild" BeforeTargets="PrepareForBuild">
This will force the project to wait until the dependant projects are completed before running the BeforeBuild target.

How to change msbuild working directory in TFS 2013 workflow

I have a TFS 2013 build xaml workflow, that eventually calls the Microsoft.TeamFoundation.Build.Workflow.Activities.MSBuild activity once for each solution that I want to build. When msbuild.exe is called, it's working directory is the working directory of the current solution being built. I can see this through the 'MSBuildStartupDirectory' property when running msbuild with a 'diagnostic' verbosity.
Unfortunately, I need the working of msbuild.exe to be somewhere else when msbuild.exe starts. This is because I use the MSBuild SonarQube runner that imposes constraints on the directory from which msbuild is called.
I have looked at the 'msbuild' activity and there is no way to control the working directory. Is there another way to control the working directory of this activity?
Its been a while since I edited a build process template but I believe you could use an activity that just executes a command in CMD and provide the full MSBuild command. I'm sure there are tons of variables you will need to setup for this to work.
Instead of editing the build process template have you considered using a PowerShell script in the Post-build script to execute SonarQube?
I still haven't found any way to control the working directory of msbuild. But since I know that the working directory will be the directory of the project being built by msbuild, I created a new proj file at the root of my workspace (where my working directory has to be) and only build this new proj file from my workflow. This new proj file then builds all my other solutions. That way, my working directory is the same for all the solutions being built.
Here is an example of my top level proj file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<ItemGroup>
<Solutions Include="**\*.sln"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(Solutions)" Targets="Build"/>
</Target>
</Project>
But beware that doing this may affect the output directory (OutDir) given to each solution. So you may want to do something like this:
<MSBuild Projects="#(Solutions)" Targets="Build" Properties="OutDir=$(OutDir)..\%(Solutions.Filename)"/>

MSBUILD - block until a file exists on an ftp server?

As part of a build process .. I would like to block the build until a file gets created (exists) remotely at an ftp location, after which I would continue the build. (Preferably with somekind of time out limit).
Suggestions?
Is this even possible using only the standard msbuild task and/or extensionPack/communitytask?
Your best bet is to build a small custom exe (you can even compile it as a build step) that polls for the file you are looking for. Then you use the PreBuild target, or a custom target in a pre-build step to verify that the file exists.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="WaitOnFTP">
<Exec Command="MyFTPWaiter.exe"/>
</Target>
</Project>
Other more MSBuild oriented suggestions are to remake that exe as a custom task, or even an inline task in MSBuild 4.0.
FWIW, I've encountered a similar solution done by a peer who didn't want large binaries used by integration tests in version control and he required the use of a custom downloader in the build to get the files from a SMB share. It worked well enough.
Custom Tasks
Inline Tasks

How can you publish a ClickOnce application through CruiseControl.NET?

I have CruiseControl.NET Version 1.4 set up on my development server. Whenever a developer checks in code, it makes a compile.
Now we're at a place where we can start giving our application to the testers. We'd like to use ClickOnce to distribute the application, with the idea being that when a tester goes to test the application, they have the latest build.
I can't find a way to make that happen with CruiseControl.NET. We're using MSBUILD to perform the builds.
We've done this and can give you some pointers to start.
2 things you should be aware of:
MSBuild can generate the necessary deployment files for you.
MSBuild won't deploy the files to the FTP or UNC share. You'll need a separate step for this.
To use MSBuild to generate the ClickOnce manifests, here's the command you'll need to issue:
msbuild /target:publish /p:Configuration=Release /p:Platform=AnyCPU; "c:\yourProject.csproj"
That will tell MSBuild to build your project and generate ClickOnce deployment files inside the bin\Release\YourProject.publish directory.
All that's left is to copy those files to the FTP/UNC share/wherever, and you're all set.
You can tell CruiseControl.NET to build using those MSBuild parameters.
You'll then need a CruiseControl.NET build task to take the generated deployment files and copy them to the FTP or UNC share. We use a custom little C# console program for this, but you could just as easily use a Powershell script.
Thanks for all the help. The final solution we implemented took a bit from every answer.
We found it easier to handle working with multiple environments using simple batch files. I'm not suggesting this is the best way to do this, but for our given scenario and requirements, this worked well. Supplement "Project" with your project name and "Environment" with your environment name (dev, test, stage, production, whatever).
Here is the tasks area of our "ccnet.config" file.
<!-- override settings -->
<exec>
<executable>F:\Source\Project\Environment\CruiseControl\CopySettings.bat</executable>
</exec>
<!-- compile -->
<msbuild>
<executable>C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>
<workingDirectory>F:\Source\Project\Environment\</workingDirectory>
<projectFile>Project.sln</projectFile>
<buildArgs>/noconsolelogger /p:Configuration=Debug /v:diag</buildArgs>
<targets>Rebuild</targets>
<timeout>0</timeout>
<logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,ThoughtWorks.CruiseControl.MsBuild.dll</logger>
</msbuild>
<!-- clickonce publish -->
<exec>
<executable>F:\Source\Project\Environment\CruiseControl\Publish.bat</executable>
</exec>
The first thing you will notice is that CopySettings.bat runs. This copies specific settings for the environment, such as database connections.
Next, the standard MSBUILD task runs. Any compile errors are caught here and handled as normal.
The last thing to execute is Publish.bat. This actually performs a MSBUILD "rebuild" again from command line, and parameters from CruiseControl are automatically passed in and built. Next, MSBUILD is called for the "publish" target. The exact same parameters are given to the publish as the rebuild was issued. This keeps the build numbers in sync. Also, our executables are named differently (i.e. - ProjectDev and ProjectTest). We end up with different version numbers and names, and this allows ClickOnce to do its thing.
The last part of Publish.bat copies the actual files to their new homes. We don't use the publish.htm as all our users are on the network, we just give them a shortcut to the manifest file on their desktop and they can click and always be running the correct executable with a version number that ties out in CruiseControl.
Here is CopySettings.bat
XCOPY "F:\Source\Project\Environment\CruiseControl\Project\app.config" "F:\Source\Project\Environment\Project" /Y /I /R
XCOPY "F:\Source\Project\Environment\CruiseControl\Project\My Project\Settings.Designer.vb" "F:\Source\Project\Environment\Project\My Project" /Y /I /R
XCOPY "F:\Source\Project\Environment\CruiseControl\Project\My Project\Settings.settings" "F:\Source\Project\Environment\Project\My Project" /Y /I /R
And lastly, here is Publish.bat
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe /target:rebuild "F:\Source\Project\Environment\Project\Project.vbproj" /property:ApplicationRevision=%CCNetLabel% /property:AssemblyName="ProjectEnvironment" /property:PublishUrl="\\Server\bin\Project\Environment\\"
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe /target:publish "F:\Source\Project\Environment\Project\Project.vbproj" /property:ApplicationVersion="1.0.0.%CCNetLabel%" /property:AssemblyVersion="1.0.0.%CCNetLabel%" /property:AssemblyName="ProjectEnvironment"
XCOPY "F:\Source\Project\Environment\Project\bin\Debug\app.publish" "F:\Binary\Project\Environment" /Y /I
XCOPY "F:\Source\Project\Environment\Project\bin\Debug\app.publish\Application Files" "F:\Binary\Project\Environment\Application Files" /Y /I /S
Like I said, it's probably not done the way that CruiseControl and MSBUILD developers had intended things to work, but it does work. If you need to get this working yesterday, it might be the solution you're looking for. Good luck!
I remember doing this last year for a ClickOnce project I was working on. I remember it taking me forever to figure out but here it is. What I wanted my scripts to do was to generate a different installer that pointed to our dev env and a different one for prod. Not only that but i needed it to inject the right versioning information so the existing clients would 'realize' there is a new version out there which is the whole point of clickOnce.
In this script you have to replace with your own server names etc. The trick is to save the publish.htm and project.publish file and inject the new version number based on the version that is provided to you by CC.NET.
Here is what my build script looked like:
<target name="deployProd">
<exec program="<framework_dir>\msbuild.exe" commandline="<project>/<project>.csproj /property:Configuration=PublishProd /property:ApplicationVersion=${build.label}.*;PublishUrl=\\<prod_location>\binups$\;InstallUrl=\\<prod_location>\binups$\;UpdateUrl=\\<prod_location>\binups$\;BootstrapperComponentsUrl=\\<prod_location>\prereqs$\ /target:publish"/>
<copy todir="<project>\bin\PublishProd\<project>.publish">
<fileset basedir=".">
<include name="publish.htm"/>
</fileset>
<filterchain>
<replacetokens>
<token key="CURRENT_VERSION" value="${build.label}"/>
</replacetokens>
</filterchain>
</copy>
</target>
Hope this helps
Just be able passing the ${CCNetLabel} in the CCNET.config msbuild task would be a great improvement.
You want to use the ClickOnce manifest generation tasks in msbuild. The process is a little long winded, so I am just going to point you to a couple of links. Here is the reference on msdn and a sample article to hopefully get you started.