Code signing is a fundamental security practice that validates the authenticity and integrity of executable code. In enterprise environments managed by Microsoft Intune, code signing becomes essential for ensuring that only trusted, verified scripts run on your managed devices.

Whether you are deploying PowerShell scripts, remediation scripts, or Win32 applications, proper code signing practices protect your organization against unauthorized or malicious code execution.

What is code signing?

Code signing is the process of digitally signing executables, scripts, and other code using a cryptographic certificate. This digital signature serves two critical purposes: it verifies the identity of the code publisher and ensures the code has not been modified since it was signed.

In the context of Microsoft Intune, the Enforce script signature check setting determines whether the Microsoft Intune Management Extension (IME) validates the digital signature of a script before execution. When enabled, only scripts signed by a trusted publisher will run on target devices.

Code signing applies to various types of code in enterprise environments:

  • PowerShell scripts (.ps1, .psm1, .psd1)
  • Remediation scripts deployed via Microsoft Intune
  • Win32 applications and installers (.exe, .msi)
  • Scripts used in application deployment (install/uninstall scripts)

Why code signing matters

Code signing is not just a nice-to-have security feature - it is a critical control for any organization serious about protecting their environment. Here is why every script and executable used in production should be signed.

Security benefits

  • Code Integrity: Ensures code has not been modified since it was signed by verifying the cryptographic hash. Even a single character change invalidates the signature.
  • Publisher Verification: Confirms the identity of the code author through certificate chain validation, establishing a clear chain of trust.
  • Tamper Detection: Any modification to signed code invalidates the signature, preventing execution of altered or compromised code.
  • Non-Repudiation: Signed code creates an audit trail linking the code to a specific publisher, supporting incident investigation and accountability.

Compliance and governance

  • Regulatory Requirements: Many security frameworks (NIST, ISO 27001, CIS Controls) require or recommend code signing for administrative scripts and executables.
  • Zero Trust Architecture: Code signing aligns with Zero Trust principles by verifying code authenticity before execution.
  • Application Control Integration: Signed code works seamlessly with Application Control for Business (formerly Windows Defender Application Control, commonly known as WDAC) and AppLocker policies.

Operational benefits

  • Tamper Protection for Privileged Scripts: Scripts deployed through Microsoft Intune often run in SYSTEM context with elevated privileges. Code signing ensures that if a cached script is modified locally, it will fail signature validation and not execute - protecting against local tampering attacks.
  • Change Management: Signing processes integrate with change management workflows, ensuring proper review before deployment.
  • Troubleshooting: Signature validation helps detect whether a script has been modified since it was signed. A HashMismatch status immediately indicates tampering or unauthorized changes.
Compliance, Governance & Operational Benefits

Understanding PowerShell execution policies

PowerShell execution policies control which scripts can run on a device and interact directly with script signing. Understanding how they work provides essential context before configuring Microsoft Intune signature enforcement.

Execution PolicyDescription
RestrictedNo scripts are permitted to run (default on Windows clients).
AllSignedOnly scripts signed by a trusted publisher can run.
RemoteSignedDownloaded scripts must be signed; locally created scripts can run unsigned.
UnrestrictedAll scripts can run, but prompts for downloaded scripts.
BypassNothing is blocked and there are no warnings or prompts.
PowerShell Execution Policies

For a complete reference on execution policies, see about execution policies  on Microsoft Learn.

Microsoft Intune signature enforcement vs. execution policy

Microsoft Intune signature enforcement works independently from the device execution policy. When you deploy scripts through Microsoft Intune, the Intune Management Extension (IME)  handles script execution through its AgentExecutor component.

The Enforce script signature check setting controls which PowerShell execution policy parameter the IME uses when invoking scripts:

  • When disabled (unchecked): The IME invokes PowerShell with -ExecutionPolicy Bypass, allowing unsigned scripts to run regardless of the device’s configured execution policy.
  • When enabled (checked): The IME invokes PowerShell with -ExecutionPolicy AllSigned, requiring scripts to have a valid signature from a trusted publisher.

