Creating Bootstrapper Package when all prerequisistes are not in the same root directory setup.exe - wix

I am new to creating Bootstrapper packages. Currently I created Bootstrapper package in which all prerequisistes (VC++, IPP, Windows Installer 3.1, application.mxi) will be inside one single folder and setup.exe will be located outside. For this, I kept product.xml which has product code details for all prerequisites and en folder which has package.xml in the root directory (Bootstrapper\Packages\Dependencies). Actually the problem that I am facing is that, the 'displayname' which is generic for all prerequisites. I want the displayname for list all missed out prerequisites while installing. If I keep all prerquisites in the same root directory of setup.exe with separate product.xml and en folder for each prerequisite, then it shows the displayname properly. But I need folder structure is like setup.exe should be outside the prerequisites. Is there any possibility not to keep product.xml in the root directory of setup.exe?
<Project DefaultTargets="DoPostBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<PropertyGroup>
<MSIName>Test</MSIName>
<MSIFile>$(MSIName).msi</MSIFile>
<DeploymentPath>D:\Test</DeploymentPath>
<TargetPath>$(DeploymentPath)\$(MSIName)</TargetPath>
<BootstrapperPath>D:\Bootstrapper</BootstrapperPath>
<BootstrapperPackagesPath>$(BootstrapperPath)\Packages</BootstrapperPackagesPath>
</PropertyGroup>
<Target Name="CreateTargetDirectory">
<Exec Command="rmdir $(MSIName) /s /q" WorkingDirectory="$(DeploymentPath)" />
<Exec Command="md $(MSIName)" WorkingDirectory="$(DeploymentPath)" />
</Target>
<Target Name="CopyMSI" DependsOnTargets="CreateTargetDirectory">
<copy SourceFiles=".\Test\Release\Test.msi" DestinationFiles="$(TargetPath)\$(MSIFile)" />
</Target>
<Target Name="MakeLinks" DependsOnTargets="CreateTargetDirectory">
<Exec Command="md $(TargetPath)\IPP6_0" />
<Exec WorkingDirectory="$(TargetPath)\IPP6_0" Command="mklink /H ipp_runtime_6_0_x86.msi "
$(BootstrapperPackagesPath)\IPP6_0\ipp_runtime_6_0_x86.msi"" />
<Exec Command="md $(TargetPath)\vcredist_x86_2005" />
<Exec WorkingDirectory="$(TargetPath)\vcredist_x86_2005" Command="mklink /H vcredist_x86.exe "
$(BootstrapperPackagesPath)\vcredist_x86_2005\vcredist_x86.exe"" />
</Target>
<ItemGroup>
<BootstrapperFile Include="IPP.Runtime.6.0.x86">
<ProductName>IPP 6.0</ProductName>
</BootstrapperFile>
<BootstrapperFile Include="Microsoft.Visual.C++.8.0.x86">
<ProductName>VC++ 2005 Runtime</ProductName>
</BootstrapperFile>
</ItemGroup>
<Target Name="BuildBootstrapper" DependsOnTargets="CopyMSI;MakeLinks">
<GenerateBootstrapper
ApplicationFile="$(MSIFile)"
ApplicationName="Test"
BootstrapperItems="#(BootstrapperFile)"
ComponentsLocation="Absolute"
ComponentsUrl="($TargetPath)"
CopyComponents="false"
Culture="en"
Path="$(BootstrapperPath)"
OutputPath="$(DeploymentPath)"/>
</Target>
<Target Name="DoPostBuild" DependsOnTargets="BuildBootstrapper" />
</Project>
}

Related

Copying entire directory using MSBuild

I have the following on an ASP.NET Core 3.1 application:
<Target Name="OnBuild" BeforeTargets="Build">
<Exec WorkingDirectory="approot" Command="npm run build --prod" />
<Copy SourceFiles="approot\dist" DestinationFolder="wwwroot" />
</Target>
When I build I get an error:
The source file "approot/dist" is actually a directory.
The "Copy" task does not support copying directories.
How can I copy the directory approot\dist to wwwroot using MSBuild?
Try this:
<Target Name="OnBuild" BeforeTargets="Build">
<ItemGroup>
<Folder Include="**\approot\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(Folder)" DestinationFolder="wwwroot\%(RecursiveDir)"></Copy>
</Target>

TFS2012 WIX builds and relative paths

