Digitally Sign an Office Document in PowerShell

The PowerShell scripts below use the OfficeSignature class of the Signature Library to digitally sign Microsoft Word, Excel and PowerPoint documents (DOCX, XLSX, PPTX) with the native digital signatures of Microsoft Office: a signature with a time stamp and a visible signature line, the verification of all the signatures of a document, and the bulk signing of all the Office documents of a folder.

1. Digitally sign an Office document (officeSign.ps1)

#How to run the PowerShell scripts

#SignLib.dll must be placed on a folder and the path must be added on the .ps1 script
#$DllPath = 'd:\SignLib.dll'
#[System.Reflection.Assembly]::LoadFrom($DllPath)

#run the script file from command line, as below:
#powershell -executionPolicy bypass -file officeSign.ps1 "d:\test.docx" "d:\test[signed].docx"

#Digitally sign an Office document (docx, xlsx, pptx): XAdES-T signature (with a time stamp), signer details and a visible signature line
if ($args.Length -lt 2)
{
    echo "Usage: officeSign.ps1 <unsigned file> <signed file>"
}
else
{
    $DllPath = 'd:\SignLib.dll'
    [System.Reflection.Assembly]::LoadFrom($DllPath) | Out-Null

    #the full paths of the files (.NET does not know the current folder of PowerShell)
    $inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($args[0])
    $outputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($args[1])

    $pfxFilePath = "d:\pfxcertificate.pfx"
    $pFXFilePassword = "123456"

    $sign = new-object -typeName SignLib.OfficeSignature("")

    #Load the signature certificate from a PFX or P12 file
    $sign.DigitalSignatureCertificate = [SignLib.Certificates.DigitalCertificate]::LoadCertificate($pfxFilePath, $pFXFilePassword)

    #Load the certificate from Microsoft Certificate Store (smart card or USB token certificates).
    #$sign.DigitalSignatureCertificate = [SignLib.Certificates.DigitalCertificate]::LoadCertificate($false, "", "Select Certificate", "Select the certificate for digital signature")

    #the hash algorithm (SHA256, SHA384 or SHA512) and the signature level: XadesB (default), XadesT (time stamp) or XadesLT (long term)
    $sign.HashAlgorithm = [SignLib.HashAlgorithm]::SHA256
    $sign.SignatureStandard = [SignLib.Xml.XadesSignatureStandard]::XadesT
    $sign.TimeStamping.ServerUrl = "https://ca.signfiles.com/TSAServer.aspx"

    #the signer details, shown by Office in the details of the signature
    $sign.CommitmentType = [SignLib.Xml.XadesCommitmentType]::ProofOfApproval
    $sign.SignatureComments = "Approval of the document"
    $sign.SignerRole = "Manager"

    #a visible signature: a signature line at the end of a Word document (remove these lines for an invisible signature)
    #the Excel and PowerPoint documents get an invisible signature
    $sign.SignatureLine = new-object -typeName SignLib.Office.OfficeSignatureLine
    $sign.SignatureLine.SuggestedSigner = "John Smith"
    $sign.SignatureLine.SuggestedSignerTitle = "Manager"
    $sign.SignatureLine.ShowSignDate = $true
    #the handwritten signature (optional)
    #$sign.SignatureLine.SignatureImage = [System.IO.File]::ReadAllBytes("d:\signature.png")

    echo "Perform the digital signature..."
    $sign.ApplyDigitalSignature($inputFile, $outputFile)

    echo ("Number of signatures: " + $sign.GetNumberOfSignatures($outputFile))
    echo ("All signatures are valid: " + $sign.VerifyDigitalSignature($outputFile))
}

The result:

powershell -executionPolicy bypass -file officeSign.ps1 "d:\test.docx" "d:\test[signed].docx"

Perform the digital signature...
Number of signatures: 1
All signatures are valid: True

2. Verify the signatures of an Office document (officeVerify.ps1)