The key point: the device’s configured execution policy does not apply to scripts deployed through Microsoft Intune. The IME explicitly sets the execution policy for each script invocation, overriding whatever is configured on the device.

  • When signature check is disabled: The IME uses -ExecutionPolicy Bypass. Even if the device has an AllSigned execution policy configured, unsigned scripts will still run.
  • When signature check is enabled: The IME uses -ExecutionPolicy AllSigned. PowerShell validates the script’s digital signature before execution. The signing certificate (or its issuing CA) must be present in the device’s Trusted Publishers and Trusted Root Certification Authorities stores for the signature to be considered valid.

Types of code signing certificates

Code signing certificates come in several varieties, each suited for different use cases. The right choice depends on factors like your existing infrastructure, budget, compliance requirements, and whether scripts are used internally or distributed externally. Understanding these options - from commercially issued certificates to internal PKI and self-signed alternatives - helps you select the approach that balances security, cost, and operational complexity for your organization.

Certificates issued by public Certificate Authorities (CAs) such as DigiCert, Sectigo, or GlobalSign chain to root CAs that are already included in the Windows Trusted Root Certification Authorities store through the Microsoft Trusted Root Program. This means the certificate chain validates without deploying root certificates.

However, for non-interactive script execution (such as scripts deployed via Microsoft Intune), the code signing certificate itself must still be deployed to the Trusted Publishers store on target devices. Without this, PowerShell cannot confirm the publisher is trusted without prompting the user - and non-interactive execution has no opportunity to respond to such prompts.

Benefits:

  • Root CA is already trusted - no need to deploy root certificates.
  • Simplified chain validation compared to internal PKI.
  • Meets compliance requirements for most regulatory frameworks.
  • Identity verification by the CA provides additional assurance.

Considerations:

  • Annual cost for certificate renewal.
  • Requires identity verification by the CA.
  • Code signing certificate must still be deployed to the Trusted Publishers store on managed devices for non-interactive execution.

Internal PKI certificates

Organizations with an internal Public Key Infrastructure (PKI) can issue code signing certificates from their enterprise CA. This approach is cost-effective for organizations that already have PKI infrastructure.

Benefits:

  • No recurring certificate costs.
  • Full control over certificate issuance and revocation.
  • Ideal for internal scripts that do not leave the organization.

Considerations:

  • Requires PKI infrastructure.
  • Root CA certificate must be deployed to all devices.

Requesting a code signing certificate

  1. Open certmgr.msc or search for user certificates in the Start menu and select Manage User Certificates.
  2. Navigate to Personal > Certificates.
  3. Right-click and select All Tasks > Request New Certificate.
  4. Follow the Certificate Enrollment wizard.
  5. Select a Code Signing certificate template.
  6. Complete the enrollment process.

Requesting a certificate using PowerShell

If you prefer automation or need to script certificate enrollment, use the Get-Certificate cmdlet to request a code signing certificate from your enterprise CA:

# Request a code signing certificate from AD CS
# Requires appropriate permissions and a published Code Signing template
$template = "CodeSigning"  # Name of your code signing template
$cert = Get-Certificate -Template $template -CertStoreLocation Cert:\CurrentUser\My

Write-Output "Certificate enrolled with thumbprint: $($cert.Certificate.Thumbprint)"

Request Code Signing Certificate from AD CS

Self-signed certificates (testing only)

Self-signed certificates can be created for testing and development purposes but should never be used in production environments.

Benefits:

  • Free and quick to create.
  • Useful for development and testing scenarios.

Considerations:

  • Must be deployed to both Trusted Root and Trusted Publishers stores on each device.
  • No established chain of trust - you are the root CA.
  • Not suitable for production deployments.

Creating a self-signed certificate

For testing purposes, you can create a self-signed code signing certificate using the New-SelfSignedCertificate cmdlet:

