MSBuild is the build system used by Visual Studio, Visual Studio for Mac, and the .NET Core SDK (dotnet). It can also be invoked from the command line. You can integrate Dotfuscator into your .NET projects using Dotfuscator's MSBuild targets. Dotfuscator also provides MSBuild tasks that can be called from your own custom build targets.
This page gives a functional overview of these components and explains best practices for using them. For a complete technical reference, see the MSBuild Reference page.
Locating the MSBuild Components
Dotfuscator's MSBuild components can be found in the following directories, based on the installation package you used:
| Installation Package | Directory |
|---|---|
| Windows Installer | $(MSBuildProgramFiles32)\MSBuild\PreEmptive\Dotfuscator\7 |
| NuGet Package | {install dir}/tools/msbuilddir |
where:
-
$(MSBuildProgramFiles32)is a reserved MSBuild property pointing to theC:\Program Files (x86)directory on 64-bit versions of Windows andC:\Program Fileson 32-bit versions. -
{install dir}is the Dotfuscator installation directory you noted when installing the NuGet package.
Handling Multiple Environments
Because the MSBuild components directory can differ depending on how Dotfuscator was installed, we recommend using an MSBuild property when importing the targets or tasks into your project file. This User Guide uses a property named DotfuscatorMSBuildDir, defined in a project file like so:
<!-- Import environment-specific properties, which may include DotfuscatorMSBuildDir -->
<Import Project="$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))/.dotfuscator.user.props"
Condition="Exists('$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))/.dotfuscator.user.props')"/>
<!-- Set a default value for the DotfuscatorMSBuildDir property -->
<PropertyGroup>
<DotfuscatorMSBuildDir Condition="'$(DotfuscatorMSBuildDir)' == ''">$(MSBuildProgramFiles32)/MSBuild/PreEmptive/Dotfuscator/7</DotfuscatorMSBuildDir>
</PropertyGroup>
On machines where Dotfuscator is installed using the Windows Installer, you do not need to set DotfuscatorMSBuildDir anywhere else. The default value specified in the project file points to the correct directory.
On machines where Dotfuscator is installed using the NuGet package, you need to set the DotfuscatorMSBuildDir property to the NuGet {install dir}. Ways to do this include:
- Setting the property in the
.dotfuscator.user.propsfile. - Setting a
DotfuscatorMSBuildDirenvironment variable (as all environment variables are exposed as MSBuild properties). - Setting the property in a
Directory.Build.propsorDirectory.Build.targetsfile located in an ancestor directory of your project's directory. - When calling the
msbuildordotnetcommand lines, specifying the property as an argument.
Once this property is set as needed, your project can import the targets or use the tasks, specifying $(DotfuscatorMSBuildDir) as the directory.
The .dotfuscator.user.props File
A convenient way to set user-specific MSBuild properties, such as the aforementioned DotfuscatorMSBuildDir, is to create an MSBuild .props file in a well-known directory and have your projects import that file. The project has the same content across environments, but the .props file differs.
This .props file can have any path you want, but for consistency with the examples in this User Guide we recommend creating a .dotfuscator.user.props file in your user directory. (Your user directory is given by an environment variable: USERPROFILE on Windows, HOME on macOS and Linux.)
Here's an example of such a file:
<!-- This is an MSBuild properties file for projects which use Dotfuscator's MSBuild components.
For details, see <https://www.preemptive.com/dotfuscator/pro/userguide/en/interfaces_msbuild.html#user>.
-->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Path where Dotfuscator's MSBuild components are installed. -->
<DotfuscatorMSBuildDir>C:\apps\Dotfuscator\tools\msbuilddir</DotfuscatorMSBuildDir>
<!-- License string for Dotfuscator. -->
<DotfuscatorLicense>00000000000000000000000000000000</DotfuscatorLicense>
</PropertyGroup>
</Project>
This example sets two environment-specific properties:
-
DotfuscatorMSBuildDiris set to a custom directory where Dotfuscator was installed via the NuGet package.If you install via the Windows Installer, then you don't need to set this property.
-
DotfuscatorLicenseis set to a license key formatted as a license string. This causes Dotfuscator to automatically activate itself when the MSBuild targets run. If you call theDotfuscatetask manually, you can get the same effect by passing this property to the task'sLicenseinput parameter.If you activated Dotfuscator with the Windows Installer, Config Editor, or environment variable, then you don't need to set this property.
The .dotfuscator.user.props file can be removed entirely if you have no properties to set.
To import the .dotfuscator.user.props file from a user directory into your project, add the following lines to your project file (.csproj, .vbproj, etc.) if not already present:
<Import Project="$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))/.dotfuscator.user.props"
Condition="Exists('$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))/.dotfuscator.user.props')"/>
Make sure to place these lines earlier in the project file than any uses of the properties it can set.
MSBuild Targets
Dotfuscator provides MSBuild targets that can be imported into Visual Studio projects to automatically apply protection during builds. The advantages of using these MSBuild targets include:
- Dotfuscator applies its protection as part of your normal build process in Visual Studio and MSBuild, instead of requiring you to apply the protection as a separate build step.
- Dotfuscator runs before the packaging steps of your build, making it easier to apply protection to apps distributed through a packaged format, such as Universal Windows (UWP) apps.
- The integration automatically generates and maintains a Dotfuscator config file based on your project's references, ensuring that all of your code is protected, even those in different projects of your solution.
The Protect Your App page illustrates how easy it is to get started using these targets to protect a typical project. This section contains additional details useful for more advanced cases, such as how to choose which projects and configurations to protect.
Once imported and enabled, these MSBuild targets run during the build automatically, after compilation but before packaging steps. If you want tighter control over how and when in the build process Dotfuscator runs, you can call the MSBuild tasks from your own MSBuild targets instead.
For technical details of the MSBuild targets, see Targets Reference.
Choosing What to Protect
Adding Dotfuscator to a build increases your app's security, but it also adds additional time to your builds, introduces the risk of misconfiguration or runtime issues, and can make it harder to debug your app. Given this, you should only use Dotfuscator for projects and build configurations that need it, such as the Release configuration of an executable project.
Choosing Which Projects to Protect
When Dotfuscator is integrated into a Visual Studio project, it protects all assemblies in the project's output directory (e.g., ProjectDir/bin/Release) that originated from the project's solution by default. Because of this, you do not need to integrate Dotfuscator into all projects in your solution in order to protect all the assemblies you distribute.
As an example, consider the following solution, MySolution.sln, and its dependencies.
We want to distribute MyApp.exe and its dependencies to untrusted users, and we want Dotfuscator to protect all of the code we own and distribute. LibA.dll and LibB.dll are never distributed except alongside MyApp.exe.
If we integrate Dotfuscator into just the MyApp project and enable protection for the Release configuration, Dotfuscator protects three assemblies in the MyApp/bin/Release directory:
LibA.dllLibB.dllMyApp.exe
Two assemblies also appear in the MyApp/bin/Release directory but are not protected:
ExtLib1.dllExtLib2.dll
Assemblies in output directories of other projects (e.g., LibA/bin/Release) are also unprotected.
There are three important points to learn from this example:
-
Dotfuscator protects only the assemblies in the integrated project's output directory.
While the copy of
LibA.dllinMyApp/bin/Releaseis protected, copies of the same assembly inLibA/bin/ReleaseandLibB/bin/Releaseare not protected. A similar rule applies forLibB.dll. -
Dotfuscator only needs to be integrated into final output projects, i.e. those whose output you intend to directly distribute.
In this example, integrating Dotfuscator into the
LibAorLibBprojects is unnecessary. The only way an untrusted user would receive theLibA.dllorLibB.dllassemblies would be as dependencies ofMyApp, and Dotfuscator protects the copies of these assemblies in theMyApp/bin/Releasedirectory.If your solution has multiple output projects, you need to integrate Dotfuscator into each. In an alternate scenario, where
LibAis also distributed to untrusted users (such as an API library for developers outside of your organization to reference), we would need to integrate Dotfuscator into both theMyAppproject and theLibAproject, ensuring Library Mode is enabled for the latter. -
Dotfuscator protects only your code (by default).
Assemblies external to your solution, such as from NuGet (
ExtLib1.dll) or a path on the filesystem (ExtLib2.dll), are not protected by default. You can protect external assemblies on disk with theDotfuscatorIncludeAsInputitem metadata as described in "Controlling Which Assemblies Are Protected". This version of Dotfuscator does not support protecting assemblies that have already been packaged into a NuGet Package.
Choosing Which Configurations to Protect
Builds in Visual Studio solutions and projects are parameterized by configuration. By default, most solutions have two configurations, Debug and Release. Each of the projects has matching configurations, so that when we build the solution in Release, all of the projects themselves also build in their respective Release configurations.
Once Dotfuscator is integrated into a project, you need to know which of these configurations should be protected. Here are some guidelines for choosing what to protect:
- You should apply Dotfuscator to all releasable build configurations. You want all builds given to untrusted users to be protected.
- You should apply Dotfuscator to build configurations that will be tested. As with any static analysis tool, Dotfuscator cannot determine everything about your app's behavior. You need to test the protected app to discover and address runtime issues.
- You should NOT apply Dotfuscator to build configurations meant exclusively for debugging. Compared to debugging unprotected code, debugging obfuscated code is a much more challenging and limited experience. Even when you have the source code present and Dotfuscator is configured to automatically emit updated debugging symbols, some debugging features are unavailable. Because of this, we don't recommend running Dotfuscator as part of your local development configurations.
Following these guidelines usually means you protect the Release configuration but not Debug. If your project has more configurations, you may also need to protect some of those; for example, in a Xamarin.iOS project, you should protect Release, Ad-Hoc, and AppStore.
Controlling Which Assemblies Are Protected
When the Dotfuscator MSBuild targets are integrated into a project and enabled, Dotfuscator's default behavior is to protect the integrated project's assembly, as well as every assembly copied to the integrated project's output directory from other projects in the same solution. However, assemblies external to the solution are not protected by default.
You can change this default behavior with DotfuscatorIncludeAsInput. This is a setting that can be applied both as an MSBuild property and as metadata on the MSBuild Reference item.
Excluding Projects
To exclude a referenced project from Dotfuscator, add the DotfuscatorIncludeAsInput tag to the project file (ex: .csproj) of the project you want to exclude, and set the value to false:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Additional Property Tags -->
<DotfuscatorIncludeAsInput>false</DotfuscatorIncludeAsInput>
<!-- Additional Property Tags -->
</PropertyGroup>
</Project>
To re-enable protection on the assembly, you can either delete the tag entirely or set its value to true.
Including External Assemblies
To add an external assembly as an input to Dotfuscator, add the DotfuscatorIncludeAsInput tag to the Reference item for that assembly and set the value to true. You can add the DotfuscatorIncludeAsInput tag to any input project that references the external assembly, not just the project configured with Dotfuscator.
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Reference Include="ExternalAssembly">
<HintPath>../path/to/the/assembly/ExternalAssembly.dll</HintPath>
<DotfuscatorIncludeAsInput>true</DotfuscatorIncludeAsInput>
</Reference>
</ItemGroup>
</Project>
To disable protection on the assembly, change the DotfuscatorIncludeAsInput tag's value to false or remove it entirely.
Finer Details
While the DotfuscatorIncludeAsInput tags used in the examples above may have the same syntax, the first is a MSBuild Project Property, while the second is MSBuild Item Metadata. The usage for both is essentially the same, although the Project Property must be added to a project file which does not directly integrate Dotfuscator. The metadata, however, can be added to a Reference item in any file.
The DotfuscatorIncludeAsInput Project Property is applied recursively. This means that any in-solution project referenced by a protected project is also protected, while an in-solution project referenced by an excluded project is excluded. In the event that an in-solution project is referenced by both protected and excluded projects, the project in question is protected by default.
Dotfuscator ignores DotfuscatorIncludeAsInput metadata on Reference items in excluded projects.
Importing the Targets
Dotfuscator's MSBuild targets file is PreEmptive.Dotfuscator.Common.targets, found in the MSBuild components directory. Thus these targets can be imported into a chosen project by editing the project file in a text editor (or by unloading, editing, and reloading in Visual Studio).
- Since the MSBuild components directory can differ among machines, we recommend setting a
DotfuscatorMSBuildDirproperty to represent the directory's path. See Handling Multiple Environments for details. -
Import the targets by adding the following line to the end of the project file, right before the closing
</Project>tag:<Import Project="$(DotfuscatorMSBuildDir)/PreEmptive.Dotfuscator.Common.targets"/> -
If your project uses the new SDK-based format introduced by .NET Core and .NET Standard, you also need to replace the root
<Project>element'sSdkattribute with explicit imports to ensure Dotfuscator's targets are imported after the SDK's targets. For example, if your project file looks like this:<Project Sdk="Microsoft.NET.Sdk"> <!-- other tags, including those from step 1 --> <Import Project="$(DotfuscatorMSBuildDir)/PreEmptive.Dotfuscator.Common.targets"/> </Project>then update it to look like this:
<Project> <!-- Sdk attribute has been REMOVED --> <!-- This import has been ADDED --> <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" /> <!-- other tags, including those from step 1 --> <!-- This import has been ADDED --> <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" /> <Import Project="$(DotfuscatorMSBuildDir)/PreEmptive.Dotfuscator.Common.targets"/> </Project>In the two new
<Import>tags withSdkattributes, make sure that attribute's value is the same as theSdkattribute that was originally on the<Project>tag.
Setting Properties for the Targets
The targets are controlled by MSBuild properties, which must be declared earlier in the project file than the import statement for the targets. This section explains commonly-used properties; for a full list, see Available Properties.
DotfuscatorEnabled
By default, Dotfuscator does not run during the build. To opt-in, set the DotfuscatorEnabled property to true:
<PropertyGroup>
<DotfuscatorEnabled>true</DotfuscatorEnabled>
</PropertyGroup>
However, you'll probably only want Dotfuscator to protect certain configurations, like Release. To only protect one configuration, specify a Condition for DotfuscatorEnabled:
<PropertyGroup>
<DotfuscatorEnabled Condition="'$(Configuration)' == 'Release'">true</DotfuscatorEnabled>
</PropertyGroup>
To protect multiple configurations (e.g., for Xamarin.iOS apps), repeat the definition with a different Condition for each configuration:
<PropertyGroup>
<DotfuscatorEnabled Condition="'$(Configuration)' == 'Release'">true</DotfuscatorEnabled>
<DotfuscatorEnabled Condition="'$(Configuration)' == 'Ad-Hoc'">true</DotfuscatorEnabled>
<DotfuscatorEnabled Condition="'$(Configuration)' == 'AppStore'">true</DotfuscatorEnabled>
</PropertyGroup>
Alternatively, instead of declaring DotfuscatorEnabled in its own <PropertyGroup>, you can declare it in pre-existing groups that are already selected based on the configuration and target platform. For example:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin/Release/</OutputPath>
<!-- ...other properties... -->
<!-- Enable Dotfuscator for Release|AnyCPU -->
<DotfuscatorEnabled>true</DotfuscatorEnabled>
</PropertyGroup>
If you go this route, don't forget to set DotfuscatorEnabled for all of a configuration's target platforms. For instance, if you want to protect the Release configuration, check for <PropertyGroup> sections for Release|AnyCPU, Release|x86, Release|iPhone, etc.
DotfuscatorGenerateConfigFileIfMissing
To perform protection, Dotfuscator requires a config file (named DotfuscatorConfig.xml by default). When setting up Dotfuscator's MSBuild targets, you should configure the targets to generate this file. To do so, set the DotfuscatorGenerateConfigFileIfMissing property to true:
<PropertyGroup>
<DotfuscatorGenerateConfigFileIfMissing>true</DotfuscatorGenerateConfigFileIfMissing>
</PropertyGroup>
However, once the config file is generated (i.e., after the initial build), you should disable this feature to avoid certain kinds of build issues.
Initial Build
After importing the targets and setting the necessary properties, you can build the project in Visual Studio or through the MSBuild command line. Be sure to select a configuration for which you have set DotfuscatorEnabled to true.
The first time you build, assuming you set DotfuscatorGenerateConfigFileIfMissing to true, Dotfuscator produces a new config file and emit a warning to this effect. You can ignore the warning on the initial build; it's intended to signal possible build issues in subsequent builds, where the config file is expected to already exist.
Every time you build, Dotfuscator applies protection to the assemblies in the project's output directory (e.g., bin/Release). For a detailed example of which assemblies are protected, see Choosing Which Projects to Protect.
Dotfuscator also produces report files as part of every build.
When the build completes, you can run the app locally to ensure it continues to behave as expected.
About the Config File
Dotfuscator uses a config file to control its various protection settings.
By default, this config file is saved as DotfuscatorConfig.xml in your project directory. You can change the path of the config file by setting the DotfuscatorConfigPath property in your project file. (Note that if the config file has already been generated at the old path, you need to move it to the new path to retain your settings.)
During the initial build, Dotfuscator generates a config file which offers a default level of protection. As part of subsequent builds, Dotfuscator keeps the config file up-to-date with any changes you make to project references in the solution.
You can customize and enhance Dotfuscator's protection by editing this config file with the Dotfuscator Config Editor. For a detailed example, see Enhance Protection.
The config file is an input to your build process. You should:
- Add this file to version control. This allows build agents and fellow team members to build the project with the same protection settings. You can also track your team's changes to the file, and thus the protection settings, over time.
-
Disable config file generation. If Dotfuscator can't find the config file during a build, it automatically generates one. While this was helpful for the initial build, leaving this feature enabled for subsequent builds causes these builds to pass, even if the config file is missing. If the config file is removed by accident, this could lead to builds that are only protected with the default settings, rather than your customized protection configuration.
To disable the config file generation feature and have the build error if the config file is missing, edit your project file to change the following property to
false:<DotfuscatorGenerateConfigFileIfMissing>false</DotfuscatorGenerateConfigFileIfMissing> - Never distribute this file outside of your organization. Sensitive information, such as the names of source code elements, can reside in the config file.
Finally, note that the config file appears under your project's node in Visual Studio's Solution Explorer. This is because the MSBuild targets add the config file as an MSBuild item to your project, which ensures Visual Studio considers changes to the config file when deciding whether to build the project.
About the Report Files
Dotfuscator generates various report files whenever it runs as part of a build. The following report files can be generated, depending on the protection settings:
-
Renaming.xml: the renaming report file, also known as the renaming map file. -
Removal.xml: the removal report file. -
SmartObfuscation.xml: the Smart Obfuscation report file.
By default, these reports are stored in the project's DotfuscatorReports subdirectory. You can change the paths for the report files by editing the config file using the Config Editor's Settings tab.
Report files are outputs of a build, much like the assemblies themselves (in the bin directory). You should:
-
Ignore these files in version control. These files are updated every time Dotfuscator runs as part of the build, so your version control should ignore the
DotfuscatorReportssubdirectory. -
Archive these files to a secure, versioned location when releasing the app. When Dotfuscator's Renaming obfuscation is enabled, stack traces from the app contain obfuscated type and method names. This can make it difficult to diagnose issues reported by customers.
To solve this, one of the reports Dotfuscator produces, the renaming map file, associates each renamed code element with the element's original name. When you release your app, you should copy this file (and the other reports) to a secure location. Then, if a customer gives you an obfuscated stack trace, you can pass it and the archived map file to Dotfuscator's tool for decoding obfuscated stack traces, which produces a stack trace matching the original source code.
- Never distribute these files outside of your organization. The information in these reports can be used to undo parts of Dotfuscator's protection, even by those who do not have Dotfuscator.
Subsequent Builds
Once you build the project once and then configure the config file and report files as recommended, you can continue building your project locally. If you push your changes to your team's version control, you also start getting protected builds from your automated build system.
You can continue to develop and test your app with Dotfuscator's protection. You can also enhance this protection.
MSBuild Tasks
Dotfuscator includes MSBuild tasks that can be called from a customized MSBuild project. The most commonly-used task is the Dotfuscate task, which calls Dotfuscator to perform protection specified by an existing Dotfuscator config file.
For a full list of tasks and their associated parameters, see MSBuild Task Reference.
Referencing the Dotfuscate Task
The Dotfuscator MSBuild tasks library, PreEmptive.Dotfuscator.Tasks.dll, is located in the MSBuild components directory. The tasks are exposed in the PreEmptive.Tasks namespace.
To reference the Dotfuscate task for use in your MSBuild project:
- Since the MSBuild components directory can differ among machines, we recommend using a
DotfuscatorMSBuildDirproperty to represent the directory's path. See Handling Multiple Environments for details. -
Add the following as a child of your project file's root
<Project>element. You should place it after theDotfuscatorMSBuildDirproperty is defined, but before any targets where you intend to use theDotfuscatetask.<UsingTask TaskName="PreEmptive.Tasks.Dotfuscate" AssemblyFile="$(DotfuscatorMSBuildDir)/PreEmptive.Dotfuscator.Tasks.dll" />If you are using a version of MSBuild older than 15.0, then replace
$(DotfuscatorMSBuildDir)/with$(DotfuscatorMSBuildDir)/legacy/. For details on the differences between the default "modern" tasks and the "legacy" tasks, see MSBuild Task Reference.
Calling the Dotfuscate Task
Once referenced, you can then call the Dotfuscate task from a custom MSBuild target. As a simple example, consider the following AfterBuild target in a C# project:
<Target Name="AfterBuild">
<Dotfuscate ConfigPath="MyDotfuscatorConfig.xml" License="$(DotfuscatorLicense)">
<Output
TaskParameter="OutputAssemblies"
ItemName="MyDotfuscatorOutputAssemblies" />
</Dotfuscate>
<Message
Importance="high"
Text="Dotfuscator wrote: @(MyDotfuscatorOutputAssemblies)" />
</Target>
This AfterBuild declaration overrides a target predefined by MSBuild. As a result, MSBuild runs the target after running the normal build steps.
We start by having the target call the Dotfuscate task. We provide three task parameters, two inputs and one output:
- We set the required
ConfigPathinput parameter toMyDotfuscatorConfig.xml. Dotfuscator performs the protection specified by that Dotfuscator config file, located in the project's directory. -
We set the optional
Licenseinput parameter to the value in theDotfuscatorLicenseMSBuild property. If you specified this property in a.dotfuscator.user.propsfile, make sure you import that file into your project before usingDotfuscate; e.g.:<Import Project="$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))/.dotfuscator.user.props" Condition="Exists('$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))/.dotfuscator.user.props')"/>When this property is set, Dotfuscator activates itself when it runs. If you don't set this property, you need to activate in a different way.
- We specify that MSBuild items from the
OutputAssembliesoutput parameter are stored as a set of MSBuild items namedMyDotfuscatorOutputAssemblies. This output parameter exposes the full paths to assemblies Dotfuscator wrote.
We then log the paths Dotfuscator wrote by having the target call the Message task.
For a full list of parameters, see Dotfuscate Task.
Deprecated MSBuild Targets
For compatibility with existing projects, Dotfuscator includes a separate set of MSBuild targets defined in the PreEmptive.Dotfuscator.targets file. Because these targets are not designed for easy integration into an existing Visual Studio or MSBuild project, we recommend either using the newer MSBuild targets to integrate Dotfuscator into your project or calling the appropriate MSBuild tasks from your own target.