How to include heat generated wsx files in a smart way in wix installer? - wpf

It's a WPF application, with Wix Installer.
I have resourceses folder and I want to include these files in the installer to put next to the executable. I solved generating a resources.wxs file with necessary information about the files under the resources folder using the heat tool. I managed to includ into the main wxs file. For that reason I modified the .wixproj file, adding a before build target action to generate the wxs and include it in the main wxs.
Concern: .wixproj is kind of hidden, there thing that you cannot modify from visual studio, like adding a before build action (pre build action is a different story)
Question: How can I extract the before build action into a separate file?
The before build action in the .wixproj:
<Target Name='BeforeBuild'>
<Exec Command='"%WIX%bin\heat" dir $(SolutionDir)resources -dr ResourcesDir -var var.ResourcesDir -cg ResourceFilesGroup -gg -g1 -sfrag -srd -out $(ProjectDir)Resources.wxs' />
<ItemGroup>
<Compile Include='$(ProjectDir)Resources.wxs' />
</ItemGroup>

You can extract it into a separate file—most project file types do that already. That's how they provide common targets to all projects of a type. A .wixproj has this:
<Import Project="$(WixVersionTargetsPath)" />
To augment your own, simply:
Create an XML file like:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name='BeforeBuild'>
<!-- tasks -->
</Target>
</Project>
Add an Import element inside the Project element and refer to that file:
<Import Project="custom.targets" />
If such a file primarily has Target elements, the convention is for it to have the file extension ".targets".
But there are two drawbacks with Visual Studio:
Visual Studio caches all the project file dependencies and runs the MSBuild internally. So, it you edit the external file, it won't be part of builds using Visual Studio until the project is next loaded. To quickly unload and reload a project, use the project context menu in the Solution Explorer. Workaround: Call MSBuild yourself.
When Visual Studio loads a project, if it includes non-standard external files, it gives a warning. (You can disable it per user by project file path, in the registry, if I recall.)
As an alternative to calling heat directly, you might want to look at the Harvest* targets that WiX provides. Note: As the documentation says, you don't invoke them directly (they're already invoked by the Build target); You simply add items to the ItemGroup they process and set properties they use.

Related

SQL Server database project in VS. Order of sql script generation step in build process