# Create a self-signed code signing certificate
# No elevation required - certificate is created in current user store
$params = @{
    Subject           = 'CN=LAB-CodeSign-SelfSigned'
    FriendlyName      = 'Code Signing Test Certificate'
    Type              = 'CodeSigning'
    CertStoreLocation = 'Cert:\CurrentUser\My'
    HashAlgorithm     = 'SHA256'
    NotAfter          = (Get-Date).AddDays(90)  # Short validity for test certificates
    KeyUsage          = 'DigitalSignature'
    KeyAlgorithm      = 'RSA'
    KeyLength         = 2048
}
$cert = New-SelfSignedCertificate @params

# Display the certificate thumbprint
Write-Output "Certificate created with thumbprint: $($cert.Thumbprint)"

Create Self-Signed Code Signing Certificate

The certificate is stored in your Personal certificate store (Cert:\CurrentUser\My). To view it, open the Start menu and search for user certificates, then open Manage user certificates (or run certmgr.msc). Navigate to Personal > Certificates - you will see the certificate listed with the friendly name Code Signing Test Certificate.

The certificate is stored in your Personal certificate store

To use this certificate for signing, you must first export the public key (without the private key) from your signing machine:

# Export the certificate (without private key)
# No elevation required - certificate is in current user store
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
    Where-Object { $_.Subject -like '*LAB-CodeSign-SelfSigned*' } | Select-Object -First 1

Export-Certificate -Cert $cert -FilePath "$env:USERPROFILE\Documents\LAB-CodeSign-SelfSigned.cer"

Export Self-Signed Certificate

Then, on each target device where signed scripts will run, import the exported certificate into both the Trusted Root Certification Authorities and Trusted Publishers stores:

# Import to Trusted Root and Trusted Publishers
# Elevation required - run as Administrator
$certFile = "$env:USERPROFILE\Documents\LAB-CodeSign-SelfSigned.cer"

Import-Certificate -FilePath $certFile -CertStoreLocation Cert:\LocalMachine\Root
Import-Certificate -FilePath $certFile -CertStoreLocation Cert:\LocalMachine\TrustedPublisher

Import Certificate to Trusted Stores

Optionally, set a friendly name on the imported certificates to make them easier to identify in the certificate manager:

# Set friendly name on imported certificates
# Elevation required - run as Administrator
$friendlyName = "Code Signing Test Certificate"
$subject = "CN=LAB-CodeSign-SelfSigned"

Get-ChildItem Cert:\LocalMachine\Root |
    Where-Object { $_.Subject -eq $subject } |
    ForEach-Object { $_.FriendlyName = $friendlyName }

Get-ChildItem Cert:\LocalMachine\TrustedPublisher |
    Where-Object { $_.Subject -eq $subject } |
    ForEach-Object { $_.FriendlyName = $friendlyName }

Set Friendly Name on Imported Certificates

To verify the certificate was imported successfully, open the Start menu and search for computer certificates, then open Manage computer certificates (or run certlm.msc). Navigate to Trusted Root Certification Authorities > Certificates and Trusted Publishers > Certificates to confirm the certificate appears in both locations.

Verify the certificate was imported successfully in Trusted Root Certification Authorities
Verify the certificate was imported successfully in Trusted Publishers

Removing the test certificates

When you are done testing, remove the self-signed certificate from all stores to clean up your environment:

# Remove the test certificate from all stores
# Elevation required - run as Administrator
$certName = 'LAB-CodeSign-SelfSigned'

# Remove from Personal store (current user)
Get-ChildItem Cert:\CurrentUser\My |
    Where-Object { $_.Subject -like "*$certName*" } |
    Remove-Item

# Remove from Trusted Root and Trusted Publishers (local machine)
Get-ChildItem Cert:\LocalMachine\Root |
    Where-Object { $_.Subject -like "*$certName*" } |
    Remove-Item
Get-ChildItem Cert:\LocalMachine\TrustedPublisher |
    Where-Object { $_.Subject -like "*$certName*" } |
    Remove-Item

Remove Self-Signed Certificate from All Stores

