MSBuild Integration
The WiseOwl.Demeanor.MSBuild NuGet package runs obfuscation as a post-build step. It bundles its own obfuscator binary — the global CLI tool does not need to be installed on the build machine.
Every CLI option has a corresponding MSBuild property. Set them in your .csproj and they apply on every Release build.
Quick setup
dotnet add package WiseOwl.Demeanor.MSBuild
dotnet build -c Release
That’s it. Release builds auto-obfuscate. Debug builds and test projects (anything where IsTestProject=true, set automatically by Microsoft.NET.Test.Sdk) are auto-skipped — obfuscating an xunit / mstest / nunit assembly would break the runner’s reflection-based discovery.
Override the default
<PropertyGroup>
<!-- Disable obfuscation entirely (any config): -->
<Obfuscate>false</Obfuscate>
<!-- Or obfuscate Debug builds too: -->
<Obfuscate>true</Obfuscate>
</PropertyGroup>
Scope
| Property | CLI | Default | Description |
|---|
<Obfuscate> | — | true on Release; false on Debug or when IsTestProject=true | Master switch. Auto-enables for Release builds. Set to false to disable, or true to obfuscate every config. |
<DemeanorIncludePublics> | --include-publics | false | Also obfuscate public/protected symbols. Use for executables; omit for libraries. |
<DemeanorIncludeDeps> | --include-deps | false | Also obfuscate co-located private dependencies. |
<DemeanorOut> | --out | (the project’s output directory) | Where the obfuscated assembly is written. By default it replaces the build output in place, so the rest of the build sees the obfuscated assembly. |
<DemeanorProject> | --project | $(MSBuildProjectDirectory) | Project root holding .demeanor/ — the committed rules and resolved decisions. Passed explicitly on every build so a CI machine reads the same catalog a developer does. |
<DemeanorFailOnPendingDecisions> | --fail-on-pending-decisions | false | Fail the build if a needs-decision rule matched a symbol nobody has resolved. Those symbols keep their names either way and are listed in the report; this makes the build stop instead. |
Naming
| Property | CLI | Default | Description |
|---|
<DemeanorNamingMode> | --names | Alpha | Alpha (a, b, c) or Unicode (whitespace/invisible chars). |
<DemeanorNamespacePrefix> | --prefix | (none) | Namespace prefix for renamed types. Prevents cross-assembly name collisions. |
<DemeanorRename> | --rename | aggressive | How hard renaming is applied: off, on, or aggressive. At aggressive, renamed members share one name through privatescope and property and event metadata is stripped. |
<DemeanorVirtualRename> | --virtual-rename | on | Renaming of overrides of external methods through injected MethodImpl rows: off or on. |
<DemeanorEnumDeletion> | --enum-deletion | on | Deletion of enum member fields: off or on. Reaches only enums that were renamed. |
Protection Toggles
| Property | CLI | Default | Description |
|---|
<DemeanorStringEncryption> | --string-encryption | on | Encryption of literal strings: off or on. |
<DemeanorConstantEncryption> | --constant-encryption | on | Encryption of integer constants: off or on. |
<DemeanorResourceEncryption> | --resource-encryption | on | Encryption of embedded resources: off or on. Applies to a raw resource this assembly reads by name. |
<DemeanorCallHiding> | --call-hiding | on | Relay methods that hide intra-assembly call targets: off or on. |
<DemeanorProxyThreshold> | --proxy-threshold | 16 | Minimum method body size (bytes) eligible for call hiding. |
<DemeanorCfg> | --cfg | flatten | Control flow obfuscation strength, weakest first: none, reorder, predicates, or flatten. |
<DemeanorHinderReflection> | --hinder-reflection | on | Metadata constructs that hinder reflection-based tools, and suppression of ildasm: off or on. |
<DemeanorAntiTamper> | --anti-tamper | on | A module initializer that verifies the assembly file has not been modified: off or on. It verifies a file on disk, so under NativeAOT or single-file publishing the check passes without doing anything. |
<DemeanorAntiDebug> | --anti-debug | on | Debugger detection scattered across method entry points: off or on. |
<DemeanorBaml> | --baml | on | Patching of compiled XAML so it tracks renamed types and properties: off or on. Turning it off freezes everything the BAML references from renaming instead. |
Category Disabling
Disable renaming of entire symbol categories. Useful when a broad set of symbols must keep their names (e.g., all properties in a heavily serialized app).
| Property | CLI | Description |
|---|
<DemeanorRenameTypes> | --rename-types | Type names: off or on. |
<DemeanorRenameMethods> | --rename-methods | Method names: off or on. |
<DemeanorRenameFields> | --rename-fields | Field names: off or on. |
<DemeanorRenameProperties> | --rename-properties | Property names: off or on. |
<DemeanorRenameEvents> | --rename-events | Event names: off or on. |
<DemeanorRenameParameters> | --rename-parameters | Parameter names: off or on. |
<DemeanorRenameEnums> | --rename-enums | Enumeration type names: off or on. |
<DemeanorRenameResourceNames> | --rename-resource-names | Managed resource names: off or on. |
<DemeanorNoSerializable> | --no-serializable | Preserve names of [Serializable] types and their fields. |
Reporting & Incremental Builds
| Property | CLI | Default | Description |
|---|
<DemeanorReport> | --report | true | Emit a JSON name-mapping report for stack-trace decoding. Uses a default path when <DemeanorReportFile> is empty. |
<DemeanorReportFile> | --report <path> | (auto) | Optional explicit path that overrides the default report filename. |
<DemeanorPriorReport> | --prior-report | (none) | Path to a prior version’s report for incremental name stability. |
Satellite Assemblies
| Property | CLI | Default | Description |
|---|
<DemeanorNoSatelliteAssemblies> | --no-satellite-assemblies | false | Leave satellite assemblies untouched. Renaming without updating them makes a localized application fall back to its neutral culture at runtime. |
<DemeanorSaCultures> | --sa-cultures | (all) | Comma-separated list of cultures to process (e.g., fr,de). |
Strong Name Re-Signing
| Property | CLI | Description |
|---|
<DemeanorKeyFile> | --keyfile | Path to a .snk or .pfx file for strong-name re-signing. |
<DemeanorKeyContainer> | --keycontainer | Named key container for strong-name re-signing. |
Output Control
| Property | CLI | Default | Description |
|---|
<DemeanorQuiet> | --quiet | true | Suppress non-error output. Set to false for verbose CI logs. |
<DemeanorVerbose> | --verbose | false | Per-phase stats and full stack traces on error. |
<DemeanorDebug> | --debug | false | Preserve PDB data in obfuscated output. |
Exclusion Item Groups
To exclude types or assemblies from renaming without modifying source code. [Obfuscation] attributes in source are preferred for long-term maintenance. See the Exclusions Guide.
| Item Group | CLI | Description |
|---|
<DemeanorExclude> | --exclude | Fully-qualified type name to exclude. |
<DemeanorExcludeRegex> | --xr | Regex pattern for types to exclude. |
<DemeanorExcludeAssembly> | --xa | Assembly name to skip entirely. |
<DemeanorAddAssembly> | --add-assembly | Include a dynamically referenced assembly. |
Example
<ItemGroup>
<DemeanorExclude Include="MyNamespace.MyType" />
<DemeanorExcludeRegex Include="MyNamespace\.Dtos\..*" />
<DemeanorExcludeAssembly Include="ThirdParty.Library" />
</ItemGroup>
Complete Example
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<!-- Obfuscation auto-enables on Release; no <Obfuscate>true</Obfuscate> needed. -->
<DemeanorIncludeDeps>true</DemeanorIncludeDeps>
<!-- Use incremental builds to preserve name stability -->
<DemeanorPriorReport>$(MSBuildProjectDirectory)\prior.report.json</DemeanorPriorReport>
<!-- Re-sign after obfuscation -->
<DemeanorKeyFile>$(MSBuildProjectDirectory)\MyApp.snk</DemeanorKeyFile>
</PropertyGroup>
<ItemGroup>
<!-- Exclude types that use custom reflection -->
<DemeanorExcludeRegex Include="MyApp\.Plugins\..*" />
</ItemGroup>
Any [Obfuscation] attributes in source carry over automatically — the engine reads them from the assembly metadata.