You ran signtool. You got an error about an unrecognized or unsupported file format. Now your build is blocked and you’re not sure whether the problem is the file, the tool, or your signing command.

Here’s the good news: this error has a small number of causes and every single one of them has a straightforward fix. By the end of this post, you’ll know exactly which cause applies to your situation and what to do about it.

 

What you’ll learn in this post:

  • What signtool can and cannot sign (the list is shorter than you think)
  • How to tell if your file is actually a valid PE binary
  • The right tool for every file format (signtool, jarsigner, codesign, Set-AuthenticodeSignature)
  • Why a .exe file sometimes isn’t actually a PE file
  • A quick-reference table so you can solve this in under 60 seconds next time

 

What the Error Is Actually Telling You

When signtool returns a message like ‘The file format is not recognized or is not supported,’ it means one of two things:

  • The file is not a PE (Portable Executable) binary. signtool only understands Windows PE format. Full stop.
  • The file claims to be a PE binary (based on its extension) but isn’t. The bytes inside the file don’t match what signtool expects to find.

 

The key thing to understand: signtool doesn’t go by file extension alone. It reads the actual file structure. A file named setup.exe that’s really a ZIP archive on the inside will fail with this error, even though the extension looks right.

 

The 5 Causes (and How to Fix Each One)

 

Cause 1: You’re trying to sign a file type signtool doesn’t support

signtool signs Authenticode-compatible formats. That list is specific:

 

signtool CAN sign signtool CANNOT sign
.exe, .dll, .sys, .ocx (PE binaries)  
.msi, .msix, .appx (Windows packages)  
.cab (cabinet files)  
.xsn, .xpi (Office and browser add-ins)  
  Scripts: .py, .rb, .sh, .php, .js
  Java archives: .jar, .war, .aar
  Android packages: .apk, .aab
  macOS bundles: .app, .dmg, .pkg
  Archives: .zip, .tar.gz, .7z
  PowerShell scripts: .ps1 (use Set-AuthenticodeSignature instead)

 

How to fix it: if you’re signing a file type that isn’t on the signtool can-sign list, you need the right tool for that format. Jump to the Quick Reference table at the end of this post to find the correct tool for your file type.

 

Cause 2: The file has the right extension but the wrong contents

This is the sneaky one. A file named installer.exe that’s actually a ZIP, a renamed PDF, or an incomplete download will fail this check every time.

How to diagnose it: check the file’s actual magic bytes. Every file format has a signature at the start of the file that identifies what it really is. PE files start with ‘MZ’ (0x4D 0x5A). A ZIP archive starts with ‘PK’ (0x50 0x4B). A PDF starts with ‘%PDF’.

# Check the first bytes of a file in PowerShell:

> $bytes = [System.IO.File]::ReadAllBytes(‘setup.exe’) | Select-Object -First 4

> $bytes | ForEach-Object { ‘{0:X2}’ -f $_ }

# PE binary: 4D 5A (those are ‘MZ’ in ASCII)

# ZIP/MSIX/APPX: 50 4B 03 04

# If you see something else, the file content doesn’t match its extension.

 

Pro tip: The ‘MZ’ header (named after Microsoft founder Mark Zbikowski) has been the PE binary signature since MS-DOS. If your .exe doesn’t start with MZ, it’s not a Windows executable, no matter what the filename says.

 

How to fix it:

  • If the file came from a build pipeline, check whether the build actually completed successfully before the signing step runs.
  • If you downloaded the file, verify the file isn’t corrupted by comparing its SHA-256 hash against the publisher’s listed checksum.
  • If the file was renamed, confirm you have the actual compiled binary and not a source archive or intermediate artifact.

 

Cause 3: The build didn’t finish before signing ran

In CI/CD pipelines, this happens more than you’d think. The build step produces an artifact, but the signing step starts before the compilation finishes writing the output file. The result: signtool opens a partially written file that doesn’t have a valid PE header yet.

This is particularly common with:

  • Post-build scripts that trigger signing before the linker finishes
  • Parallel build steps where signing isn’t correctly gated on build completion
  • Race conditions in build tools that emit a placeholder output file before populating it

How to fix it: ensure the signing step has an explicit dependency on the build completion step in your pipeline configuration. The signing step should never run in parallel with the build step that produces the file being signed.

 

Cause 4: You’re trying to sign a PowerShell script with signtool

This is a very common mistake. PowerShell scripts (.ps1) are text files, not PE binaries. signtool doesn’t sign them. But here’s the thing: PowerShell has its own signing mechanism built right in.

# Sign a PowerShell script (not signtool, use Set-AuthenticodeSignature):

> $cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1