Alternatively, you can remove certificates manually. Open the Start menu and search for user certificates, then open Manage user certificates (or run certmgr.msc) for current user certificates. For local machine certificates, search for computer certificates, then open Manage computer certificates (or run certlm.msc). Navigate to each store and delete the certificate.

Signing PowerShell scripts

Once you have a code signing certificate, you can sign your PowerShell scripts using the Set-AuthenticodeSignature cmdlet. Always include a timestamp server to ensure your signed scripts remain valid even after the signing certificate expires:

# Get the code signing certificate
# No elevation required - certificate is in current user store
$certName = "LAB-CodeSign-SelfSigned"
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
    Where-Object { $_.Subject -like "*$certName*" } | Select-Object -First 1

# Sign a PowerShell script with timestamp
$scriptPath = ".\MyIntuneScript.ps1"
$timestampServer = "http://timestamp.digicert.com"

Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $cert -TimestampServer $timestampServer

# Verify the signature
Get-AuthenticodeSignature -FilePath $scriptPath

Sign PowerShell Script

What a signed script looks like

After signing, a signature block is appended to the end of your script:

# Your script content here
Write-Output "Hello from a signed script!"

# SIG # Begin signature block
# MIIb6QYJKoZIhvcNAQcCoIIb2jCCG9YCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# ... (signature data) ...
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU4Zgfq829xbUBygNK4PS1KBIn
# QQegghZSMIIDFDCCAfygAwIBAgIQb5H0HoPF/I9LNAQ6ZcdRUDANBgkqhkiG9w0B
# SIG # End signature block

Example of a Signed PowerShell Script

Deploying certificates to devices using Microsoft Intune

For devices to trust your signed scripts, the signing certificate (or its issuing CA) must be present in the appropriate certificate stores. Microsoft Intune provides several methods for certificate deployment.

Deploying a root CA certificate using a trusted certificate profile

The built-in Trusted certificate profile template supports deploying certificates to the Root and Intermediate certificate stores. Use this method for root CA certificates (required for internal PKI and self-signed certificates):

  1. Sign in to the Microsoft Intune admin center .
  2. Navigate to Devices > Windows > Configuration.
  3. Select Create > New policy.
  4. Select Windows 10 and later as the platform.
  5. Choose Templates > Trusted certificate.
  6. Select Create.
  7. Enter a name (e.g., Code Signing - Root CA).
  8. Upload the root CA certificate file (.cer format).
  9. Select the Destination store: Computer certificate store - Root.
  10. Assign the profile to your device groups.

Deploying a code signing certificate to Trusted Publishers

Because the built-in Trusted certificate profile does not support the Trusted Publishers store, you need to create a custom configuration profile using an OMA-URI setting. This approach uses the RootCATrustedCertificates CSP  to deploy the certificate.

Preparing the certificate

  1. Export the certificate in Base-64 encoded X.509 (.CER) format. If you need to extract it from a signed file:

    • Right-click on the signed file and choose Properties.
    • Go to the Digital Signatures tab, select the signature, and click Details.
    • Click View Certificate, then go to the Details tab and select Copy to File.
    • Complete the Certificate Export Wizard, choosing Base-64 encoded X.509 (.CER) format.
  2. Retrieve the certificate thumbprint: Open the exported certificate file, go to the Details tab, and copy the Thumbprint value (remove any spaces).

  3. Get the Base-64 encoded value: Open the .cer file in a text editor and copy the content between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- (excluding those lines). Remove any line breaks so the value is a single continuous string.

Creating the custom profile

  1. Sign in to the Microsoft Intune admin center .
  2. Navigate to Devices > Windows > Configuration.
  3. Select Create > New policy.
  4. Select Windows 10 and later as the platform.
  5. Choose Templates > Custom.
  6. Select Create.
  7. Enter a name (e.g., Code Signing - Trusted Publisher).
  8. Add an OMA-URI setting with the following values:
    • Name: A descriptive name for the setting.
    • OMA-URI: ./Device/Vendor/MSFT/RootCATrustedCertificates/TrustedPublisher/<thumbprint>/EncodedCertificate (replace <thumbprint> with the actual certificate thumbprint, no spaces).
    • Data type: String.
    • Value: The Base-64 encoded certificate content (single line, no line breaks).
  9. Assign the profile to your device groups.

