Help us improve Softanics
We use analytics cookies to understand which pages and downloads are useful. No ads. Privacy Policy
Artem Razin
Low-level software protection engineer with 20+ years in native and managed code security. Creator of ArmDot, protecting commercial .NET applications since 2014.

.NET Obfuscation in Azure DevOps Pipelines

If ArmDot is already integrated into your project via MSBuild and NuGet, obfuscation in an Azure DevOps pipeline requires no additional tooling. The pipeline runs dotnet publish or invokes VSBuild, and the MSBuild target handles obfuscation automatically. The only platform-specific setup is passing the license key securely - and Azure DevOps offers two mechanisms for that, depending on your organization's secrets infrastructure.

If you have not yet added the NuGet packages and MSBuild target to your project, do that first: Integrating .NET Obfuscation with MSBuild and NuGet →

A basic pipeline

A standard .NET pipeline in Azure DevOps already produces obfuscated output if the MSBuild target is in the .csproj:

trigger:
  - main
 
pool:
  vmImage: 'ubuntu-latest'
 
variables:
  buildConfiguration: 'Release'
 
steps:
  - task: UseDotNet@2
    inputs:
      packageType: 'sdk'
      version: '8.0.x'
 
  - task: DotNetCoreCLI@2
    displayName: 'Restore'
    inputs:
      command: 'restore'
 
  - task: DotNetCoreCLI@2
    displayName: 'Publish'
    inputs:
      command: 'publish'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
 
  - task: PublishBuildArtifacts@1
    displayName: 'Upload artifact'
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'protected-build'

The DotNetCoreCLI publish task triggers the MSBuild target, which runs ArmDot on the compiled assembly. The published artifact is already obfuscated. In demo mode (no license key), protected assemblies stop working after two weeks.

License key via secure files

Azure DevOps secure files provide a straightforward way to store the license key outside the repository. Upload the license file once, then download it during the pipeline run.

In Azure DevOps, navigate to Pipelines > Library > Secure files and upload the file containing your license key. Then add the download task and pass the file path to the build:

steps:
  - task: DownloadSecureFile@1
    name: armDotLicenseKey
    displayName: 'Download ArmDot license key'
    inputs:
      secureFile: 'armdot-license-key.txt'
 
  - task: DotNetCoreCLI@2
    displayName: 'Publish'
    inputs:
      command: 'publish'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
    env:
      ARMDOT_LICENSE_FILE_PATH: $(armDotLicenseKey.secureFilePath)

The DownloadSecureFile task downloads the file to a temporary location on the agent. The secureFilePath output variable provides the path, which is passed to the MSBuild task via ARMDOT_LICENSE_FILE_PATH.

The first time the pipeline runs, Azure DevOps requires explicit permission to access the secure file. A prompt appears in the pipeline run view - click Permit to authorize access for subsequent runs.

License key via Azure Key Vault

Organizations that already use Azure Key Vault for secrets management can store the license key there and reference it through a variable group. This approach keeps the license key alongside other organizational credentials and uses the same access control policies.

In Azure DevOps, create a variable group under Pipelines > Library, link it to your Key Vault, and select the secret containing the license key. Then reference the variable group in the pipeline:

variables:
  - group: 'ArmDot-Credentials'
  - name: buildConfiguration
    value: 'Release'
 
steps:
  - script: |
      echo "$(ArmDotLicenseKey)" > $(Agent.TempDirectory)/ArmDotLicenseKey
    displayName: 'Write license to file'
 
  - task: DotNetCoreCLI@2
    displayName: 'Publish'
    inputs:
      command: 'publish'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
    env:
      ARMDOT_LICENSE_FILE_PATH: $(Agent.TempDirectory)/ArmDotLicenseKey

The Key Vault approach adds a step - writing the secret value to a temporary file - because the MSBuild task reads a file path rather than a secret value directly. The secret is not logged in build output; Azure DevOps masks Key Vault-backed variable values automatically.

For teams that do not use Key Vault, the secure files approach above is simpler and sufficient.

Multi-stage pipeline with approval gates

Enterprise release pipelines often separate the build from deployment, with a manual approval gate before production artifacts are released. Obfuscation fits naturally into this model because it runs during the build stage, not the deployment stage:

stages:
  - stage: Build
    jobs:
      - job: BuildAndObfuscate
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: DownloadSecureFile@1
            name: armDotLicenseKey
            inputs:
              secureFile: 'armdot-license-key.txt'
          - task: DotNetCoreCLI@2
            displayName: 'Publish'
            inputs:
              command: 'publish'
              arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
            env:
              ARMDOT_LICENSE_FILE_PATH: $(armDotLicenseKey.secureFilePath)
          - task: PublishBuildArtifacts@1
            inputs:
              ArtifactName: 'protected-build'
 
  - stage: Deploy
    dependsOn: Build
    condition: succeeded()
    jobs:
      - deployment: Production
        environment: 'production'
        strategy:
          runOnce:
            deploy:
              steps:
                - script: echo "Deploy obfuscated artifact"

The environment: 'production' reference triggers any approval checks or gates configured on that environment in Azure DevOps. The obfuscated artifact is produced once in the Build stage and consumed by all downstream stages without re-obfuscation.

Debug builds vs release builds

To keep local debug builds unobfuscated while Release builds in the pipeline are protected, add a condition to the MSBuild target in the .csproj:

<Target Name="Protect" AfterTargets="AfterCompile" BeforeTargets="BeforePublish"
        Condition="'$(Configuration)' == 'Release'">

This is the same condition used for GitHub Actions and applies identically in Azure DevOps pipelines.

Agent pool considerations

ArmDot runs on Microsoft-hosted agents (Ubuntu, Windows, macOS) with no platform-specific installation. The NuGet packages are restored during the normal restore step. If your organization uses self-hosted agents, verify that the .NET SDK version matches the project's target framework and that NuGet package restore has network access to nuget.org.

If you use VSBuild@1 instead of DotNetCoreCLI@2, the MSBuild target works identically - the obfuscation task is invoked by MSBuild regardless of which tool triggers the build. Pass the license path via the env block on the VSBuild task the same way.

Classic (GUI) pipelines

Azure DevOps also supports the older classic pipeline editor. The integration works the same way: the MSBuild target fires during the build task regardless of whether the pipeline was defined in YAML or in the classic editor. Add the DownloadSecureFile task before the build task, and set ARMDOT_LICENSE_FILE_PATH as an environment variable on the build task. No YAML required.

Verifying the artifact

Check the build log for [ArmDot] output lines confirming obfuscation ran. A successful run shows Names obfuscation started, Conversion started for method, and Writing protected assembly.

To verify the artifact itself, download it from the build's artifact drop and open the main assembly in ILSpy. Non-public members should show obfuscated names. Virtualized methods should show the VM interpreter loop. String literals should be absent from the decompiled output.

For a complete step-by-step walkthrough with screenshots of the Azure DevOps UI: Obfuscation .NET applications with Azure DevOps Pipelines →

Back to: .NET Obfuscation in CI/CD →

ArmDot in Azure DevOps

ArmDot runs on Microsoft-hosted and self-hosted Azure DevOps agents with no platform-specific installation. NuGet packages restore alongside all other project dependencies. Full documentation →