I have a SQL Server database project in my solution and several dependencies projects.
When I compiling (in VS) this database project I've get a SQL script and a .dacpac file as result.
But also I want to aggregate all my dependencies projects in one dll and make SQL script/.dacpac file only for this result dll.
I'm using ILMerge.MSBuild.Tasks.ILMerge to aggregate all dll on AfterBuild event in sqlproj. But this aggregation happens after generating SQL script.
How can I enforce SQL script generation in the end?
Build log:
2>------ Rebuild All started: Project: TestCLR, Configuration: Debug Any CPU ------
2> D:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\Roslyn\csc.exe /noconfig /nowarn:1701,1702,2008 /fullpaths /nostdlib+ /errorreport:prompt /warn:4 /define:DEBUG;TRACE /errorendlocation /preferreduilang:en-US /highentropyva+ /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\mscorlib.dll" /reference:D:\Work\FF\Sql-Objects\Tools\TestCLR\bin\Debug\TestLogic.dll /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Data.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.dll" /reference:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.dll" /debug+ /debug:full /optimize- /out:obj\Debug\TestCLR.dll /subsystemversion:6.00 /target:library /warnaserror- /utf8output /langversion:7.3 TestProcedures.cs
2> Loading project references...
2> Loading project files...
2> Building the project model and resolving object interdependencies...
2> Validating the project model...
2> Writing model to D:\Work\FF\Sql-Objects\Tools\TestCLR\obj\Debug\Model.xml...
2> Writing create script to TestCLR_Create.sql...
2> TestCLR -> D:\Work\FF\Sql-Objects\Tools\TestCLR\bin\Debug\TestCLR.dll
2> TestCLR -> D:\Work\FF\Sql-Objects\Tools\TestCLR\bin\Debug\TestCLR.dacpac
2> ILMerge bin\Debug\TestCLR.dll;bin\Debug\TestLogic.dll;bin\Debug\Sider.dll -> D:\Work\FF\Sql-Objects\Tools\TestCLR\bin\Debug\TestCLR.dll
========== Rebuild All: 2 succeeded, 0 failed, 0 skipped ==========
.sqlproj part with ILMerge (Right now it's on the last place. Right before /Project tag )
<UsingTask TaskName="ILMerge.MSBuild.Tasks.ILMerge" AssemblyFile="$(SolutionDir)\packages\ILMerge.MSBuild.Tasks.1.0.0.3\tools\ILMerge.MSBuild.Tasks.dll" />
<Target Name="AfterBuild">
<ItemGroup>
<MergeAsm Include="bin\Debug\*.dll" />
</ItemGroup>
<PropertyGroup>
<MergedAssembly>$(ProjectDir)$(OutDir)RedisCLR.dll</MergedAssembly>
</PropertyGroup>
<Message Text="ILMerge #(MergeAsm) -> $(MergedAssembly)" Importance="high" />
<ILMerge InputAssemblies="#(MergeAsm)" OutputFile="$(MergedAssembly)" TargetKind="SameAsPrimaryAssembly" />
</Target>
Much appreciate Mr. Solomon Rutzky. This solution was wery helpful.
<PropertyGroup>
<SqlBuildDependsOn>
BeforeSqlBuild;
$(SqlBuildDependsOn);
</SqlBuildDependsOn>
</PropertyGroup>
<Target Name="BeforeSqlBuild">
ILMerge things
</Target>
But there was one curious thing. Dll comes to Bin\debug in the end of flow (I think on PostBuildEvent step or near). And if I move ILMerge upper on the flow - it returns error, that there are no DLLs. Changed config to gater dll from obj folder and from dependant project.
Interesting question. I don't know of any easy way to do this, or if it's even possible in the first place. However, there are two options that I know of for altering the build process:
Option 1
Inject a build step / target. SSDT is missing at least two build steps/targets — "BeforeSqlBuild" and "BeforePublish" — because the "AfterBuild" target is too late in the process (as you have discovered).
To get around the "CLR strict security" debacle introduced in SQL Server 2017 (documented in the following post of mine: SQLCLR vs. SQL Server 2017, Part 3: “CLR strict security” – Solution 2), I updated the .sqlproj file by placing the following at the end, just before the closing </Project> tag (to sign the assembly with a certificate):
<PropertyGroup>
<SqlBuildDependsOn>
BeforeSqlBuild;
$(SqlBuildDependsOn);
</SqlBuildDependsOn>
</PropertyGroup>
<Target Name="BeforeSqlBuild">
<Exec Command="{DOS / Windows commands}"/>
</Target>
You can keep the <Exec Command="..."/> and/or add the contents of your existing "AfterBuild" target. HOWEVER, even with this performing the action at the correct time, I'm not entirely certain that you will be able to change the assembly name that it will want to serialize and put in the "Create" script (i.e. switch from using "TestCLR.dll" to "RedisCLR.dll"). You can try re-assigning the build variable, but I've never tried that and am not sure it's allowed. In which case the next option might help.
(Please vote for my enhancement request to improve the SSDT build process:
Add MSBuild predefined Targets for "BeforeSqlBuild" and "BeforePublish" to SSDT SQL Server Database Projects)
Option 2
Use .NET to create a custom build task that has access to the objects, files, and overall process:
Customize Database Build and Deployment by Using Build and Deployment Contributors
BuildContributor Class
BuildContributorContext Class
Walkthrough: Extend Database Project Build to Generate Model Statistics

How to get current wpf project assembly version in wix toolset bootstrapper project?

I have a wpf project for which the installer is created by wix setup project. the wix setup project is compiled to get the .msi. The .exe is generated from the .msi file using wix bootstrapper project.Now my question is how to get the assembly version [assembly: AssemblyFileVersion("x.x.x.xx")] form wpf .cs file?
Andy is right, you will not be able to pull the version number from a .cs file as WiX does not compile or know anything to do with the C# code.
What you want to do is version your main executable file (presumably the .csproj application) using the assembly version properties. (These can be found in the project properties)
1) In the Product.wxs add said main executable as a file in the installer.
<Component Id='MainExecutable' Guid='*'>
<File Id='MainExe' Name='MainExe.exe' Source='Path-to-exe' KeyPath='yes' />
</Component>
2) Bind the version of the .msi to this main executable. This is done in the Version attribute of the Product element.
<Product Id="*" Name="My Product Name" Language="1033" Version="!(bind.FileVersion.MainExe)" Manufacturer="Debabrata" UpgradeCode="PUT-GUID-HERE">
Not that the value after the FileVersion is the ID of your file. This is important.
Now to use this version number in the bootstrapper project - the process is very similar.
1) Add the MSI to the bootstrapper.
<MsiPackage SourceFile="Path-to-msi" Id="MyMSI">
2) In the Version attribute of the Bundle element the binding should be.
<Bundle Name="My Bundle" Version="!(bind.packageVersion.MyMSI)">
Again, note how the ID matches.
Hope this helps!