For more details, see Adding a Certificate to Trusted Publishers using Microsoft Intune .

Certificate store locations

A trusted publisher is a code signing identity that you have explicitly authorized to run code on your systems. By adding a code signing certificate to the Trusted Publishers store, you are declaring: “I have reviewed this publisher and I trust code signed by this certificate to execute without further confirmation.” This is essentially an allowlist of approved code signers for your environment.

When PowerShell encounters a signed script, it performs two distinct validations:

  1. Chain validation: Is the certificate chain valid? This checks whether the signing certificate chains to a trusted root CA in the Trusted Root Certification Authorities store.
  2. Publisher trust: Is this publisher authorized to execute code? This checks whether the signing certificate exists in the Trusted Publishers store.

Chain validation alone is not sufficient - a valid certificate chain only proves the certificate is legitimate, not that you trust that particular publisher to run code on your devices. Many certificates chain to trusted roots (any commercial CA customer, for example), but that does not mean you want their code running in your environment.

For interactive execution, if a script is signed by an unknown publisher (valid chain but not in Trusted Publishers), PowerShell prompts the user: “Do you want to run software from this untrusted publisher?” The user can choose to trust the publisher, and PowerShell remembers this choice by adding the certificate to their personal Trusted Publishers store.

However, scripts deployed through Microsoft Intune run non-interactively - typically in SYSTEM context with no user session to display prompts. If the signing certificate is not already in the Trusted Publishers store, there is no opportunity for user confirmation, and the script fails silently. This is why deploying the code signing certificate to Trusted Publishers is required for all certificate types - even commercial certificates whose root CA is already trusted through the Microsoft Trusted Root Program.

The following table summarizes which certificates should be deployed to each store:

Certificate TypeTrusted RootTrusted PublishersNotes
CommercialNot requiredRequiredRoot CA is already trusted via Microsoft Trusted Root Program.
Internal PKIRequired
(root CA)
Required
(signing cert)
Deploy root CA separately from signing certificate.
Self-SignedRequiredRequiredSame certificate goes in both stores.
Certificate Store Requirements by Type
StorePurpose
Trusted Root Certification AuthoritiesInstall root CA certificates here for chain validation.
Trusted PublishersInstall code signing certificates here to trust specific publishers.
Intermediate Certification AuthoritiesInstall intermediate CA certificates if needed for chain building.
Certificate Store Locations

Configuring script signature enforcement in Microsoft Intune

When creating a PowerShell script policy in Microsoft Intune, you configure the signature enforcement setting:

  1. Sign in to the Microsoft Intune admin center .
  2. Navigate to Devices > Scripts and remediations > Platform scripts.
  3. Select Add > Windows 10 and later.
  4. In the Basics tab, enter a name and description.
  5. In the Script settings tab, configure the following:
    • Script location: Upload your signed PowerShell script.
    • Run this script using the logged on credentials: Choose based on your requirements.
    • Enforce script signature check: Set to Yes to require signed scripts.
    • Run script in 64-bit PowerShell host: Configure as needed.
  6. Assign scope tags and device groups.
  7. Review and create the policy.

Testing signed scripts locally

Before deploying signed scripts through Microsoft Intune, you should test them locally to verify the signature is valid and trusted.

You do not need to change the device’s execution policy to test. Instead, use the -ExecutionPolicy parameter when invoking PowerShell:

powershell.exe -ExecutionPolicy "AllSigned" -File ".\MyScript.ps1" -Verbose

This tests signature validation without modifying device settings.

Interpreting test results

If the script is unsigned, you will see:

File C:\Scripts\MyScript.ps1 cannot be loaded. The file C:\Scripts\MyScript.ps1 is not
digitally signed. You cannot run this script on the current system. For more information...

Unsigned Script error

If the script is signed but the certificate is not trusted:

File C:\Scripts\MyScript.ps1 cannot be loaded. A certificate chain processed, but terminated
in a root certificate which is not trusted by the trust provider...

Untrusted Certificate error

If the script was signed but has been modified since signing:

File C:\Scripts\MyScript.ps1 cannot be loaded. The file C:\Scripts\MyScript.ps1 is not
digitally signed. You cannot run this script on the current system. For more information...

Modified Script error

These errors confirm the AllSigned policy is being enforced correctly:

ErrorCauseResolution
Unsigned ScriptThe script was never signed.Sign it using Set-AuthenticodeSignature.
Untrusted CertificateThe certificate chain is not trusted. Either the root CA certificate is missing from Trusted Root Certification Authorities, or the signing certificate is missing from Trusted Publishers.Deploy both certificates as needed.
Hash Mismatch / Unknown ErrorThe script was modified after signing.Review, and if applicable, re-sign the script with your code signing certificate.
Script Signature Validation Errors

Enforcing restricted execution policy via Microsoft Intune

The default PowerShell execution policy on Windows clients is Restricted, which helps prevent accidental script execution. This is the recommended setting for most devices - it protects against unintentional script execution while not impacting scripts deployed through Microsoft Intune (which override the device policy).

If your organization wants to ensure devices maintain the Restricted policy and prevent users or software from changing it, you can enforce this via the Settings Catalog:

  1. Navigate to Devices > Configuration profiles > Create profile.
  2. Select Windows 10 and later and Settings catalog.
  3. Search for PowerShell and add Turn on Script Execution.
  4. Set to Disabled to enforce Restricted (no scripts can run).
  5. Assign to your device groups.

Overcoming unsigned code challenges

Now that you understand how to implement code signing, the next step is addressing a common reality: dealing with unsigned code in production environments. This problem often stems from a disconnect between development practices and security requirements.

Why code signing gets skipped

Code signing gets skipped for several common reasons. In my experience working with IT teams and developers, I have observed these patterns:

  • Development Convenience: Signing every iteration during development adds friction.
  • Lack of Awareness: Many IT professionals are not aware of code signing’s importance or implementation.
  • Certificate Access: Obtaining and managing certificates requires planning that may not be prioritized.
  • Time Pressure: Under deadline pressure, signing gets skipped with intentions to “do it later.”

The impact on security controls

When unsigned code reaches production, organizations face difficult choices:

  • Disabling Security Controls: Administrators may disable signature enforcement or relax execution policies, creating security gaps.
  • Application Control Conflicts: WDAC and AppLocker block unsigned scripts by default, leading to weakened policies or operational failures.
  • Audit Failures: Security assessments often flag unsigned administrative scripts as findings requiring remediation.

Building a signing culture

To address these challenges:

  • Make signing easy: Provide developers with access to certificates and simple signing tools.
  • Integrate signing into CI/CD: Automate signing as part of build and deployment pipelines.
  • Establish clear policies: Document when and how code must be signed before production deployment.
  • Lead by example: IT and security teams should sign all scripts they deploy.

Auditing your existing scripts

Before enabling signature enforcement, audit your script inventory to identify what needs to be signed. The following PowerShell example scans a directory for PowerShell files and reports their signature status:

# Audit-ScriptSignatures.ps1
# Find all PowerShell scripts and check their signature status
# No elevation required
[string]$scriptDirectory = "C:\Scripts"  # Change to your script directory
Get-ChildItem -Path $scriptDirectory -Recurse -Include *.ps1, *.psm1, *.psd1 |
    ForEach-Object {
        $sig = Get-AuthenticodeSignature -FilePath $_.FullName
        [PSCustomObject]@{
            File          = $_.Name
            Status        = $sig.Status
            SignerName    = $sig.SignerCertificate.Subject
            CertValidFrom = $sig.SignerCertificate.NotBefore
            CertExpires   = $sig.SignerCertificate.NotAfter
            Timestamped   = if ($sig.SignerCertificate) { [bool]$sig.TimeStamperCertificate } else { 'N/A' }
        }
    } | Format-Table -AutoSize

