As automation becomes more prevalent, so do the associated security risks. A tampered script or one obtained from an untrusted source can make extensive changes to a system. In enterprise environments, this raises an important question: how can organizations ensure that only verified and unaltered scripts are executed?
Script integrity is only one aspect of secure automation. Organizations also need controls around who can execute sensitive scripts and under what conditions.
One way to validate the origin and integrity of PowerShell scripts is through digital code signing. In this process, a script is signed with a code signing certificate. The resulting signature makes it possible to verify who signed the script and whether it has been modified since it was signed.
In this article, we'll look at how PowerShell scripts are signed, the different types of certificates that can be used, and the role of PowerShell's Execution Policy. We'll also cover private key protection, certificate chains, timestamping, and best practices for deploying code signing in production enterprise environments.
What Are the Benefits of Signing a PowerShell Script?
When a PowerShell script is signed, a digital certificate is attached to the script.
This allows you to verify two key properties:
- Authenticity: Who signed the script?
- Integrity: Has the script been modified since it was signed?
For example, if a signed script is edited after signing, its signature becomes invalid.
This is particularly valuable in environments where scripts are centrally distributed and executed. Administrators can ensure that a modified or tampered version of a script is not accidentally or intentionally run.
Related reading: Learn why version control remains essential even when scripts are signed in Version Controlling Automation Scripts: Why Git Alone Is Not Enough.
However, it's important to understand that a digital signature does not automatically make a script safe. A signed script can still contain bugs or even malicious code. The primary purpose of the signature is to answer two questions:
- Who signed it?
- Has it been changed since it was signed?
What Certificate Is Required?
To sign a PowerShell script, you need a certificate that includes a private key.
Several certificate types can be used for this purpose.
Self-Signed Certificate
For testing and development environments, a self-signed certificate is often sufficient.
You can create one directly in PowerShell:
1 $cert = New-SelfSignedCertificate `
2 -Subject "CN=PowerShell Code Signing" `
3 -Type CodeSigningCert `
4 -CertStoreLocation "Cert:\CurrentUser\My" The certificate can then be found in the current user's certificate store:
1 Get-ChildItem Cert:\CurrentUser\My -CodeSigningCertThe important part is the certificate type:
1CodeSigningCertThis indicates that the certificate is intended for code-signing purposes.
Certificate Issued by an Internal PKI
Many organizations already operate an internal Public Key Infrastructure (PKI).
In this scenario, the internal Certification Authority (CA) can issue a code signing certificate. This provides a significant advantage: clients that trust the organization's PKI will generally also trust the certificate chain associated with the signing certificate.
For production environments, this is often the preferred approach over using a self-signed certificate.
Publicly Issued Certificate
Alternatively, a certificate from a public Certificate Authority can be used.
This may be the better option when scripts are distributed outside the organization or executed on systems that do not trust the internal PKI.
Signing a PowerShell Script
Let's assume the script is located at:
C:\Scripts\Backup.ps1
First, identify available code signing certificates:
1Get-ChildItem Cert:\CurrentUser\My -CodeSigningCertThe results will include the certificate thumbprint.
You can then sign the script:
1 Set-AuthenticodeSignature `
2 -FilePath "C:\Scripts\Backup.ps1" `
3 -Certificate $certHere, $cert refers to the selected certificate.
Alternatively, you can load a certificate directly by its thumbprint:
1 $cert = Get-ChildItem Cert:\CurrentUser\My\THUMBPRINT
2
3 Set-AuthenticodeSignature `
4 -FilePath "C:\Scripts\Backup.ps1" `
5 -Certificate $certReplace THUMBPRINT with the actual thumbprint of your certificate.
Verifying the Signature
After signing the script, the signature should be validated.
PowerShell provides the Get-AuthenticodeSignature cmdlet for this purpose:
1 Get-AuthenticodeSignature -FilePath "C:\Scripts\Backup.ps1" A successfully signed script should show a status such as:
1 Status : ValidYou can also view the certificate used to sign the script:
1 $signature = Get-AuthenticodeSignature "C:\Scripts\Backup.ps1"
2
3 $signature.SignerCertificate.Subject This allows you to confirm exactly which certificate was used.
What Happens if the Script Is Modified?
One of the most important features of digital signatures is integrity validation.
Assume a script has been successfully signed.
Later, someone adds the following line:
1 Write-Host "Test" The original signature no longer matches the script contents.
As a result, the next signature check will fail and the signature will no longer be considered valid.
This is one of the key benefits of code signing: any modification made after signing becomes immediately detectable.
Important: Every time a script is modified, it must be signed again.
Why Timestamping Matters
Whenever possible, a timestamp should be applied when signing a script.
The timestamp records when the signature was created, which is especially important for long-term validation.
Consider this example:
- The script is signed in 2026.
- The certificate remains valid until the end of 2027.
- The script is still needed after the certificate expires.
A trusted timestamp can prove that the signature was created while the certificate was still valid.
With Set-AuthenticodeSignature, you can specify a timestamp server:
1 Set-AuthenticodeSignature `
2 -FilePath "C:\Scripts\Backup.ps1" `
3 -Certificate $cert `
4 -TimestampServer "https://example.com/timestamp"The specific timestamp service should align with your PKI or certificate provider.
Best practice: Use a reliable timestamp service for all production signing operations.
Execution Policy and Signed Scripts
A common misconception is:
"If a script is signed, PowerShell will automatically run it."
That's not entirely true.
PowerShell uses the Execution Policy to determine under which conditions scripts are permitted to run.
You can view the current configuration by running:
1 Get-ExecutionPolicy -List
Common values include:
· Restricted
· AllSigned
· RemoteSigned
· Unrestricted
· Bypass
For signed scripts, AllSigned and RemoteSigned are particularly relevant
AllSigned
With:
1 Set-ExecutionPolicy AllSigned all PowerShell scripts must be digitally signed.
This includes scripts created locally.
While this approach can improve security in enterprise environments, it also increases administrative overhead.
RemoteSigned
With:
1 Set-ExecutionPolicy RemoteSignedscripts downloaded from the internet, or otherwise marked as originating from an external source, must be signed.
Locally created scripts can generally be executed without a signature.
The appropriate setting depends on the organization's security architecture. In enterprise environments, Execution Policy should be considered alongside other controls such as application control technologies and centralized security policies.
A Valid Signature Is Not Enough, the Certificate Must Also Be Trusted
A valid digital signature does not automatically mean that the computer trusts the signer.
There are two separate questions that must be considered:
Is the signature cryptographically valid?
and
Does the computer trust the issuing certificate and its certificate chain?
For example, a self-signed certificate may produce a technically valid signature, yet the certificate itself may not be trusted on another system.
This is one of the key reasons why a proper PKI is so important in enterprise environments.
Distributing the Certificate Chain Correctly
Let's assume an organization operates an internal Microsoft PKI.
A code-signing certificate has been issued by an internal Certification Authority (CA).
For clients to trust the certificate, the corresponding certificate chain must be available and trusted on those systems.
Depending on the PKI design, this may include:
- Root CA
- Intermediate CA
- Code-signing certificate
In Active Directory environments, the required certificates can typically be distributed through Group Policy.
This avoids the need for administrators to manually import certificates on every individual system.
Protecting the Private Key
The most important part of a code-signing certificate is not the public certificate itself, but the private key.
Anyone who gains access to the private key may be able to sign new scripts on behalf of the certificate owner.
For that reason, a code-signing certificate should not be treated like a standard user certificate.
Key recommendations include:
· Restrict access to the private key
· Avoid unnecessary certificate exports
· Never store private keys in scripts or repositories
· Centralize signing processes whenever possible
· Audit certificate usage
· Monitor certificate expiration and renewal dates
In highly sensitive environments, additional protections such as Hardware Security Modules (HSMs) or dedicated signing infrastructure may be appropriate.
Why You Shouldn't Distribute the Certificate Together with the Script
A common mistake is distributing the certificate, including its private key, alongside the scripts.
This is not a good practice.
The system that executes the script typically does not need access to the private key. It only needs to be able to trust the signer and validate the signature.
The process should look like this:
Signing System
1 Private Key
2 │
3 ▼
4 Code-Signing Certificate
5 │
6 ▼
7 Sign PowerShell Script
Target System
1 Public Certificate / Certificate Chain
2 │
3 ▼
4 Verify Signature
5 │
6 ▼
7 Execute Script
The private key should always remain on the signing system.
Read on:
From Development to Production: How Staging Improves PowerShell and Git Workflows
Version Controlling Automation Scripts: Why Git Alone Is Not Enough
What Happens When a Certificate Is Renewed?
Code-signing certificates have a limited lifetime.
Organizations should therefore establish a renewal process before a certificate expires.
Issuing a new certificate alone is often not enough. Important questions include:
- Which scripts were signed with the previous certificate?
- Do existing scripts need to be re-signed?
- Is timestamping being used?
- Is the original certificate chain still available?
- Is the old certificate archived?
- When should the old certificate be revoked?
A well-managed certificate lifecycle helps prevent situations where large numbers of scripts suddenly stop functioning as expected years later.
What Happens If the Certificate Is Compromised?
One of the most critical scenarios is the compromise of a code-signing certificate's private key.
An attacker could potentially sign malicious scripts using what appears to be a trusted certificate.
In this situation, simply issuing a replacement certificate is not sufficient.
A formal incident response process should be triggered and may include:
- Investigating the affected certificate.
- Revoking the certificate if necessary.
- Verifying trust relationships on client systems.
- Identifying files signed with the compromised certificate.
- Issuing a new code-signing certificate.
- Updating trust relationships and signing procedures.
- Re-signing affected scripts.
For this reason, code signing is not just a PowerShell feature. It is an integral part of overall certificate and security management.
Common PowerShell Code-Signing Mistakes
1. Using a Self-Signed Certificate in Production
Self-signed certificates are convenient for testing and development.
In larger production environments, an internal PKI is usually a much better choice.
2. Distributing the Private Key
The private key should never be present on every client.
Clients only need the information required to validate the signature and establish trust.
3. Forgetting to Re-Sign After Changes
Even a minor modification can invalidate a signature.
A recommended process looks like this:
1 Development
2 ↓
3 Code Review
4 ↓
5 Testing
6 ↓
7 Build / Deployment
8 ↓
9 Signing
10 ↓
11 Distribution
Not:
1 Signing
2 ↓
3 Modify Script
4 ↓
5 Distribute
4. Not Using Timestamping
Without a timestamp, long-term management and validation of signed files can become more difficult.
Production environments should therefore evaluate whether a suitable timestamp service is available.
5. Relying Only on Execution Policy
Execution Policy is not a complete application control solution.
It should be viewed as one component of a broader security strategy rather than a standalone protection mechanism.
PowerShell Code Signing in a CI/CD Pipeline
In professional environments, scripts should ideally not be signed manually on an administrator's workstation.
Instead, signing should be integrated into a controlled pipeline:
1 Git Repository
2 │
3 ▼
4 Code Review
5 │
6 ▼
7 Automated Testing
8 │
9 ▼
10 Build
11 │
12 ▼
13 Security Checks
14 │
15 ▼
16 Code Signing
17 │
18 ▼
19 Artifact Repository
20 │
21 ▼
22 DeploymentParticular attention should be given to protecting access to the private key.
A well-designed signing process ensures that only approved artifacts are signed.
This provides traceability for:
- Which commit was signed
- When it was signed
- Which certificate was used
- Who or what process initiated the signing operation
- Which artifact was ultimately distributed
A Practical Example
Let's assume we have the following script:
1 # Backup.ps1
2
3 $Source = "C:\Data"
4 $Destination = "\\server\backup"
5
6 Copy-Item `
7 -Path $Source `
8 -Destination $Destination `
9 -RecurseWe have a suitable code-signing certificate:
1 $cert = Get-ChildItem Cert:\CurrentUser\My |
2 Where-Object {
3 $_.EnhancedKeyUsageList.ObjectId `
4 -contains "1.3.6.1.5.5.7.3.3"
5 } |
6 Select-Object -First 1Next, we sign the script:
1 Set-AuthenticodeSignature `
2 -FilePath "C:\Scripts\Backup.ps1" `
3 -Certificate $certFinally, we verify the result:
1 Get-AuthenticodeSignature "C:\Scripts\Backup.ps1"The output should indicate a valid signature.
At this point, the basic workflow is complete:
1 Select Certificate
2 ↓
3 Test Script
4 ↓
5 Sign Script
6 ↓
7 Verify Signature
8 ↓
9 Distribute ScriptEnterprise Best Practices
Organizations that want to implement PowerShell code signing professionally should follow a few fundamental principles.
1. Use a Dedicated Code-Signing Certificate
Code-signing certificates should not be mixed with standard user certificates.
2. Protect Private Keys
Private keys should reside on a controlled system and only be accessible to authorized signing processes.
3. Leverage an Internal PKI
If an organizational PKI already exists, evaluate whether it can issue and manage code-signing certificates.
4. Use Timestamping
Timestamps are essential for long-term signature validation.
5. Sign Only After Testing
A strong workflow looks like this:
Develop → Test → Review → Sign → Distribute
6. Do Not Modify Signed Files
Once a script has been signed, its contents should remain unchanged.
7. Monitor Certificates
Expiration, renewal, and revocation should be integrated into normal certificate lifecycle management.
8. Audit the Signing Process
Especially in larger environments, it should always be possible to determine when a script was signed and by whom or by which process.
Looking to go beyond code signing? Download our whitepaper Maximizing Governance and Compliance: Microsoft IT Automation with the Power of PowerShell to learn how to establish centralized governance, auditing, compliance, and secure automation practices across your PowerShell environment.
Conclusion
Signing PowerShell scripts is an important component of a controlled and auditable script execution strategy.
PowerShell provides built-in support through Set-AuthenticodeSignature, but a secure implementation involves much more than simply running a signing command. A robust solution combines several elements:
Certificate + protected private key + trust chain + signature validation + controlled deployment process
For testing environments, a self-signed certificate is often sufficient. In production environments, however, organizations should consider an internal PKI or a professionally managed code-signing process.
Most importantly, the private key associated with the code-signing certificate is the truly critical asset. If it is compromised, the trust placed in signed scripts can be abused.
Organizations that combine code signing with code reviews, automated testing, timestamping, a well-managed PKI, and a controlled deployment process gain a significantly more secure, transparent, and auditable PowerShell environment.


