Hello S-1-1-0! Today I would like to present a PowerShell script that allows you to add multiple files to a form and sign them using Authenticode signature.

As you already know, I’m using Software Restriction Policies and Applocker to prevent users (including myself) from running unauthorized applications and scripts. I’m using various options to allow certain applications and scripts in group policy. In certain cases I’m using hash rules, but mostly (as possible) I’m using digital signatures and publisher/certificate rules.

Since I’m writing a lot of PowerShell scripts I have to sign them in user-friendly way. Yes, there is Set-AuthenticodeSignature cmdlet, but I dislike to specify file path and signing certificate each time I want to sign something. Previously I used a nice function that adds a context menu to Windows Explorer and signs file: Подписывание скриптов PowerShell – практическая реализация (часть 2). However this script adds context menu only for PS1 files, while there are a lot of other file types that supports digital signatures. To address this question I wrote a little WinForms PowerShell script that allows me to perform file signing very easy. At first I start with screenshots.

image

Here is the default window when you run the script. Here we see grid view where you can drag and drop files, certificate selection combo box, re-sign checkbox, several labels and 2 buttons (they are inactive until you select the certificate. At first we add some files and look for available certificates:

image

As you see, when you add files, the code attempts to retrieve current signature status if available. Also I displayed an expanded combo box with available valid code signing certificates. Once we select one we are ready to sign:

image

We see validity information about selected certificate and both buttons are activated. View button displays selected signing certificate. Now, we sign all files:

image

All files are signed. Currently I used only PowerShell files, however you can put any file that supports authenticode signatures. The script supports the following files: .exe, .msi, .msp, .cmd, .bat, .vbs, .js, .ps1, .psm1, .psd1, .ps1xml, .dll, .ocx

if you think, that this tool can be useful for you, here is the code that you can use and improve:

########################################################
# SignUI.ps1
# Version 1.0
#
# This script allows you to add multiple files to UI form,
# select certificate and sign files.
#
# Vadims Podans (c) 2012
# http://en-us.sysadmins.lv/
########################################################

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Security
#region helper functions
# simple function that displays message boxes if necessary
function __msgbox ($title, [string[]]$text, $type = "None") {
    $msg = [Windows.Forms.MessageBox]::Show($text, $title, [Windows.Forms.MessageBoxButtons]::OK,
    [Windows.Forms.MessageBoxIcon]::$type)
}
# the function retrieves existing valid codesigning certificates.
function __get_certs {
    $certsinstore = @(Get-ChildItem cert:\currentuser\my -codesigning)
    if ($certsinstore.Length -gt 0) {
        $certsinstore | ForEach-Object {
            [void]($_.Subject -match 'CN=([^,]+)')
            [void]$CertMapping.Add($matches[1],$_.Thumbprint)
            [void]$Certs.Add($_)
            $matches[1]
        }
    }
}
# this function performs file signing
function __sign {
    $dgv.Rows | Where-Object {$_.Cells["filePath"].Value} | ForEach-Object {
        # if the file is already signed and force re-sign checkbox is not checked, the file is
        # skipped.
        if ($_.Cells["status"].Value -eq "Valid" -and !$ForceCheckBox.Checked) {return}
        # there might be cases when file was moved immediately after it was added.
        try {$file = Get-Item $_.Cells["filePath"].Value -ErrorAction Stop}
        catch {__msgbox "File Not Found" $_.Cells["filePath"].Value "Error"; return}
        $status = Set-AuthenticodeSignature $file.FullName $cert -TimestampServer $Timestamp
        $_.Cells["status"].Value = $status.Status
    }
}
#endregion
$CertMapping = @{}
$Certs = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
# this is a list of file extensions that supports authenticode signatures.
$SupportedExtensions = ".exe",".msi",".msp",".cmd",".bat",".vbs",".js",".ps1",".psm1",".psd1",".ps1xml",".dll",".ocx"
# it is highly recommended to put timestamp information in the signature. If you prefer other timestamping
# service, or do not want to use timestamps -- just remove this variable and variable usage in __sign function
$Timestamp = "http://timestamp.verisign.com/scripts/timstamp.dll"
# form UI
$form = New-Object Windows.Forms.Form -Property @{
    Text = "Sign files wizard"
    Size = New-Object System.Drawing.Size(455,350)
    StartPosition = "CenterScreen"
    MaximizeBox = $false
    MinimizeBox = $false
    FormBorderStyle = "FixedSingle"
    AutoSize = $false
}

$dgv = New-Object Windows.Forms.DataGridView -Property @{
    Size = New-Object System.Drawing.Size(430,200)
    Location = New-Object System.Drawing.Size(5,5)
    BorderStyle = "Fixed3D"
    ScrollBars = 3
    ReadOnly = $true
    AllowDrop = $true
}
$dgv.Columns.Add("filePath","File path")
$dgv.Columns.Add("status","Status")
$dgv.add_DragEnter({$_.effect = [Windows.Forms.DragDropEffects]::Move})
[void]$dgv.add_DragDrop({
    $items = @($_.Data.GetFileDropList()) | Select-Object -Unique
    foreach ($item in $items) {
        try {$file = Get-Item $item -ErrorAction Stop}
        catch {return}
        if ($SupportedExtensions -contains $file.Extension) {
            # do not allow duplicate file entries
            if ($dgv.Rows | Where-Object {$_.Cells["filePath"].Value -eq $item}) {return}
            # extract current signature information and add this information to grid view.
            $status = Get-AuthenticodeSignature $item -ErrorAction SilentlyContinue
            $dgv.Rows.Insert(0,@($item,$status.status))
        }
    }
})

$Label = New-Object Windows.Forms.Label -Property @{
    Location = New-Object System.Drawing.Size(5,210)
    Text = "Select a signing certificate:"
    AutoSize = $true
}

$ComboBox = New-Object Windows.Forms.ComboBox -Property @{
    Location = New-Object System.Drawing.Size(5,230)
    Width = 200
    DropDownStyle = "DropDownList"
}
__get_certs | ForEach-Object {[void]$ComboBox.Items.Add($_)}

# populate validity information about selected certificate and enable View and Sign buttons
$ComboBox.add_SelectedIndexChanged({
    if ($ComboBox.SelectedIndex -ge 0) {        
        $Thumbprint = $CertMapping[$ComboBox.SelectedItem]
        $cert = $Certs.Find("FindByThumbprint", $Thumbprint, $false)[0]
        $ValidFromLabel.Text = "Valid From: " + $cert.NotBefore.ToString()
        $ValidToLabel.Text = "Valid To: " + $cert.NotAfter.ToString()
        $ViewButton.Enabled = $true
        $SignButton.Enabled = $true
    }
})

$ViewButton = New-Object Windows.Forms.Button -Property @{
    Location = New-Object System.Drawing.Size(215,230)
    Text = "View"
    Enabled = $false
}
$ViewButton.add_Click({[Security.Cryptography.X509Certificates.X509Certificate2UI]::DisplayCertificate($cert)})

$ValidFromLabel = New-Object Windows.Forms.Label -Property @{
    Location = New-Object System.Drawing.Size(5,255)
    Text = "Valid From:"
    AutoSize = $true
}

$ValidToLabel = New-Object Windows.Forms.Label -Property @{
    Location = New-Object System.Drawing.Size(5,270)
    Text = "Valid To:"
    AutoSize = $true
}
# This check-box is used to specify behavior for already signed and valid files.
# By default only unsigned and files with bad signature are signed/re-signed.
# if we enable this check-box, all files are re-signed, even if they have valid signature.
$ForceCheckBox = New-Object Windows.Forms.CheckBox -Property @{
    Location = New-Object System.Drawing.Size(5,285)
    Text = "Force re-sign"
}

$SignButton = New-Object Windows.Forms.Button -Property @{
    Location = New-Object System.Drawing.Size(300,230)
    Size = New-Object System.Drawing.Size(135,50)
    Text = "Sign"
    Enabled = $false
}
$SignButton.add_Click({__sign})

$CopyLabel = New-Object Windows.Forms.Label -Property @{
    Location = New-Object System.Drawing.Size(270,290)
    Text = "Copyrights Vadims Podans (2012)"
    AutoSize = $true
}
# show UI
$dgv, $Label, $ComboBox, $ViewButton, $ValidFromLabel, $ValidToLabel,
$ForceCheckBox, $SignButton, $CopyLabel | ForEach-Object {$form.Controls.Add($_)}
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()

you can wrap this code to a function and store this function in the PowerShell profile. No parameters are necessary.

Note: this script was tested in PowerShell V2, it won’t work in PowerShell 3.0, unfortunately (requires some modifications in variable scopes). Also, PowerShell must be launched in STA mode.

Here is script download button:

HTH


Share this article:

Comments:

David Cryner
David Cryner 11.11.2020 02:45 (GMT+2) Bulk file signing with PowerShell and user interface

Vadims,

Thanks for the topic. Although posted a long ago, it still has a value.
Would you be able to provide the Script for Windows 10 build 1909 or higher, and for the current PowerShell version associated with it?
Many thanks,
Kind Regards,

David

 

Vadims Podāns
Vadims Podāns 11.11.2020 08:29 (GMT+2) Bulk file signing with PowerShell and user interface

This script works on Windows 10 perfectly. Even on my 2004 box. I'm still using this script and don't see any issues.


Post your comment:

Please, solve this little equation and enter result below. Captcha