Audit PowerShell Script Signatures

The output shows each script’s signature status at a glance:

File                      Status       SignerName                              CertValidFrom        CertExpires          Timestamped
----                      ------       ----------                              -------------        -----------          -----------
Install-Application.ps1   Valid        CN=Contoso Code Signing, O=Contoso Ltd  01/15/2025 00:00:00  01/15/2027 23:59:59  True
Get-DeviceInfo.ps1        NotSigned
Set-Configuration.ps1     HashMismatch CN=Contoso Code Signing, O=Contoso Ltd  01/15/2025 00:00:00  01/15/2027 23:59:59  True
Update-Registry.ps1       NotTrusted   CN=Unknown Publisher                    03/01/2025 00:00:00  03/01/2026 23:59:59  False

Sample Audit Output

This outputs the signature status for each file:

  • Valid: Properly signed and trusted.
  • NotSigned: No signature.
  • NotTrusted: Signed, but certificate not trusted.
  • HashMismatch: Script content modified after signing (signature block intact).
  • UnknownError: Signature block corrupted, certificate chain issues, or encoding problems. Often occurs when a signed script is edited and the signature block becomes malformed.

Use this to prioritize which scripts need signing before enabling Enforce script signature check in Microsoft Intune. This approach also works for other Authenticode-signed file types such as .exe, .msi, .dll, and .cab files - simply adjust the -Include parameter to target the file types you want to audit.

Auditing scripts in Microsoft Intune

You can also audit your Microsoft Intune script policies to identify which ones have signature enforcement disabled. The following example uses Microsoft Graph PowerShell to query both platform scripts and remediation scripts:

# Audit-IntuneScriptEnforcement.ps1
# Query Microsoft Intune for script policies and their signature enforcement status
# No elevation required
Connect-MgGraph -Scopes "DeviceManagementConfiguration.Read.All", "DeviceManagementScripts.Read.All"

# Audit Platform Scripts (Device Management Scripts)
$platformScripts = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts"

Write-Output "`nPlatform Scripts:"
$platformScripts.value | ForEach-Object {
    [PSCustomObject]@{
        Name                  = $_.displayName
        EnforceSignatureCheck = $_.enforceSignatureCheck
        RunAsAccount          = $_.runAsAccount
    }
} | Format-Table -AutoSize

# Audit Remediation Scripts (Device Health Scripts / Proactive Remediations)
$remediationScripts = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts"

Write-Output "`nRemediation Scripts:"
$remediationScripts.value | ForEach-Object {
    [PSCustomObject]@{
        Name                  = $_.displayName
        EnforceSignatureCheck = $_.enforceSignatureCheck
        RunAsAccount          = $_.runAsAccount
    }
} | Format-Table -AutoSize

# Summary: Scripts without signature enforcement
$unsignedPlatform = ($platformScripts.value | Where-Object { $_.enforceSignatureCheck -eq $false }).Count
$unsignedRemediation = ($remediationScripts.value | Where-Object { $_.enforceSignatureCheck -eq $false }).Count

Write-Output "`nSummary:"
Write-Output "Platform scripts without signature enforcement: $unsignedPlatform"
Write-Output "Remediation scripts without signature enforcement: $unsignedRemediation"

Audit script for Microsoft Intune script signature enforcement

This script outputs a list of all platform and remediation scripts along with their signature enforcement status. For policies where Enforce script signature check is disabled, you should validate whether the underlying script is signed - if it is, you can enable enforcement; if not, sign it first.

Common pitfalls and troubleshooting

Based on my experience helping organizations implement code signing, these are the most common issues you will encounter - and how to resolve them.

Certificate trust issues

Symptom: Scripts fail with signature validation errors even though they are signed.

