Embed File in Assembly with MSBuild Targets

I have a problem – I want to automatically generate a license file then embed the file in my C# assembly. This solution works by creating a new MSBuild targets file and embedding it in my project (csproj) file. Let’s start by modifying my project file.

I imported the new MSBuild targets file near the bottom of my current project file (CustomMSBuild.targets). At the top of the file, I added the folder of the target file on my local machine. My Azure Pipeline build can easily redirect the directory by defining the $(CustomMSBuildDir) property prior to loading this project.

<!-- Allow Azure Pipeline to override custom build directory -->
<CustomMSBuildDir="'$(CustomMSBuildDir)' == ''">C:\Dev\BuildResources\MSBuild\</CustomMSBuildDir>
...
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(CustomMSBuildDir)CustomMSBuild.targets" />

Next in my custom targets file (CustomMSBuild.targets), I have inserted my custom build step. (Additional information on this method of adding build steps is at the end of this article.) I want my task to run before the CoreBuildDependsOn targets. If I pick a target that runs later in the build cycle, the file will not be embedded. (It took me a day to realize that I had done this before I hooked into this earlier step.)

<PropertyGroup Condition="$([System.Text.RegularExpressions.Regex]::IsMatch($(DefineConstants), '^(.*;)*SELECT_43(;.*)*$'))">
  <CoreBuildDependsOn>
    CG_AutogenLicenseFile;
    $(CoreBuildDependsOn)
  </CoreBuildDependsOn>
</PropertyGroup>

Finally, my custom task generates my required file (details omitted), adds it to the collection of files to be embedded and dumps a diagnostic message to the log. The file is written to my main project folder

<Target Name="CG_AutogenLicenseFile">
  <CGTask_AutogenLicenseFile
    <!-- Custom task that generates license file and writes it to filesystem at path "OutputLicenseFilePath" -->
    <Output PropertyName="OutputLicenseFilePath" TaskParameter="OutputLicenseFilePath" />
  </CGTask_AutogenLicenseFile>
  <ItemGroup>
    <EmbeddedResource Include="$(OutputLicenseFilePath)" />
  </ItemGroup>
  <Message Text="License file -> $(OutputLicenseFilePath)" Importance="high" />
</Target>

For those that are curious, these are the targets defined in the target we overrode:

  <PropertyGroup>
    <CoreBuildDependsOn>
      BuildOnlySettings;
      PrepareForBuild;
      PreBuildEvent;
      ResolveReferences;
      PrepareResources;
      ResolveKeySource;
      Compile;
      ExportWindowsMDFile;
      UnmanagedUnregistration;
      GenerateSerializationAssemblies;
      CreateSatelliteAssemblies;
      GenerateManifests;
      GetTargetPath;
      PrepareForRun;
      UnmanagedRegistration;
      IncrementalClean;
      PostBuildEvent
    </CoreBuildDependsOn>
  </PropertyGroup>

Links

Example source: Microsoft.CSharp.targets

Example source: Microsoft.CSharp.CommonVersion.targets

How to: Extend the Visual Studio build process