PowerShell Genie

PowerShell Code signing

Signing a script attaches a cryptographic signature that proves who published it and that the file has not changed since. Set-AuthenticodeSignature signs, Get-AuthenticodeSignature checks.

Lesson 2 min read Windows PowerShell 5.1 and PowerShell 7

Documentation: Microsoft Learn

Overview

You need a code-signing certificate. In an enterprise that comes from your internal CA; for public distribution it comes from a commercial CA. New-SelfSignedCertificate -Type CodeSigningCert makes one for testing, and it is only trusted on machines where you have installed it into the trusted roots, which is fine for a lab and wrong for anything else.

The verification statuses are worth knowing: Valid, NotSigned, HashMismatch when the file changed after signing, UnknownError, and NotTrusted when the signing certificate's chain is not trusted on this machine. NotTrusted is the one you meet with self-signed certificates.

Add a timestamp with -TimestampServer. Without it, the signature stops validating when the certificate expires. With it, the signature remains valid because the timestamp proves the file was signed while the certificate was still good. Skipping this is the mistake that shows up a year later, all at once.

Editing a signed file breaks the signature. Sign as the last step of your release, not while you are still working.

Inspect this example’s structure

Read the command and its supplied arguments. This example requires real PowerShell or its module; this diagram does not execute it.

[pscustomobject]@{ Step1 = 'Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert' Step2 = 'Set-AuthenticodeSignature -FilePath .\script.ps1 -Certificate $cert' Step3 = 'Get-AuthenticodeSignature .\script.ps1' Note = 'timestamp the signature so it outlives the certificate' } | Format-List
  1. Stage 1[pscustomobject]@{Positional values or no named parameters
  2. Stage 2Format-ListPositional values or no named parameters

    An example to inspect

    Preview the example. Sign in for the full lesson and activities.

    [pscustomobject]@{
      Step1 = 'Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert'
      Step2 = 'Set-AuthenticodeSignature -FilePath .\script.ps1 -Certificate $cert'
      Step3 = 'Get-AuthenticodeSignature .\script.ps1'
      Note = 'timestamp the signature so it outlives the certificate'
    } | Format-List

    What happens

    This prints the three commands in order rather than signing anything, since there is no certificate here. Step 1 finds a code-signing certificate in your personal store, step 2 applies it, step 3 verifies the result. The note is the part that gets forgotten until certificates start expiring.

    Worth remembering

    • Set-AuthenticodeSignature signs; Get-AuthenticodeSignature verifies.
    • Self-signed certificates are only trusted where you installed them.
    • HashMismatch means the file was edited after signing.
    • Use -TimestampServer or signatures die with the certificate.

    Primary documentation

    Get-ChildItem reference

    Try it

    Expected output · prints command text; does not execute these commands

    1PS> [pscustomobject]@{
    2PS> Step1 = 'Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert'
    3PS> Step2 = 'Set-AuthenticodeSignature -FilePath .\script.ps1 -Certificate $cert'
    4PS> Step3 = 'Get-AuthenticodeSignature .\script.ps1'
    5PS> Note = 'timestamp the signature so it outlives the certificate'
    6PS> } | Format-List

    Example reader

    Inspect this page's example

    Fictional browser data when supported

    Run supported commands against the practice data, or inspect the example without executing it.

    Ready to inspect.

      Prints the sign-and-verify sequence and the timestamping caveat.

      Show reference output

      Expected output · prints command text; does not execute these commands

      Step1 : Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
      Step2 : Set-AuthenticodeSignature -FilePath .\script.ps1 -Certificate $cert
      Step3 : Get-AuthenticodeSignature .\script.ps1
      Note  : timestamp the signature so it outlives the certificate

      Keep going

      Next: PowerShell Least privilege automation

      Check your understanding

      A signed script changes after signing. What should you check before running it?

      Genie guidePowerShell Code signing

      Signing a script attaches a cryptographic signature that proves who published it and that the file has not changed since. Set-AuthenticodeSignature signs, Get-AuthenticodeSignature checks.