How to get the Windows SDK folder in MSBuild? - 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);
}
}

Related

Add .dll reference to nuget that is generated before build

I'm deploying a MsBuild task using nuget, that generates a .dll before each build.
I fail to get the generated dll referenced in the References node in the Visual Studio project of consumers.
I'm also using MSBuild to build the .nupkg file. Generating and compiling works just fine, I deploy the following target in the build/directory
<Project>
<!-- this will automatically run before the 'Build' target -->
<Target Name="GenerateAndBuild" BeforeTargets="Build">
<!--the deployed task that generates the code-->
<Utils.CreateUtilResourceTask/>
<ItemGroup>
<CompileGeneratedSources Include="GeneratedClass.cs" />
</ItemGroup>
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<Csc Sources="#(CompileGeneratedSources )" OutputAssembly="$(OutputPath)Util.dll" TargetType="library" EmitDebugInformation="true" />
<Delete Files="#(CompileGeneratedSources )" />
</Target>
</Project>
That generates the util.dll in the project output folder but I fail to get it referenced in the consuming project.
I thought that this would work in the .csproj file:
<PropertyGroup>
<TargetFrameworks>netstandard1.6;net46</TargetFrameworks>
<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>
<VersionPrefix>0.1.1</VersionPrefix>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageOutputPath>$(MSBuildThisFileDirectory)</PackageOutputPath>
<PackageId>BuildUtil</PackageId>
<!-- that does not add a refenrece in consuming projects -->
<references>
<reference file="$(OutputPath)Util.dll"/>
</references>
<files>
<file src="$(OutputPath)Util.dll"/>
</files>
</PropertyGroup>
Perhaps someone has a hint on that?
You need to declare the assembly reference in an ItemGroup, not a PropertyGroup. For example:
<ItemGroup>
<Reference Include="$(OutputPath)Util.dll" />
</ItemGroup>

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

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>
}

Issue with ApplicationFile file not found in MSBuild script

my issue is that the msbuild script can't find my MSI installer file.
Here is the script file bootstrapper.msbuild :
<Project ToolsVersion="3.5"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<BootstrapperFile Include="Microsoft.Windows.Installer.4.5" >
<ProductName>Windows Installer 4.5</ProductName>
</BootstrapperFile>
<!--<BootstrapperFile Include="DotNetFX40" >
<ProductName>Microsoft DotNet Framework 4.5 SP1</ProductName>
</BootstrapperFile>-->
</ItemGroup>
<!-- from http://stackoverflow.com/questions/346175/use-32bit-program-files-directory-in-msbuild
-->
<PropertyGroup>
<ProgramFiles32>$(MSBuildProgramFiles32)</ProgramFiles32>
<ProgramFiles32 Condition=" '' == '$(ProgramFiles32)'">$(ProgramFiles%28x86%29)</ProgramFiles32>
<ProgramFiles32 Condition=" '' == '$(ProgramFiles32)'">$(ProgramFiles)</ProgramFiles32>
</PropertyGroup>
<Target Name="SetupExe">
<GenerateBootstrapper
ApplicationFile="..\MySetup\MySetup\bin\Debug\MySetup.msi"
ApplicationName="MyApplication"
Culture="en"
BootstrapperItems="#(BootstrapperFile)"
ComponentsLocation="HomeSite"
Path="$(ProgramFiles32)\Microsoft SDKs\Windows\v7.0A\Bootstrapper\"
OutputPath="output"/>
</Target>
</Project>
The folders structure is as follows :
The file boostrapper.msbuild is inside Mybootstrapper. I've tried the path using command line and it works fine. Why not in ms build ? Am I missing something ?
The question is what is the directory the task GenerateBootstrapper is referring to ?
Could you advice please ?
It looks like your relative path is not correct. Try:
ApplicationFile="..\MySetup\bin\Debug\MySetup.msi"
The issue was I didn't put MSI file in the path where the generated setup.exe is located. I was thinking that this later will contain all files...
Am I wrong ?

NuGet pack and msbuild - how can pack reference assemblies in another location?