#Verify all the signatures of an Office document (docx, xlsx, pptx) and show their details
if ($args.Length -lt 1)
{
    echo "Usage: officeVerify.ps1 <signed file>"
}
else
{
    $DllPath = 'd:\SignLib.dll'
    [System.Reflection.Assembly]::LoadFrom($DllPath) | Out-Null

    $signedFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($args[0])
    $verifier = new-object -typeName SignLib.OfficeSignature("")

    foreach ($info in $verifier.GetSignatures($signedFile))
    {
        #Status: Success, ContentModified, ReferenceNotFound or InvalidSignature
        echo ("Signature " + ($info.Index + 1) + ": " + $info.Status)
        echo ("  Signer: " + $info.SignerName + " (" + $info.Certificate.Issuer + ")")
        echo ("  Signing time: " + $info.SigningTime.ToLocalTime())
        if ($info.HasTimestamp) { echo ("  Time stamp: " + $info.TimestampTime.ToLocalTime()) } else { echo "  Time stamp: no" }
        echo ("  Visible signature: " + $info.IsVisible + ", algorithm: " + $info.SignatureAlgorithm)
    }

    #the signature lines of a Word document and whether they are signed
    foreach ($line in $verifier.GetSignatureLines($signedFile))
    {
        echo ("Signature line: " + $line)
    }
}

The result, for a contract signed by two people (Status is ContentModified if the document was changed after signing):

powershell -executionPolicy bypass -file officeVerify.ps1 "d:\contract[signed].docx"

Signature 1: Success
  Signer: John Smith (CN=John Smith, O=Demo Company, C=US)
  Signing time: 10/02/2026 00:11:51
  Time stamp: 10/02/2026 00:11:53
  Visible signature: True, algorithm: RSA-SHA256
Signature 2: Success
  Signer: Mary Jones (CN=Mary Jones, O=Demo Company, C=US)
  Signing time: 10/02/2026 00:12:01
  Time stamp: 10/02/2026 00:12:03
  Visible signature: True, algorithm: RSA-SHA256
Signature line: John Smith, Manager - signed
Signature line: Mary Jones, Accountant - signed

3. Digitally sign all the Office documents of a folder (officeSignFolder.ps1)

#Digitally sign all the Office documents (docx, xlsx, pptx) of a folder (bulk signing, invisible XAdES-T signatures)
if ($args.Length -lt 2)
{
    echo "Usage: officeSignFolder.ps1 <input folder> <output folder>"
}
else
{
    $DllPath = 'd:\SignLib.dll'
    [System.Reflection.Assembly]::LoadFrom($DllPath) | Out-Null

    $inputFolder = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($args[0])
    $outputFolder = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($args[1])
    New-Item -ItemType Directory -Force -Path $outputFolder | Out-Null

    $sign = new-object -typeName SignLib.OfficeSignature("")
    $sign.DigitalSignatureCertificate = [SignLib.Certificates.DigitalCertificate]::LoadCertificate("d:\pfxcertificate.pfx", "123456")
    $sign.SignatureStandard = [SignLib.Xml.XadesSignatureStandard]::XadesT
    $sign.TimeStamping.ServerUrl = "https://ca.signfiles.com/TSAServer.aspx"

    foreach ($file in Get-ChildItem -Path $inputFolder -File | Where-Object { $_.Extension -in ".docx", ".xlsx", ".pptx" })
    {
        $signedFile = Join-Path $outputFolder $file.Name
        try
        {
            $sign.ApplyDigitalSignature($file.FullName, $signedFile)
            echo ($file.Name + " - signed")
        }
        catch
        {
            #for example: a password protected document or a file opened in Office
            echo ($file.Name + " - error: " + $_.Exception.GetBaseException().Message)
        }
    }
}

The result:

powershell -executionPolicy bypass -file officeSignFolder.ps1 "d:\documents" "d:\signed"

broken.docx - error: File contains corrupted data.
budget.xlsx - signed
contract.docx - signed
presentation.pptx - signed

Notes:
– Windows PowerShell 5.1 uses the .NET Framework version of SignLib.dll; PowerShell 7 uses the .NET 8 / .NET 9 version.
– Replace “” in OfficeSignature(“”) with your serial number. Without it the library works in demonstration mode and waits 10 seconds before each signature or verification.
– Visible signatures (signature lines) are available for Word documents; Excel and PowerPoint documents receive an invisible signature.
– To let several people sign the same Word document, add all the signature lines before the first signature, then each signer signs his own line (see the C# example).

See also: