Skip to content

How to code-sign Windows apps, including an automation script

You build a Windows app, zip it up, hand it to a user, and Windows may hit them with “Windows protected your PC”. A clean but unsigned binary has no publisher identity or established SmartScreen reputation, so Windows has little reason to trust it.

This guide walks you through the complete process of signing EXE and DLL files on Windows with Azure Artifact Signing.

Here’s the shape of it, so you know what you’re signing up for:

  • One-time setup (Steps 1–9): Azure account, portal clicks, and identity validation. This is the slow part. Budget a day to a week for the identity check to clear.
  • Every build (Step 10): one script that signs your exe and DLLs in seconds.

If you’ve already done the setup, skip straight to Step 10.

Why I Picked Azure Artifact Signing

  • $9.99/mo for up to 5,000 signatures (Basic tier), with no USB hardware token to manage.
  • Fully managed. Microsoft rotates the certificates daily behind the scenes. You never see or store a private key.
  • Identity-based publisher reputation. Public Trust signing ties the signature to your validated identity instead of a certificate you manage yourself. SmartScreen warnings can still appear for first-time or low-volume downloads; signing does not guarantee they will disappear immediately.

A note on names: this service was called “Azure Trusted Signing” until recently. Same thing, new name. If you see “Trusted Signing” in older docs or screenshots, it’s the same service.


Prerequisites

  1. A Microsoft account (you get one when you sign up with Azure).
  2. paid Azure subscription. ⚠️ This is the #1 early blocker: Artifact Signing does not work on free/trial/sponsored subscriptions. Upgrade to pay-as-you-go first (Cost Management + Billing → your subscription → upgrade) – https://azure.microsoft.com/en-us/products/artifact-signing.

Step 1 — Register the resource provider

Quick orientation first: an Azure subscription is the billing container for everything you create. Azure charges your services to it, and it also defines where those resources live and who can access them. When you signed up, Azure created one for you automatically (often with a forgettable default name like “Azure subscription 1”).

A separate subscription is optional. If you want clean billing and access boundaries for signing, create or dedicate one; otherwise, use your existing paid subscription and keep the signing resources in a dedicated resource group.

Then:

  1. Portal → Subscriptions → the subscription you chose.
  2. Left menu → Settings → Resource providers.
  3. Find Microsoft.CodeSigning → click the  → Register. Wait for Registered.

Step 2 — Create the Artifact Signing account

Keep the signing resources together in the same subscription and a dedicated resource group.

  1. Portal search → Artifact Signing Accounts → Create.
  2. Subscription: the paid subscription you chose. Resource group: new, e.g. rg-artifact-signing.
  3. Account name: 3–24 alphanumeric, starts with a letter, globally unique (no consecutive hyphens). e.g. xojoappsigning.
  4. Region: pick one you’ll remember; it determines your signing endpoint.
  5. PricingBasic unless you’ll exceed 5,000 signs/month.
  6. Create, then note the Account URI on the overview page. It follows the pattern https://<region-code>.codesigning.azure.net/.

Step 3 — Assign the Identity Verifier role

You need this before the identity-validation button becomes clickable.

  1. From your Artifact Signing account, left menu → Access control (IAM) → Add role assignment.
  2. Role: Artifact Signing Identity Verifier (use the search field) → assign to your own user account.

Step 4 — Identity validation (the real gate)

From your Artifact Signing account (the one you created in Step 2), go to the left menu → Objects → Identity validations → New Identity → Public.

Public is what you want for normal app signing (it’s what SmartScreen cares about).

Fill in your legal business name, website, emails, business identifier (tax ID / DUNS), and address. Two gotchas that bite everyone:

  • The secondary email must be on the same domain as the primary.
  • Individual developers: the form pulls from your Azure billing account, and its name/address must exactly match your ID.

What happens next: status goes In Progress → Action Required (this is the point when you may have to wait a few hours to days… In my case, it took only an hour). You’ll be routed through a third-party verifier (AU10TIX). You upload a government ID and a selfie from your phone, then add the resulting Verified ID to Microsoft Authenticator and share it back (another hour or so).

Timeline: officially 1–20 business days; in practice often hours to a few days. If it stalls, re-submit with a different identifier (tax ID instead of DUNS) and make sure your company name/address match your documents exactly.


Step 5 — Create the certificate profile

  1. From your Artifact Signing account, left menu → Objects → Certificate profiles → Create.
  2. Type: Public Trust.
  3. Name it (5–100 chars), then pick your completed identity validation under Verified CN and O.
  4. Create. You never see or hold the certificate. It’s rotated daily behind the scenes. The public cert is embedded in every file you sign.

Step 6 — Assign the Certificate Profile Signer role

This is the role that lets you actually sign. Because we’re using the Azure CLI, it goes on your user. No app registration is needed (this is a separate/alternative process, but we’re not talking about that in this article).

  1. From your Artifact Signing account, left menu → Access control (IAM) → Add role assignment.
  2. Role: Artifact Signing Certificate Profile Signer → assign to your own user account.

Give role assignments a few minutes to propagate. If you try signing immediately and hit a 403, wait ~5 minutes and retry.


Step 7 — Install the signing tools

First, the Azure CLI: winget install Microsoft.AzureCLI

You’ll use it in Step 9 to authenticate with Azure.

SignTool comes with the Windows SDK. Install it with: winget install --id Microsoft.WindowsSDK --exact

⚠️ Run this to find the newest x64 signtool.exe:
Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter signtool.exe | Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } | Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName

Note the path down.

.NET 8 runtime (x64).

Microsoft Visual C++ Redistributable.

Artifact Signing Client Tools:winget install -e --id Microsoft.Azure.ArtifactSigningClientTools
This installs Azure.CodeSigning.Dlib.dll (the plugin signtool uses to talk to Azure).

⚠️ Locate the actual dll path after install:
Get-ChildItem -Path "C:\Program Files","$env:LOCALAPPDATA" -Recurse -Filter Azure.CodeSigning.Dlib.dll -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName

Note the path down.

If signtool gives you “command not found”, that’s normal; it’s not on PATH. Use the full path, or add ...\10\<version>\x64 to your PATH.


Step 8 — Create the metadata file

This is a small JSON with three values. Name it anything you like, e.g. artifact-code-signing.json:

{
	"Endpoint": "https://<region-code>.codesigning.azure.net",
	"CodeSigningAccountName": "xojoappsigning",
	"CertificateProfileName": "...."
}

This JSON file will be used a lot, so save it somewhere safe.

  • Endpoint = your account URI (region-specific; must match where the account + profile live, or you get a 403).
  • CodeSigningAccountName = the Artifact Signing account name from Step 2.
  • CertificateProfileName = the certificate profile name you created in Step 5.

Step 9 — Authenticate with the Azure CLI

az login

This opens your browser; sign in with the Microsoft account tied to your Azure subscription. When signtool runs, the dlib picks up your logged-in Azure CLI session automatically.

If you have multiple Azure subscriptions, point the CLI at the right one, then confirm it took:

az account set --subscription "<your subscription id>"
az account show --query name -o tsv

The second command should print your code-signing subscription’s name. If you also have multiple Microsoft tenants (a personal account and a work account, for example), log straight into the right one:

az login --tenant "<tenant id>"

You may occasionally need to re-run az login after long idle periods.


Step 10 — Sign your Xojo app

Just remember, you need to sign the compiled application after each new build.

What to sign (and skip)

Your Xojo Windows build folder looks roughly like this. Sign the binaries you build or distribute under your publisher identity; leave Xojo, Microsoft, and other vendor-signed dependencies alone.

Windows 64 bit/
└── YourApp/
    ├── YourApp.exe              ← sign
    ├── <your own DLLs>          ← sign
    ├── XojoGUIFramework64.dll   ← skip, Xojo provides signed DLLs
    ├── YourApp Libs/            ← skip, Xojo provides signed DLLs
    ├── msvcp140.dll, vcruntime*.dll …  ← skip (Microsoft runtime)
    └── YourApp.exe.WebView2/    ← skip (WebView2 cache)

The script

Save this as sign.ps1, next to the Xojo project, update the paths at the top, and run it from PowerShell:

$ErrorActionPreference = "Stop"

$Signtool    = "C:\...\signtool.exe"                            # path from Step 7
$Dlib        = "C:\...\Azure.CodeSigning.Dlib.dll"              # path from Step 7
$Metadata    = "C:\...\artifact-code-signing.json"              # from Step 8
$BuildFolder = "F:\...\YourApp"                                 # folder containing YourApp.exe

# Installer (Inno Setup) — optional. Set $EnableInno to $false to skip this part.
$EnableInno  = $true
$InnoSetup   = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
$IssScript   = "F:\...\YourAppSetup.iss"                        # your Inno Setup script
$Installer   = "F:\...\YourAppSetup.exe"                        # output from the .iss

# Sign binaries you build or distribute under your publisher identity.
# Leave Xojo, Microsoft, and other vendor-signed dependencies alone.
$FilesToSign = @(
    "$BuildFolder\YourApp.exe"
    "$BuildFolder\MyPlugin.dll"
    # add any other files you own here, one per line
)

function Invoke-SigningCommand {
    param([string[]]$Arguments)

    & $Signtool @Arguments
    if ($LASTEXITCODE -ne 0) {
        throw "SignTool failed with exit code $LASTEXITCODE."
    }
}

foreach ($f in $FilesToSign) {
    Invoke-SigningCommand @(
        "sign", "/v", "/fd", "SHA256",
        "/tr", "http://timestamp.acs.microsoft.com", "/td", "SHA256",
        "/dlib", $Dlib, "/dmdf", $Metadata,
        $f
    )
}

if ($EnableInno) {
    # Build a fresh installer, then sign it.
    & $InnoSetup $IssScript
    if ($LASTEXITCODE -ne 0) {
        throw "Inno Setup failed with exit code $LASTEXITCODE."
    }

    Invoke-SigningCommand @(
        "sign", "/v", "/fd", "SHA256",
        "/tr", "http://timestamp.acs.microsoft.com", "/td", "SHA256",
        "/dlib", $Dlib, "/dmdf", $Metadata,
        $Installer
    )
}

What the flags mean:

  • /tr http://timestamp.acs.microsoft.com: the timestamp server. The http:// (not https) is intentional.
  • /fd SHA256 and /td SHA256: SHA-256 for the file and the timestamp.
  • /dlib: the Azure plugin from Step 7. /dmdf: your metadata JSON from Step 8.

Confirm it worked

Two checks:

  1. Signtool:$Signtool verify /v /pa "$BuildFolder\YourApp.exe" You want to see Successfully verified.
  2. Or: right-click YourApp.exe → Properties → Digital Signatures → your org name should be listed.

Rules of thumb

  • Sign after every build. Each new .exe is a fresh unsigned binary.
  • Sign before you zip. Sign the files, then package them. Signing inside a zip does nothing.

Troubleshooting

SymptomFix
signtool: command not foundUse the full SDK path; it’s not on PATH
No certificates were founddlib path wrong, or x86/x64 mismatch between signtool and dlib
403 ForbiddenSigner role on the wrong account, wrong account name in JSON, or endpoint/region mismatch
SignTool Error: An unexpected internal errorUpdate signtool to ≥ 10.0.22621.755; check .NET 8 + VC++ redist
Signing appeared to succeed, but no signature is visibleRun signtool verify /v /pa <file>; its exit code and Successfully verified output are authoritative. Explorer’s Digital Signatures tab is not available for every file type.

Recap

One time (Steps 1–9): register the Microsoft.CodeSigning provider → create the account → assign the Identity Verifier role → complete identity validation (slow’ish) → create the certificate profile → assign the Signer role → install tools → write the metadata JSON → az login.

Every build (Step 10): run sign.ps1.


The bottom line

Azure Artifact Signing is a low-cost managed option for signing Windows apps, and Azure CLI authentication means local builds do not need a stored signing private key. The setup has some Azure bureaucracy, and identity validation is the slow part. After that, it is one script per build.

Gabriel is a digital marketing enthusiast who loves coding with Xojo to create cool software tools for any platform. He is always eager to learn and share new ideas!