I am using NuGet pack for the first time, using the AfterBuild target in the .csproj file.
<Target Name="AfterBuild">
<!-- package with NuGet -->
<Exec WorkingDirectory="$(BaseDir)" Command="$(NuGetExePath) pack -Verbose -OutputDirectory $(OutDir) -Symbols -Prop Configuration=$(Configuration)" />
</Target>
This works fine when building the project itself with msbuild ("msbuild MyProj.csproj"). NuGet is able to find the compiled assemblies in projectdir/bin/Release or projectdir/bin/Debug.
But, this project is one of many in a solution and there is a build file dedicated to building the entire solution. The directory tree is like this:
- project root
- build
- src
- MyProj
- MyProj.csproj
- MyProj.nuspec
- AnotherProj
- AnotherProj.csproj
- AnotherProj.nuspec
- project.proj (msbuild file)
This msbuild file overrides the output path of the Visual Studio build.
<PropertyGroup>
<CodeFolder>$(MSBuildProjectDirectory)\src</CodeFolder>
<CodeOutputFolder>$(MSBuildProjectDirectory)\build\$(Configuration)</CodeOutputFolder>
</PropertyGroup>
<Target Name="Build" DependsOnTargets="CleanSolution">
<Message Text="============= Building Solution =============" />
<Message Text="$(OutputPath)" />
<Message Text="$(BuildTargets)" />
<MsBuild Projects="$(CodeFolder)\$(SolutionName)"
Targets="$(BuildTargets)"
Properties="Configuration=$(Configuration);RunCodeAnalysis=$(RunCodeAnalysis);OutDir=$(OutputPath);" />
</Target>
Now that the build redirects the assemblies to the build directory, when I run pack, NuGet can't find them. How do I get NuGet to find the assemblies in the build directory?
Giving NuGet a TargetPath property for where to find the assembly works for this.
<Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release' ">
<!-- package with NuGet -->
<Exec WorkingDirectory="$(BaseDir)" Command="$(NuGetExePath) pack -OutputDirectory $(OutDir) -Symbols -Prop Configuration=$(Configuration);TargetPath=$(OutDir)$(AssemblyName)$(TargetExt)" />
</Target>
Try to define files section in your *.nuspec files, set copy Copy to Output Directory property to Copy always or Copy if newer for them in VS properties. After that all you compiled files and nuspecs will be in $(OutputPath). And then change AfterBuild to:
<Target Name="AfterBuild">
<PropertyGroup>
<NuspecPath>$([System.IO.Path]::Combine($(OutputPath), "$(ProjectName).nuspec"))</NuspecPath>
</PropertyGroup>
<!-- package with NuGet -->
<Exec WorkingDirectory="$(BaseDir)" Command="$(NuGetExePath) pack $(NuspecPath) -Verbose -OutputDirectory $(OutDir) -Symbols -Prop Configuration=$(Configuration)" />
</Target>

MSBUILD Unable to force PackageLocation for Package target

I'm trying to build a deployment package for my web service from msbuild just like you can do in Visual Studio by right-clicking on the project file.
The package gets created fine and is put in the /obj/Release/Packages folder under my source directory for the project file.
You should be able to specify where that package is created by setting the PackageLocation property in a PropertyGroup inside the project file. However, that is not working for me. It still puts the package in /obj/Release/Packages under the source directory.
Here is the snippet from my project file:
<Import Project="$(SrcTreeRoot)\Build\TaskInit.Tasks" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<Import Project="$(SrcTreeRoot)\Build\TaskOverrides.Tasks" />
<PropertyGroup>
<Platform>Any Cpu</Platform>
<Configuration>Dev</Configuration>
<PackageLocation>$(PackageOutputDir)\MatrixOnCdsService\MatrixOnCdsService.zip</PackageLocation>
<PackageAsSingleFile>True</PackageAsSingleFile>
<EnablePackageProcessLoggingAndAssert>True</EnablePackageProcessLoggingAndAssert>
<!--OutDir>$(PackageOutputDir)\MatrixOnCdsService\</OutDir-->
</PropertyGroup>
We are also using a MasterBuild.proj that has the following sections:
<PackageProject Include="..\Source\AnalysisSuite\MatrixOnCdsService\MatrixOnCdsService.csproj"/>
...
<Target Name="Package">
<MSBuild Projects="#(PackageProject)" Targets="Package" Properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=true;
DeployTarget=Package;
PackageLocation=$(PackageLocation);"/>
</Target>
TaskInit.tasks is our own custom import file that contains the PackageOutputDir property that we use to tell the project where to put the package.
My question is why is the package still being placed in the /obj/Release/Packages folder even after specifying the PackageLocation?
Stick the property group you have in a target:
<Target Name="SetValues">
<PropertyGroup>
<Platform>Any Cpu</Platform>
<Configuration>Dev</Configuration>
<PackageLocation>$(PackageOutputDir)\MatrixOnCdsService\MatrixOnCdsService.zip</PackageLocation>
<PackageAsSingleFile>True</PackageAsSingleFile>
<EnablePackageProcessLoggingAndAssert>True</EnablePackageProcessLoggingAndAssert>
<!--OutDir>$(PackageOutputDir)\MatrixOnCdsService\</OutDir-->
</PropertyGroup>
</Target>
then add this as a DependsOnTarget for your Package Target and i think you will have your values passed.
e.g. <Target Name="Package" DependsOnTargets="SetValues">
You could do the following in your MasterBuild.proj.
<Target Name="Package">
<ConvertToAbsolutePath Paths="$(PackageOutputDir)">
<Output TaskParameter="AbsolutePaths" PropertyName="Source_Dir_Abs"/>
</ConvertToAbsolutePath>
<MSBuild Projects="#(PackageProject)" Targets="Package"
properties="Platform=$(Platform);
Configuration=$(Configuration);
DeployOnBuild=false;
DeployTarget=Package;
PackageLocation=$(Source_Dir_Abs)\$(PackageProjectName).zip;
PackageAsSingleFile=true;
ExcludeFilesFromDeployment=Web.config;
_PackageTempDir=$(PackageOutputDir)\temp;">
</MSBuild>
</Target>
Where you are calling msbuild you will need to add a property that will be used in $(PackageProjectName) by doing the following:
msbuild.exe /property:PackageProjectName=$project