Resolution: Verify that certificates are deployed as described in Deploying Certificates to Devices Using Microsoft Intune. Specifically:

  • Signing certificate must be in the Trusted Publishers store.
  • Root CA certificate must be in the Trusted Root Certification Authorities store (only required for internal PKI certificates - commercial CA roots are already trusted via the Microsoft Trusted Root Program).
  • Intermediate certificates must be available for chain building.

Expired certificates

Symptom: Previously working scripts start failing after a certificate expires.

Resolution:

  • Always use a timestamp server when signing scripts.
  • Establish a certificate renewal process before expiration.
  • Re-sign scripts with the new certificate and redeploy if no timestamp was used.

Script modifications

Symptom: Script fails after minor edits were made.

Resolution:

  • Any modification to a signed script invalidates the signature.
  • Re-sign the script after making changes.
  • Implement a signing step in your script development workflow.

Verifying script signatures

Use the following command to check if a script is properly signed:

$sig = Get-AuthenticodeSignature -FilePath "C:\Scripts\MyScript.ps1"
$sig | Format-List *

Check the Status property:

  • Valid: Script is properly signed and trusted.
  • NotSigned: Script has no signature.
  • HashMismatch: Script content was modified after signing.
  • UnknownError: Signature block corrupted, certificate chain issues, or the script was modified in a way that broke the signature format. This is common when editing signed scripts changes the file encoding or line endings.

Microsoft Intune Management Extension (IME) log files

If scripts fail to execute, review the Microsoft Intune Management Extension logs:

C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\AgentExecutor.log

This log file tracks PowerShell script executions and will contain details about signature validation failures.

The log also shows the exact command line used to invoke your scripts, confirming which execution policy the IME applied:

cmd line for running powershell is -NoProfile -executionPolicy bypass -file "C:\WINDOWS\IMECache\HealthScripts\...\detect.ps1"...

Signature Enforcement Disabled

cmd line for running powershell is -NoProfile -executionPolicy allSigned -file "C:\WINDOWS\IMECache\HealthScripts\...\detect.ps1"...

Signature Enforcement Enabled

This is useful for verifying whether Enforce script signature check is enabled (-executionPolicy allSigned) or disabled (-executionPolicy bypass) for a specific script execution.

Best practices

Certificate management

  • Use a dedicated code signing certificate for Microsoft Intune scripts, separate from other signing purposes.
  • Protect your private key with strong passwords and secure storage.
  • Monitor certificate expiration and renew before expiry.
  • Maintain a certificate inventory documenting which certificates are used for which purpose.

Script development workflow

  • Sign scripts as the final step in your development process.
  • Version control your scripts before signing.
  • Test signed scripts in a pilot group before broad deployment.
  • Document your signing process for team consistency.
  • Leave shared scripts unsigned when publishing to GitHub or other repositories. Your signature identifies you as the publisher - users should review the code and sign with their own certificate before deploying to their environment.

Security considerations

  • Never disable signature enforcement in production to work around signing issues.
  • Audit script deployments regularly to ensure only authorized scripts are deployed.
  • Use separate certificates for development/testing and production.
  • Revoke compromised certificates immediately and re-sign affected scripts.

Final thoughts

Code signing is a foundational security practice that every organization should embrace, especially when deploying scripts and applications through Microsoft Intune. While implementing a signing workflow requires initial investment in certificates, processes, and training, the security benefits are substantial and lasting.

The key to success is treating code signing not as an afterthought but as an integral part of your development and deployment workflow. By establishing signing practices early - before you implement strict application control policies - you avoid the operational disruption and security compromises that come from deploying unsigned code.

The key takeaways:

  • Sign everything in production, without exception.
  • Plan ahead for application control - code signing is a prerequisite for WDAC and AppLocker.
  • Reduce friction by providing teams with signing tools and automating through CI/CD pipelines.

Start with a pilot deployment to test your signing infrastructure, then gradually expand to your production environment. As you mature your practices, you will find that code signing becomes second nature - and your environment will be significantly more secure as a result.