I'm setting up CI builds on TFS. A team member developers doesn't like idea of unloading the .wixproj file to edit the msbuild definition (because it is 'hidden') and I kind of agree with this.
So he has created a separate setup.build file which he calls with:
msbuild /t:Build;PublishWebsite;Harvest;WIX setup.build
The contents of which are below:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebSiteSource>..\InstoreApplications\</WebSiteSource>
<SetupF>..\Setup\</SetupF>
<PublishF>publish\</PublishF>
<Publish>$(SetupF)$(PublishF)</Publish>
<WebSiteContentCode>WebSiteContent.wxs</WebSiteContentCode>
<WebSiteContentObject>WebSiteContent.wixobj</WebSiteContentObject>
<!--<MsiOut>bin\\Release\\INT SMK Coles Store WebApi 1.0.1.msi</MsiOut>-->
<MsiName>INT SMK Instore Applications 1.0.2.msi</MsiName>
<MsiOut>bin\Release\$(MsiName)</MsiOut>
</PropertyGroup>
<!-- Defining group of temporary files which is the content of the web site. -->
<ItemGroup>
<WebSiteContent Include="$(WebSiteContentCode)" />
</ItemGroup>
<!-- The list of WIX input files -->
<ItemGroup>
<WixCode Include="Product.wxs" />
<WixCode Include="$(WebSiteContentCode)" />
<WixCode Include="WebAPIDlg.wxs" />
<WixCode Include="IISConfiguration.wxs" />
<WixCode Include="WixUI_InstallDirNoLicense.wxs" />
</ItemGroup>
<!-- The list of WIX after candle files -->
<ItemGroup>
<WixObject Include="Product.wixobj" />
<WixObject Include="$(WebSiteContentObject)" />
<WixObject Include="WebAPIDlg.wixobj" />
<WixObject Include="IISConfiguration.wixobj" />
<WixObject Include="WixUI_InstallDirNoLicense.wixobj" />
</ItemGroup>
<!-- Define default target with name 'Build' -->
<Target Name="Build">
<!-- Compile whole solution in release mode -->
<MSBuild
Projects="..\InstoreApplications.sln"
Targets="ReBuild"
Properties="Configuration=Release" />
</Target>
<Target Name="PublishWebsite">
<!-- Remove complete publish folder in order to
be sure that evrything will be newly compiled -->
<Message Text="Removing publish directory: $(SetupF)"/>
<RemoveDir Directories="$(SetupF)" ContinueOnError="false" />
<Message Text="Start to publish website" Importance="high" />
<MSBuild
Projects="..\\InstoreApplications\InstoreApplications.csproj"
Targets="ResolveReferences;_CopyWebApplication"
Properties="Configuration=Release;WebProjectOutputDir=$(Publish)\;OutDir=$(Publish)bin\;" />
</Target>
<!-- Define creating installer in another target -->
<Target Name="Harvest">
<!-- Harvest all content of published result -->
<Exec
Command='"$(Wix)bin\heat" dir $(Publish) -dr INSTALLDIR -ke -srd -cg ApplicationComponents -var var.publishDir -gg -out $(WebSiteContentCode)'
ContinueOnError="false"
WorkingDirectory="." />
</Target>
<Target Name="WIX">
<!-- At last create an installer -->
<Exec
Command='"$(Wix)bin\candle" -ext WixIISExtension -ext WixUtilExtension -ext WixSqlExtension -dpublishDir=$(Publish) -dMyWebResourceDir=. #(WixCode, &apos; &apos;)'
ContinueOnError="false"
WorkingDirectory="." />
<Exec
Command='"$(Wix)bin\light" -ext WixIISExtension -ext WixUIExtension -ext WixUtilExtension -ext WixSqlExtension -out "$(MsiOut)" #(WixObject, &apos; &apos;)'
ContinueOnError="false"
WorkingDirectory="." />
<!-- A message at the end -->
<Message Text="Install package has been created." />
</Target>
<!-- Optional target for deleting temporary files. Usually after build -->
<Target Name="DeleteTmpFiles">
<RemoveDir Directories="$(Publish)" ContinueOnError="false" />
<RemoveDir Directories="$(SetupF)" ContinueOnError="false" />
<Delete Files="#(WixObject);#(WebSiteContent)" />
</Target>
</Project>
This works absolutely fine on local machine.
For the TFS build I reference the setup.build file directly in the build defination and pass in the: "/t:Build;PublishWebsite;Harvest;WIX" in Advanced>MSBuild Arguments ...this produces a 'success' build however the build is no longer producing an MSI.
I believe the issue is between the HEAT generation of files and WIX picking these files up, note the variables:
<SetupF>..\Setup\</SetupF>
<PublishF>publish\</PublishF>
<Publish>$(SetupF)$(PublishF)</Publish>
The HEAT command does produce a folder with files on the build server, the folder ends up under /src rather than /bin and this is logically the same as building locally
However as the path is relative and I am guessing the WIX candle and/or light commands need their WorkingDirectory changed?...Is there a way to do this and not break the local build and not hardcode the path?
Yes, use a condition to let a property use a different value on a desktop build compared to TFS Team Build (see documentation).
If your script is run from within Visual Studio, check the BuildingInsideVisualStudio MSBuild property.
In TFS 20102, you usually customize your Build workflow to pass an MSBuild script workflow variables (e.g. Pass Relative Path Arguments to MSBuild in TFS2010 Team Build). In TFS 2013 is much easier as you have a lot of useful environment properties (see documentation).