> Set-AuthenticodeSignature -FilePath .\YourScript.ps1 -Certificate $cert

#                            -TimestampServer ‘http://timestamp.digicert.com’

 

# Verify it signed correctly:

> Get-AuthenticodeSignature .\YourScript.ps1

 

Set-AuthenticodeSignature uses the same code signing certificate as signtool. It just works differently under the hood because it’s embedding a signature in the script’s comment block rather than the PE certificate table.

 

Cause 5: You’re trying to sign a format that needs a different tool entirely

signtool is a Windows-only tool for Windows-specific formats. Other platforms and formats have their own signing tools, and mixing them produces this error every time.

 

File type Correct tool Why not signtool
.jar, .war (Java) jarsigner (part of the JDK) JAR is a ZIP-based format; uses Java’s own signing standard
.apk, .aab (Android) apksigner (Android SDK build-tools) Android APK signing uses Android-specific v1/v2/v3/v4 schemes
.app, .dmg, .pkg (macOS) codesign + xcrun notarytool macOS uses Apple’s own signing/notarization system; signtool doesn’t run on macOS
.ps1, .psm1 (PowerShell) Set-AuthenticodeSignature (PowerShell cmdlet) Scripts are text; signature goes in a comment block, not a PE header
.py, .rb, .sh (scripts) Not directly signable with Authenticode Plain text scripts cannot embed PE-format signatures; distribute via signed package instead

 

Pro tip: If you’re building a cross-platform app with Electron, Flutter, or React Native, you need platform-specific signing for each target. Electron Builder handles this automatically if configured correctly: it calls signtool for the Windows build and codesign for the macOS build. The unrecognized format error usually means the tool configuration is pointing at the wrong binary for the wrong platform.

 

How to Diagnose Your Specific Situation in 3 Steps

Not sure which of the five causes applies to you? Walk through these steps:

 

Step 1: What file extension are you signing? If it’s not .exe, .dll, .sys, .msi, .cab, .msix, or .ocx, signtool probably can’t sign it. Jump to the Quick Reference table to find the right tool.

 

Step 2: Is the file extension .exe or .dll but you’re still getting the error? Check the actual file contents. Run the magic bytes check above. If it doesn’t start with MZ, the file isn’t a valid PE binary regardless of its name.

 

Step 3: Is it a valid PE binary but the signing still fails? Check whether the file was fully written before signing. Re-run the build and sign it interactively instead of through a pipeline to rule out a race condition.

 

Quick Reference: File Format to Signing Tool

 

File type Tool to use Command pattern
Windows .exe/.dll/.sys signtool.exe signtool sign /a /fd sha256 /tr [timestamp] /td sha256 file.exe
Windows .msi signtool.exe signtool sign /a /fd sha256 /tr [timestamp] /td sha256 setup.msi
Windows .msix/.appx signtool.exe signtool sign /sha1 [thumbprint] /fd sha256 /tr [timestamp] /td sha256 app.msix
PowerShell .ps1/.psm1 Set-AuthenticodeSignature Set-AuthenticodeSignature -FilePath script.ps1 -Certificate $cert
Java .jar jarsigner jarsigner -keystore keystore.jks app.jar alias
Android .apk apksigner apksigner sign –ks keystore.jks app.apk
macOS .app codesign codesign –sign ‘Developer ID Application: …’ –timestamp App.app
macOS .pkg productsign productsign –sign ‘Developer ID Installer: …’ input.pkg output.pkg
Python/Ruby/Shell scripts Not directly signable Distribute in a signed installer or signed package format

 

The One Thing That Prevents This Error in CI/CD Pipelines

If you’re hitting this error in an automated pipeline, the most reliable prevention is simple: always sign as the very last step after all build artifacts have been fully written to disk.

The pattern that works:

  • Build all executables and packages
  • Run all tests on unsigned binaries
  • Package and bundle everything
  • Sign last, once, before upload or distribution

 

Never sign intermediate build artifacts. Sign only the final output that users will download or install. Signing mid-build and then having the build tool modify the file afterward will break the signature, and signing too early on an incomplete file produces the unrecognized format error.

 

The Bottom Line

The unrecognized file format error always means one of the same five things: you’re signing a file type signtool doesn’t support, the file has the right extension but wrong contents, the build didn’t finish before signing started, you need Set-AuthenticodeSignature for PowerShell scripts, or you need a different tool for a different platform.

Check your file extension against the signtool support list. If that doesn’t solve it, check the actual file contents with the magic bytes check. If you’re in a pipeline, confirm signing is gated on build completion. Nine times out of ten, that sequence resolves it within five minutes.

 

Previous Post
Next Post