Blog

Automating Code Signing for Windows Installers in Azure DevOps

Your Windows application is ready. The build passes. But without code signing, users see alarming warnings when they try to install it.

That warning box is enough to make many users abandon the download entirely. For software developers, this means lost installs and damaged trust—regardless of how good the application actually is.

Automating code signing within your Azure DevOps pipeline solves this. Every build that reaches release gets signed automatically, with no manual steps required. The process is straightforward once you understand the pieces involved.

What Is Code Signing?

Code signing attaches a digital signature to your executables, DLLs, and installers. It proves the software came from you and hasn't been tampered with since it was built.

For Windows users, this is the difference between a clean "Windows protected your PC" message and a scrolled-out warning that asks if they really want to run something from the internet. Signed software installs cleanly. Unsigned software fights an uphill battle against User Account Control and SmartScreen filters.

The mechanism relies on public key infrastructure. You obtain a code signing certificate from a certificate authority, then use it to sign your artifacts. The certificate contains your identity, and the signature proves nothing was changed after signing.

Prerequisites and Initial Setup

Before touching the pipeline, you need a code signing certificate. Purchase one from a trusted certificate authority—Digicert, Sectigo, and GlobalSign are common choices. The certificate file (usually .pfx or .p12) contains both the public and private keys.

For self-hosted build agents, install the certificate directly on the machine. Export the certificate thumbprint from the local certificate store. You'll need this thumbprint in your pipeline.

For Microsoft-hosted agents, you cannot install certificates directly. Instead, upload your .pfx file to the Secure Files library in Azure DevOps. The pipeline downloads it at runtime and uses it for signing without persisting it anywhere on the agent.

In both cases, store sensitive values like passwords and thumbprints as pipeline variables. Mark them as secret if your pipeline supports it.

Signing DLLs and Executables

Microsoft's SignTool does the heavy lifting. This command-line tool ships with Windows SDK and handles the actual cryptographic signing.

The basic pattern involves calling SignTool with your certificate thumbprint, a trusted timestamp server, and the files you want to sign:

“`

Signtool.exe sign /sha1 "[thumbprint]" /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sm MyProject.*.dll

“`

Notice the parameter order differs from examples you might find online. SignTool accepts parameters in flexible orders, so structuring them clearly helps when you return to edit the pipeline later.

For a complete build scenario, your pipeline first compiles the project to generate all DLLs and the service executable. Then it signs them in a dedicated pipeline task before the installer project runs.

“`

Signtool.exe sign /sha1 "$(CertificateThumbprint)" /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sm MyApp.Service.exe

“`

If your DLLs follow a naming convention, wildcards let you sign them all in a single command rather than calling SignTool separately for each file.

Signing the MSI Installer

For Windows installers built with WiX, there's a catch. By default, the WiX installer project rebuilds all its dependencies during packaging. This means DLLs you already signed get rebuilt and lose their signatures.

The solution is to prevent the rebuild. Pass the BuildProjectReferences property set to false when building the WiX project. This tells MSBuild to use the already-compiled binaries from an earlier build step rather than recompiling them.

Build the installer without rebuilding its dependencies:

“`

Msbuild Installer.wixproj /p:BuildProjectReferences=false /p:Configuration=Release

“`

Now the signed DLLs and executables get packaged into the MSI without modification. You can then sign the resulting installer:

“`

Signtool.exe sign /sha1 "$(CertificateThumbprint)" /tr http://timestamp.digicert.com /td sha256 /fd sha256 /sm MyApp.Installer.msi

“`

The timestamp server (Digicert's in this example) is important. It embeds a trusted timestamp into the signature. When the certificate eventually expires, files signed with a valid timestamp remain valid because the signature proves they were signed while the certificate was still good.

Signing PowerShell Scripts

If your software distributes PowerShell scripts, those should be signed too. Windows may block unsigned scripts from running depending on execution policy settings.

PowerShell provides the Set-AuthenticodeSignature cmdlet for this purpose. First, retrieve the certificate from the certificate store using the thumbprint:

“`

$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "$(CertificateThumbprint)" }

“`

Then apply the signature to matching scripts:

“`

Set-AuthenticodeSignature -FilePath *.ps1 -Certificate $cert -TimestampServer http://timestamp.digicert.com

“`

This signs all PowerShell scripts in the current directory. Add the files parameter or specify individual scripts if you need precision.

Our Analysis

Code signing is one of those practices that feels like overhead until it isn't. The setup takes an afternoon. The benefit—trustworthy software that installs without friction—lasts for every release you ship afterward.

For open-source projects, a code signing certificate costs money but signals professionalism. For commercial software, it's closer to mandatory. Users have been trained to distrust unsigned downloads.

The Azure DevOps integration is clean. Once the certificate thumbprint is in place, adding SignTool calls to any pipeline is trivial. The /sm flag enables cross-machine compatibility, and the timestamp server ensures signatures age well.

The WiX rebuild issue catches many people first. Understanding that BuildProjectReferences=false preserves your signatures saves debugging time later.

Practical Example

Imagine you're publishing a Windows background service that monitors system resources. Your pipeline builds the service executable and two supporting DLLs, signs all three, then packages them into an MSI installer using WiX.

With automated signing in place, the pipeline handles everything. Developers push code, the build runs, artifacts are signed, the installer is packaged and signed, and the result is ready for distribution. No one manually opens certificate files or runs SignTool locally.

What to Watch Next

Publishing checksums alongside your downloads lets users verify integrity independently. Many developers publish both the signature (for automatic trust) and a SHA-256 hash (for manual verification).

Windows SmartScreen reputation builds over time for signed executables. If your software is new, expect initial warnings that diminish as more users run your signed software and Microsoft collects data on it.

Key takeaways

  • Acquire a code signing certificate from a trusted certificate authority before automating anything
  • Store certificate thumbprints and passwords as pipeline variables, marked secret where possible
  • Use BuildProjectReferences=false when building WiX installers to preserve DLL signatures
  • Always include a timestamp server in your SignTool commands so signatures outlast certificate expiry
  • Sign PowerShell scripts if your software distributes them to avoid execution policy blocks

Frequently asked questions

What happens if I don't timestamp my signatures?

When your certificate expires, all files you signed become invalid and trigger security warnings. Timestamp servers embed trusted time into the signature, preserving validity indefinitely.

Can I use a self-signed certificate for testing?

Yes, but only in development. Self-signed certificates produce the same warnings as unsigned software in production environments. Use them to test the signing workflow, then switch to a CA-issued certificate for distribution.

How do I handle signing across multiple build agents?

For self-hosted agents, install the certificate on each machine. For Microsoft-hosted agents, use the Secure Files library to distribute the certificate dynamically to any agent that runs the pipeline.

Do I need to sign every file separately?

No. Wildcards let you sign multiple files matching a pattern in one command. This works well for DLLs that follow naming conventions.

Should I sign both 32-bit and 64-bit DLLs?

Yes. If your application ships both architectures, sign both sets. Unsigned DLLs from either architecture trigger warnings when loaded on systems expecting signed binaries.

Related