How to publish additional files using msbuild file and TeamCity?

I'm using a msbuild file, TeamCity and Web Deploy to deploy my siteand everything works just fine, for the files included in the Visual Studio csproj file. In addition to these files I want to publish a couple of more files such as license files etc depending on environment.
This is my build file DeployToTest.proj:
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<ItemGroup>
<LicenseSourceFiles Include="License.config"/>
<RobotSourceFile Include="robots.txt" />
</ItemGroup>
<Target Name="Build">
<Message Text="Starting build" />
<MSBuild Projects="..\..\WebApp.sln" Properties="Configuration=Test" ContinueOnError="false" />
<Message Text="##teamcity[buildNumber '$(FullVersion)']"/>
<Message Text="Build successful" />
</Target>
<Target Name="Deploy" DependsOnTargets="Build">
<Copy SourceFiles="#(LicenseSourceFiles)" DestinationFolder="..\..\wwroot"></Copy>
<Copy SourceFiles="#(RobotSourceFile)" DestinationFolder="..\..\wwwroot"></Copy>
<Message Text="Started deploying to test" />
<Exec Command="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe ..\..\wwwroot\WebApp.csproj /property:Configuration=Test /t:MsDeployPublish /p:MsDeployServiceUrl=99.99.99.99;DeployIisAppPath=MySite;username=user;password=pass;allowuntrustedcertificate=true" />
<Message Text="Finished deploying to test" />
</Target>
</Project>
As you can see I tried to copy the license.config and robots.txt without any luck.
This .proj file is selected as the 'Build file path' in TeamCity.
Any suggestions on how I can accomplish this?
To solve this problem it may be worth executing the build script with the verbosity set to the 'detailed' or 'diagnostic' level. That should tell you exactly why the copy step fails.
However one of the most likely problems could be the fact that the script is using relative file paths, which depend on the working directory being set to the correct value. For build scripts I prefer use absolute paths to prevent any file path problems.
To get the absolute path you can use the MSBuildProjectDirectory property. The value of this property points to the path of the directory containing the currently executing MsBuild script. With that you can change your MsBuild script like this:
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<BaseDir>$(MSBuildProjectDirectory)</BaseDir>
</PropertyGroup>
<ItemGroup>
<LicenseSourceFiles Include="$(BaseDir)\License.config"/>
<RobotSourceFile Include="$(BaseDir)\robots.txt" />
</ItemGroup>
<Target Name="Build">
<Message Text="Starting build" />
<MSBuild Projects="$(BaseDir)\..\..\WebApp.sln" Properties="Configuration=Test" ContinueOnError="false" />
<Message Text="##teamcity[buildNumber '$(FullVersion)']"/>
<Message Text="Build successful" />
</Target>
<Target Name="Deploy" DependsOnTargets="Build">
<Copy SourceFiles="#(LicenseSourceFiles)" DestinationFolder="$(BaseDir)\..\..\wwroot"></Copy>
<Copy SourceFiles="#(RobotSourceFile)" DestinationFolder="$(BaseDir)\..\..\wwwroot"></Copy>
<Message Text="Started deploying to test" />
<Exec Command="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe ..\..\wwwroot\WebApp.csproj /property:Configuration=Test /t:MsDeployPublish /p:MsDeployServiceUrl=99.99.99.99;DeployIisAppPath=MySite;username=user;password=pass;allowuntrustedcertificate=true" />
<Message Text="Finished deploying to test" />
</Target>
</Project>
Now this should fix the problem if there is indeed a problem with the relative file paths.
Solution was to change settings for the web project in Visual Studio. Under Package/Publish Web i set 'Items to deploy' to 'All files in this project folder'. I then added a filter to remove all .cs files and other unwanted files.

MSBuild and creating ZIP files

Here is my build script:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<PropertyGroup>
<!-- Path where the solution file is located (.sln) -->
<ProjectPath>W:\Demo</ProjectPath>
<!-- Location of compiled files -->
<DebugPath>W:\Demo\bin\Debug</DebugPath>
<ReleasePath>W:\Demo\bin\Release</ReleasePath>
<!-- Name of the solution to be compiled without the .sln extension --> <ProjectSolutionName>DemoTool</ProjectSolutionName>
<!-- Path where the nightly zip file will be copyd -->
<NightlyBuildPath>W:\Nightly_Builds\Demo</NightlyBuildPath>
<!-- Name of the nighly zip file (YYYYMMDD_NightlyZipName.zip, date added automatically) -->
<NightlyZipName>Demo</NightlyZipName>
</PropertyGroup>
<ItemGroup>
<!-- All files and folders from ./bin/Debug or ./bin/Release what will be added to the nightly zip -->
<DebugApplicationFiles Include="$(DebugPath)\**\*.*" Exclude="$(DebugPath)\*vshost.exe*" />
<ReleaseApplicationFiles Include="$(ReleasePath)\**\*.*" Exclude="$(ReleasePath)\*vshost.exe*" />
</ItemGroup>
<Target Name="DebugBuild">
<Message Text="Building $(ProjectSolutionName) Debug Build" />
<MSBuild Projects="$(ProjectPath)\$(ProjectSolutionName).sln" Targets="Clean" Properties="Configuration=Debug"/>
<MSBuild Projects="$(ProjectPath)\$(ProjectSolutionName).sln" Targets="Build" Properties="Configuration=Debug"/>
<Message Text="$(ProjectSolutionName) Debug Build Complete!" />
<CallTarget Targets="CreateNightlyZip" />
</Target>
<Target Name="CreateNightlyZip">
<PropertyGroup>
<StringDate>$([System.DateTime]::Now.ToString('yyyyMMdd'))</StringDate>
</PropertyGroup>
<MakeDir Directories="$(NightlyBuildPath)"/>
<Zip Files="#(DebugApplicationFiles)"
WorkingDirectory="$(DebugPath)"
ZipFileName="$(NightlyBuildPath)\$(StringDate)_$(NightlyZipName).zip"
ZipLevel="9" />
</Target>
</Project>
My script works perfectly, only there is one strange problem. When i build a project first time and there is no \bin\Debug folder and its created during the build, but the ZIP file still comes empty. Running the build script second time when the \bin\Debug folder is now in place with builded files then the file are added to the ZIP.
What could be the problem that running script first time the ZIP file is empty?
The problem is in the DebugApplicationFiles item collection. It is created before the build is invoked. Move the DebugApplicationFiles into CreateNightlyZip target. Update your script this way:
<Target Name="CreateNightlyZip">
<PropertyGroup>
<StringDate>$([System.DateTime]::Now.ToString('yyyyMMdd'))</StringDate>
</PropertyGroup>
<ItemGroup>
<DebugApplicationFiles Include="$(DebugPath)\**\*.*" Exclude="$(DebugPath)\*vshost.exe*" />
</ItemGroup>
<MakeDir Directories="$(NightlyBuildPath)"/>
<Zip Files="#(DebugApplicationFiles)"
WorkingDirectory="$(DebugPath)"
ZipFileName="$(NightlyBuildPath)\$(StringDate)_$(NightlyZipName).zip"
ZipLevel="9" />
</Target>
If powershell 5.0 or greater is available, you could use powershell command directly.
<Target Name="Zip" BeforeTargets="AfterBuild">
<ItemGroup>
<ZipFiles Include="$(OutDir)release\file1.exe" />
<ZipFiles Include="$(OutDir)release\file2.exe" />
</ItemGroup>
<Exec Command="PowerShell -command Compress-Archive #(ZipFiles, ',') $(OutDir)release\zippedfiles.zip" />
</Target>
Should you wish to zip a whole folder for 'xcopy deploy', since MSBuild 15.8 there is a simple way - the ZipDirectory build task.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ZipOutputPath" AfterTargets="Build">
<ZipDirectory
SourceDirectory="$(OutputPath)"
DestinationFile="$(OutputPath)\..\$(AssemblyName).zip"
Overwrite=="true" />
</Target>
</Project>
[1] https://learn.microsoft.com/en-us/visualstudio/msbuild/zipdirectory-task?view=vs-2019

How to get the Windows SDK folder in MSBuild?

What would be the way to retrieve the Windows SDK folder in an MSBuild task?
Using the generateBootstrapper task I'm creating a bootstrapper for my setup to be able to install the pre-requisites. This task needs the path to the folder where the pre-requisite packages are located, i.e. the Windows SDK folder
"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\"
when using Visual Studio 2008. So far I have been using a hard-coded path but this won't work on any system. Is there a better way to get the path?
This is my build script:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<ItemGroup>
<BootstrapperFile Include="Microsoft.Net.Framework.2.0">
<ProductName>.NET Framework 2.0</ProductName>
</BootstrapperFile>
<BootstrapperFile Include="Microsoft.Windows.Installer.3.1">
<ProductName>Windows Installer 3.1</ProductName>
</BootstrapperFile>
</ItemGroup>
<Target Name="Bootstrapper">
<GenerateBootstrapper ApplicationFile="mySetup.msi"
Culture="de-DE"
ApplicationName="My Application"
OutputPath="$(OutDir)\de-DE"
BootstrapperItems="#(BootstrapperFile)"
Path="C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\" />
<GenerateBootstrapper ApplicationFile="mySetup.msi"
Culture="en-US"
ApplicationName="My Application"
OutputPath="$(OutDir)\en-US"
BootstrapperItems="#(BootstrapperFile)"
Path="C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bootstrapper\Packages\" />
</Target>
</Project>
You can also use the GetFrameworkSdkPath MSBuild task.
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
</GetFrameworkSdkPath>
For example:
<GenerateBootstrapper
ApplicationFile="$(SolutionName).application"
ApplicationName="$(ClickOnceAppTitle)"
ApplicationUrl="$(ClickOnceUrl)"
BootstrapperItems="#(BootstrapperFile)"
Culture="en"
FallbackCulture="en-US"
Path="$(WindowsSDKPath)"
OutputPath="." />
thanks John. According to your post I edited the MSBuild script to read the folder from the registry. It was however not necessary to append "Packages" on the end, that was another mistake in my original script.
The following is the working script:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WindowsSDKPath>$(registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\GenericBootstrapper\3.5#Path)</WindowsSDKPath>
</PropertyGroup>
<ItemGroup>
<BootstrapperFile Include="Microsoft.Net.Framework.2.0">
<ProductName>.NET Framework 2.0</ProductName>
</BootstrapperFile>
<BootstrapperFile Include="Microsoft.Windows.Installer.3.1">
<ProductName>Windows Installer 3.1</ProductName>
</BootstrapperFile>
</ItemGroup>
<Target Name="Bootstrapper">
<GenerateBootstrapper ApplicationFile="mySetup.msi"
Culture="de-DE"
ApplicationName="My Application"
OutputPath="$(OutDir)\de-DE"
BootstrapperItems="#(BootstrapperFile)"
Path="$(WindowsSDKPath)" />
<GenerateBootstrapper ApplicationFile="mySetup.msi"
Culture="en-US"
ApplicationName="My Application"
OutputPath="$(OutDir)\en-US"
BootstrapperItems="#(BootstrapperFile)"
Path="$(WindowsSDKPath)" />
</Target>
</Project>
The install path of the Windows SDK is stored in the CurrentInstallFolder value of the following registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows
I followed the answer from Jeremy D, but that gave the error message:
error MSB3147: Could not find required file 'setup.bin' in 'C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\Engine'.
The reason is that the path to the bootstrapper (at least with V8.0A of the SDK) is a subdirectory under the path returned by the GetFrameworkSdKPath.
So the MSBuild code that works for me is:
<Target Name="AfterBuild">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="WindowsSdkPath"/>
</GetFrameworkSdkPath>
<GenerateBootstrapper
ApplicationFile="myapp.msi"
ApplicationName="MyApplication"
BootstrapperItems="#(BootstrapperFile)"
OutputPath="$(OutputPath)"
Path="$(WindowsSdkPath)\Bootstrapper" />
</Target>
Note the \Bootstrapper suffix to $(WindowsSdkPath)
The path to the bootstrapper is stored under the registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\GenericBootstrapper\3.5
To find out the packages folder, open this, read the "Path" registry value, and append "Packages" on the end and that should give you the full path to the folder you want.
For example:
string bootStrapperPackagesFolder = "";
RegistryKey regKey = Registry.LocalMachine.OpenSubKey
(#"SOFTWARE\Microsoft\GenericBootstrapper\3.5");
if (regKey != null)
{
bootStrapperPackagesFolder = (string)regKey.GetValue("Path");
if (bootStrapperPackagesFolder != null)
{
bootStrapperPackagesFolder += #"Packages\";
Console.WriteLine(bootStrapperPackagesFolder);
}
}