App.config transforms aren't applied to AppName.exe.config

I have an app.config file in our WPF project that is transformed using the following build target in the .csproj
<!-- MSbuild Task for transforming app.config based on the configuration settings -->
<UsingTask TaskName="TransformXml"
AssemblyFile="$(SolutionDir)\packages\MSBuild.Microsoft.VisualStudio.Web.targets.12.0.4\tools\VSToolsPath\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterBuild">
<!-- Transform the app.config into the correct config file associated to the current build settings -->
<TransformXml Source="App.config" Transform="App.$(Configuration).config" Destination="$(OutputPath)\App.config" />
<!-- Bit of .Net to get all folders and subfolders that we want to delete post-build -->
<ItemGroup>
<FoldersToClean Include="$([System.IO.Directory]::GetDirectories("$(OutputPath)"))" />
</ItemGroup>
<!-- Delete all of our localization folders and .pdb symbols. We only delete PDB files for Release builds. Debug builds we will leave them. -->
<RemoveDir Directories="#(FoldersToClean)" />
<Exec Command="del $(OutputPath)*.pdb" Condition=" '$(Configuration)'=='Release' " />
</Target>
This correctly generates an App.config after the compilation, with my transformed QA or Production configuration files created. The issue I ran into however is that the following code uses AppName.exe.config.
string encryptedString = ConfigurationManager.AppSettings["SqlConnection"];
After doing a bit of reading, I understand why it's doing that. The App.config file ultimately becomes the AppName.exe.config for use during runtime. That's fine; my transformation happens to late though. When the compilation is completed, the AppName.exe.config file contains my base App.config file information, and none of the transformed settings. I assume that is due to the transform happening as a post-build step, where it transforms the App.config after the original App.config file was used to generate AppName.exe.config.
It looks like there isn't any thing preventing me from changing
<TransformXml Source="App.config"
Transform="App.$(Configuration).config"
Destination="$(OutputPath)\App.config" />
so that it replaces the AppName.exe.config file post-build.
<TransformXml Source="App.config"
Transform="App.$(Configuration).config"
Destination="$(OutputPath)\AppName.exe.config" />
Is there anything wrong with this? There isn't much in the way of help when using app.config and transforms for desktop applications. Everything I've read online is for transforming things into a web-config file. I assume this is safe and more or less is the same thing. However I wanted to make sure I'm not missing any glaring side-effects or issues i'll encounter by replacing the original AppName.exe.config with the transformed App.config.
Install this https://github.com/acottais/msbuild.xdt NuGet package and you will be able to create transforms like a web.config.
There are several other Nuget packages that will transform the app.config just like the web.config.
I used this one the other day and it worked great.

Link to files from SDK to output folder of build instead of copying files

