Build Pipeline
Where the package runs Babel inside the MSBuild pipeline, how to move that step, the targets you can hook, what happens on publish, and how the build tools are selected.
Where Obfuscation Runs
By default the package runs Babel right after CoreCompile, on the assembly the compiler has just written to the intermediate folder, $(IntermediateOutputPath)$(TargetFileName), that is the obj folder. Every later step of the build then works on the obfuscated assembly: the copy to bin, the generation of .deps.json, satellite assemblies, and on publish trimming, single-file bundling, ReadyToRun or NativeAOT compilation, and packaging into an APK or an app bundle.
This ordering is the reason to use the package in SDK-style projects. The .NET SDK rewrites IL after compilation, and Babel has to see the assembly before it does; an assembly taken from bin or from the publish folder has already been through those steps. Placing the task after CoreCompile by hand is fragile, because the exact target sequence depends on the project type and the SDK version. The package resolves that for you.
Two more rules keep the IDE responsive:
- Design-time builds are skipped. Visual Studio and code analyzers run background builds continuously; the package does not inject the obfuscation step into them.
- Obfuscation runs only when the compiler ran. An incremental build that finds the assembly up to date skips Babel as well, so the already obfuscated assembly in
objis never processed twice.
Moving the Obfuscation Step
Three properties change the injection point:
| Property | Effect |
|---|---|
RunBabelAfterBuild | When true, Babel runs at the end of the build, after the output has been copied to bin, and processes $(TargetPath) instead of the intermediate assembly. |
BabelAfterTargets | Runs the obfuscation step after the named MSBuild target. |
BabelBeforeTargets | Runs the obfuscation step before the named MSBuild target. |
Setting any of them disables the default placement after CoreCompile. With BabelAfterTargets or BabelBeforeTargets Babel still processes the intermediate assembly; if the chosen target runs after the output has been copied, point BabelInputFile and BabelOutputFile to $(TargetPath).
A typical use is a Windows Forms application with localized resources, where Babel should run once the satellite assemblies have been generated so that it can process them too:
<PropertyGroup>
<BabelAfterTargets>CoreGenerateSatelliteAssemblies</BabelAfterTargets>
</PropertyGroup>Whatever runs between compilation and the moved obfuscation step sees the unobfuscated assembly. Keep the default placement for trimmed, single-file and AOT-published projects, and for any project where an SDK step rewrites IL after compilation.
Target Sequence and Extension Points
The obfuscation step is the ObfuscateBuild target. It first prepares the Babel task configuration, then runs it:
ObfuscateBuild
ββ ObfuscateSettings
β ββ ObfuscateDefaultSettings renaming defaults, license lookup, babelRules.xml, search directories
β ββ ObfuscateFrameworkSettings selects the assembly to process (obj, or $(TargetPath))
β ββ ObfuscateSetupBabelFiles BabelInputFile / BabelOutputFile, GenerateDebug when a PDB exists
β ββ ObfuscateCustomSettings empty, yours to redefine
β ββ SetupObfuscate empty, yours to redefine
β ββ ConfigureBabel empty, yours to redefine
ββ Obfuscate
ββ BeforeObfuscate empty, yours to redefine
ββ CoreObfuscate runs the Babel task
ββ AfterObfuscate empty, yours to redefineRedefine an empty target in the project file to run your own steps at that point:
| Target | Runs | Use it to |
|---|---|---|
ObfuscateCustomSettings, SetupObfuscate, ConfigureBabel | After the package computed its defaults and the input and output files, before Babel runs | Override computed properties such as BabelInputFile, BabelOutputFile, GenerateDebug or the search directories. |
BeforeObfuscate | Just before the Babel task | Obfuscate a dependency first, compute settings from resolved references, generate rules. |
AfterObfuscate | Just after the Babel task | Copy the map or log file somewhere, run a check on the obfuscated assembly. |
CoreObfuscate publishes two outputs you can read in AfterObfuscate: the BabelExitCode property, and the @(BabelCommandLineArgs) item holding the command line when BabelProvideCommandLineArgs is true.
Overriding the Task Configuration
The package configures the Babel task from the project: search directories from the references, the strong-name key, the PDB, the rules file. When the defaults do not fit, adjust them in BeforeObfuscate. The example below forces debug symbol generation and replaces the search directories with the output folder:
<Target Name="BeforeObfuscate">
<!-- Override computed properties -->
<PropertyGroup>
<GenerateDebug>true</GenerateDebug>
</PropertyGroup>
<!-- Replace the Babel search directories -->
<ItemGroup>
<BabelSearchDirectories Remove="@(BabelSearchDirectories)" />
<BabelSearchDirectories Include="$(TargetDir)" />
</ItemGroup>
</Target>BeforeObfuscate is also the place to run a second Babel task on a dependency before the main assembly is obfuscated, as the Publish .NET App example does to rename the public interface of a NuGet package with cross-assembly renaming.
An AfterObfuscate target can collect the artifacts Babel produced, for example the map file for a shared folder used by the Unit Tests example:
<PropertyGroup>
<GenerateMapOutFile>true</GenerateMapOutFile>
<BabelMapOutFile>$(IntermediateOutputPath)$(TargetFileName).map.xml</BabelMapOutFile>
</PropertyGroup>
<Target Name="AfterObfuscate">
<Copy SourceFiles="$(BabelMapOutFile)" DestinationFolder="$(SolutionDir)MapOut" />
</Target>Publishing
Because obfuscation happens before the publish steps, dotnet publish and Visual Studio publish profiles produce obfuscated output with no extra configuration. The package adds three adjustments on top:
- Merged and embedded assemblies are removed from the publish set. The
UpdateBabelFilesToPublishtarget, which runs afterComputeFilesToPublish, drops everyMergeAssemblyandEmbedAssemblyitem, together with its.pdband.xmlfiles, from the files to publish. SetBabelPublishEnabledtofalseto keep them. - The
.deps.jsonfiles are updated. TheUpdateBabelBuildDependencyFileandUpdateBabelPublishDependencyFiletargets remove the merged and embedded assemblies, and the package itself, from the build and publish dependency manifests, so the host does not look for assemblies that no longer exist as separate files. SetBabelUpdateDependencyFiletofalseto leave the manifests alone. - Debug symbols are not published in Release. The package sets
CopyOutputSymbolsToPublishDirectorytofalsefor the Release configuration unless the project sets it, because PDB files give an attacker the file names and line numbers of the original code. Set it totrueto publish them anyway.
Single-file, trimmed and AOT publishing work with the obfuscated assembly, with two caveats: the desktop tampering check cannot verify a single-file image, and Babel warns about it (see Tampering Detection), and for .NET MAUI iOS the linker should be set to Link SDK assemblies only, as explained in Obfuscate .NET MAUI.
Selecting the Build Tools
The package ships one set of Babel build tools per MSBuild host and selects it from the host that is running the build:
| MSBuild host | Tools folder |
|---|---|
.NET SDK 6.0, 7.0, 8.0, 9.0 or 10.0 (dotnet build, Visual Studio) | tools\net6.0 β¦ tools\net10.0, matching the SDK version |
| Any other .NET SDK version | tools\net9.0 |
.NET Framework MSBuild (MSBuild.exe) | tools\net472 |
| Mono MSBuild | tools\Mono |
The selected folder is exposed as BabelTaskDir, and BabelPackageDir points to the root of the extracted package. To force a different set of tools, set BabelTaskDir in the project file:
<PropertyGroup>
<BabelTaskDir>$(BabelPackageDir)tools\net8.0\</BabelTaskDir>
</PropertyGroup>Set it in the project file or in Directory.Build.targets, not in Directory.Build.props: the packageβs own .props file is imported after Directory.Build.props and would overwrite the value.
To run a Babel installed outside the package, for example the command line tool extracted from a babel_net* zip package, set BabelPath to its folder and, if needed, BabelExe to the executable name (babel.exe or babel.dll).
Build Servers
Nothing in the package is specific to a machine, so a project that builds obfuscated on a developer PC builds obfuscated on any agent that can restore the package and reach a license. The examples cover the common services:
- GitHub Actions β package on GitHub Packages, license key from a repository secret.
- Azure DevOps β package on an Azure Artifacts feed, license key from a secret pipeline variable.
- AppVeyor β package on the account feed, license key from an encrypted variable.
- Unit Tests β testing the obfuscated assemblies in the same pipeline.
Linux agents and containers run the tools\netX.0 build of Babel. Hosts with a broken OpenSSL FIPS configuration need the settings described in FIPS Compliance.