I'm investigating to OpenNI SDK ant it's wrappers for .NET. So, I created MSBuild AfterBuild target to copy files from SDK folder (path from environment variable) to build output folder. Now build works on each computer (even if SDK isn't installed). But in this case build is very heavy.
Is there the way to create links to this files in solution? I need build to execute only for computers with installed SDK.
Add an Exists condition to the AfterBuild target, this would prevent the AfterBuild target from running.
<Target Condition="Exists('$(SdkLocation)')" Name="AfterBuild">
...
</Target>
You could also make a BeforeBuild target containing an Error task that will cause the build to break if the SDK is not detected.
<Target Name="BeforeBuild">
<Error Condition="!Exists('$(SdkLocation)')" Text="OpenNI SDK not found." />
</Target>
MsBuild should also be copying the SDK files to the output file if you've added References in the dependent projects. Are you copying extra files?

How can I modify my C# project file so that Resources.resx is compiled into the satellite assembly for my UICulture?

I have a WPF application in C# that may need to be localized in the future. I want to support XAML/BAML localization and conventional resx localization. (The former is useful for most of the code, but some localized content comes from the view model, where it is more straightforward to use resx files.)
I have read the relevant parts of the WPF Localization Guidance. I have set the UICulture property in the msbuild file. I have added the following line to AssemblyInfo.cs:
[assembly: NeutralResourcesLanguage("en", UltimateResourceFallbackLocation.Satellite)]
I understand that I need to copy Resources.resx as Resources.en.resx so that my localized resources get written to the satellite assembly.
I have set the build target on Resources.resx to None and on Resources.en.resx to EmbeddedResource. The custom tool on Resources.resx is PublicResXFileCodeGenerator to generate the strongly typed resources class. I know that the generator only works on a file that doesn't have a culture-specific suffix.
At the moment I must manually keep Resources.resx and Resources.en.resx synchronized. They must be identical. (Rick Stahl explains that here.)
I have tried to modify my C# project file to copy the file automatically. However, I can't get this to work. I'm no msbuild expert! I added the following build target:
<Target Name="BeforeResGen">
<Copy SourceFiles="#(CopyAsLocalizedResources)" DestinationFiles="$(IntermediateOutputPath)Resources.$(UICulture).resx">
<Output ItemName="EmbeddedResource" TaskParameter="DestinationFiles"/>
</Copy>
</Target>
I changed the Build Action for Resources.resx from None to CopyAsLocalizedResources.
I see my Resources.en.resx file being copied to the intermediate directory during build, but my resources aren't found at runtime, and I get an exception. Presumaby they are never being compiled into the satellite assembly.
Can anyone help me achieve this using a modification to the project file?
The compiled language dll is expected to be in a folder named according to the ISO culture code for that culture. This culture-named folder is expected to be in the same directory as the parent assembly.
So, for some foo.dll:
(Default DLL)
bin\foo.dll
(Spanish(Mexico) Resources)
bin\es-mx\foo.Resources.dll
This folder structure and assembly will be made at compilation, so you just need to tweak your post-build action to move the dll in the culture folder to a matching folder in your target directory.
Note that you can perform this move via cp or other command line tools simply by typing the actions you need in the project post-build step box with the project properties in Visual Studio.
Eureka!
<Target Name="CreateLocalizedResources" BeforeTargets="AssignTargetPaths">
<Copy SourceFiles="#(CopyAsLocalizedResources)" DestinationFiles="$(IntermediateOutputPath)Resources.$(UICulture).resx" SkipUnchangedFiles="true" UseHardlinksIfPossible="true">
<Output TaskParameter="DestinationFiles" ItemName="GeneratedLocalizedResources" />
</Copy>
<ItemGroup>
<EmbeddedResource Include="#(GeneratedLocalizedResources)">
<ManifestResourceName>$(RootNamespace).Properties.Resources.$(UICulture)</ManifestResourceName>
</EmbeddedResource>
</ItemGroup>
<Message Text="Generated resources are: #(GeneratedLocalizedResources)" Importance="high" />
</Target>
I set the Build Action on my Resources.resx file to CopyAsLocalizedResources, and the custom build target handles tricking the build into making the proper satellite assembly.

Resources