Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Drag-and-drop to sign code

I set up a simpler hands-on process for signing binary assets of various kinds. There are apparently still people in the world who don't want to "sign every rubbish binary blob the software developers produce." (Read: give developers a CI/CD with automatic signing, so that they can produce whatever malware they want and get a company signature on it.) For small-scale operations, this suffices to manually sign assets.

The Windows world calls this "Authenticode." There is no direct equivalent in the Free Software world, because official software comes from gpg-signed rpm or apt repositories. Additional installation assets are usually published with checksums on https pages.

The solution involves the signing process with a specific certificate identified by thumbprint. Windows really wants to identify certificates by thumbprint rather than by serial number. Sometimes that means you can get a new certificate with the exact same thumbprint, which I'm sure is useful somewhere. (It also means you're using the same private key, which may or may not be within your certificate policy to allow.)

Yes, this is partially vibecoded. It's my standard format of a library that can be loaded into the current shell (I forgot how and why to use modules.)

files/2026/listings/Sign-Code.ps1 (Source)

#!/bin/pwsh
# File: Sign-Code.ps1
# Last-Modified: 2026-04-01T11:07:34-0400
# Locations:
#     \\utility5\e$\CodeSign\Scripts
# Author: bgstack15
# Startdate: 2026-03-31
# Title: Sign-Code
# Project: Codesign
# Purpose: Functions for easily signing binaries on utility5
# History:
# Usage:
#    As a library, . .\Sign-Code.ps1
#    Or directly, ".\Sign-Code.ps1"
# References:
#     https://www.scriptinglibrary.com/languages/powershell/how-to-modify-your-proxy-settings-with-powershell/
#     https://4sysops.com/archives/how-to-create-an-open-file-folder-dialog-box-with-powershell/
# Improve:
# Dependencies:
#    Cert loaded in Cert:\LocalMachine\My
# Documentation:
<#
.SYNOPSIS
    Functions for easily signing binaries on the utility server.
.DESCRIPTION
    This script provides functions to configure proxy settings, select files via a dialog,
    sign binaries using signtool.exe, and move signed files to a destination folder.
    It supports being dot-sourced as a library or run directly to invoke the signing process.
.NOTES
.USAGE
    As a library (import functions for manual use):
        . .\Sign-Code.ps1
    Run directly (sign files from default or specified folders):
        .\Sign-Code.ps1
.PARAMETER Set-Proxy
    Configures the user's system proxy settings by modifying registry keys.
.PARAMETER Get-File-Dialog
    Opens a Windows File Open dialog to select one or more files.
.PARAMETER Sign-File
    Signs each file using signtool.exe and moves successfully signed files to a destination folder.
.PARAMETER Ask-Sign-Files
    Main function to select files, and sign them.
.DEPENDENCIES
    - Windows signtool.exe located at the specified path.
    - Code signing certificate loaded in Cert:\LocalMachine\My.
    - User has write permissions to destination folders.
.REFERENCES
    - https://www.scriptinglibrary.com/languages/powershell/how-to-modify-your-proxy-settings-with-powershell/
    - https://4sysops.com/archives/how-to-create-an-open-file-folder-dialog-box-with-powershell/
#>
#region Proxy Configuration Function
Function Set-Proxy {
    <#
    .SYNOPSIS
        Sets the system proxy server and port for the current user.
    .PARAMETER Server
        The proxy server address (e.g., proxy.example.com).
    .PARAMETER Port
        The proxy server port number (e.g., 8080).
    .EXAMPLE
        Set-Proxy -Server proxy.example.com -Port 8080
    #>
    [CmdletBinding()]
    [Alias('proxy')]
    [OutputType([string])]
    Param
    (
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0)]
        [string]$Server,
        [Parameter(Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 1)]
        [int]$Port
    )
    # Set proxy server address and enable proxy in the user's registry
    Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -Name ProxyServer -Value "$($Server):$($Port)"
    Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -Name ProxyEnable -Value 1
    # (Optional) Display the current proxy settings
    # Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' | Select-Object ProxyServer, ProxyEnable
}
#endregion Proxy Configuration Function
#region File Selection Function
Function Get-File-Dialog {
    <#
    .SYNOPSIS
        Opens a file open dialog for the user to select one or multiple files.
    .PARAMETER InitialFolder
        The folder that the dialog will open initially.
    .OUTPUTS
        String[] - Full paths of the selected files.
    .EXAMPLE
        $files = Get-File-Dialog -InitialFolder "E:\CodeSign\ToBeSigned"
    #>
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [string]$InitialFolder = "E:\CodeSign\ToBeSigned"
    )
    # Load Windows Forms assembly for GUI dialog
    Add-Type -AssemblyName System.Windows.Forms
    # Configure and show OpenFileDialog allowing multi-selection
    $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
        InitialDirectory = $InitialFolder;
        Title = "Choose file(s) to sign";
        MultiSelect = $true
    }
    $null = $FileBrowser.ShowDialog()
    # Return array of selected file paths
    return $FileBrowser.FileNames
}
#endregion File Selection Function
#region File Signing Function
Function Sign-File {
    <#
    .SYNOPSIS
        Signs one or more files using signtool.exe and moves successfully signed files.
    .PARAMETER FilePath
        One or more full file paths to sign. Supports pipeline input.
    .PARAMETER DestinationFolder
        Directory where successfully signed files will be moved.
    .NOTES
        Requires signtool.exe at specified path and appropriate certificates installed.
    .EXAMPLE
        $files | Sign-File -DestinationFolder "E:\CodeSign\SignedCode"
    #>
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true,
                   ValueFromPipeline = $true,
                   ValueFromPipelineByPropertyName = $true,
                   Position = 0)]
        [string[]]$FilePath,
        [Parameter(Mandatory = $true)]
        [string]$DestinationFolder
    )
    Process {
        foreach ($file in $FilePath) {
            Write-Host "Signing file: $file"
            # Execute signtool.exe with specified options
            & "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe" sign `
                /fd sha256 /td sha256 /v `
                /f "E:\CodeSign\cert2025\example.cer" `
                /sha1 B38EXAMPLE17340436A80B0A2FDC3BEC9352250F `
                /kc CodeSignExample2025 `
                /csp "Safenet Key Storage Provider" `
                /tr http://timestamp.digicert.com `
                $file
            $exitCode = $LASTEXITCODE
            if ($exitCode -eq 0) {
                # Signing succeeded; ensure destination folder exists
                if (-not (Test-Path -Path $DestinationFolder)) {
                    New-Item -Path $DestinationFolder -ItemType Directory -Force | Out-Null
                }
                # Move signed file to destination folder
                $destinationPath = Join-Path -Path $DestinationFolder -ChildPath (Split-Path $file -Leaf)
                try {
                    Move-Item -Path $file -Destination $destinationPath -Force
                    Write-Host "Moved file to $destinationPath"
                }
                catch {
                    Write-Warning "Failed to move file $file to ${destinationPath}: $_"
                }
            }
            else {
                Write-Warning "Signing failed for $file with exit code $exitCode. File not moved."
            }
        }
    }
}
#endregion File Signing Function
#region Main Orchestration Function
Function Ask-Sign-Files {
    <#
    .SYNOPSIS
        Orchestrates proxy setup, file selection, and signing of files.
    .PARAMETER DestinationFolder
        Optional. Destination folder for signed files. Defaults to "E:\CodeSign\SignedCode".
    .PARAMETER InitialFolder
        Optional. Initial folder for the file selection dialog.
    .EXAMPLE
        Ask-Sign-Files -InitialFolder "E:\CodeSign\ToBeSigned" -DestinationFolder "E:\CodeSign\SignedCode"
    #>
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [string]$DestinationFolder = "E:\CodeSign\SignedCode",
        [Parameter(Mandatory = $false)]
        [string]$InitialFolder
    )
    # Configure proxy settings for the user environment
    Set-Proxy -Server proxy.example.com -Port 8080
    # Get files to sign via file dialog
    $Infiles = Get-File-Dialog -InitialFolder $InitialFolder
    # Pass files and destination folder to signing function
    $Infiles | Sign-File -DestinationFolder $DestinationFolder
}
#endregion Main Orchestration Function
#region Script Execution Control
# Check if script is being run directly or dot-sourced/imported
# If called directly, with more than 0 arguments
if ($args.Count -gt 0) {
    # Files were dropped onto the script or shortcut, process them directly
    # Assuming the DestinationFolder is set or can be defaulted
    $destinationFolder = "E:\CodeSign\SignedCode"
    # Validate that all args are files
    $filesToSign = $args | Where-Object { Test-Path $_ -PathType Leaf }
    if ($filesToSign.Count -eq 0) {
        Write-Warning "No valid files passed as arguments."
        exit 1
    }
    # Call Sign-File with the files from args
    $filesToSign | Sign-File -DestinationFolder $destinationFolder
}
elseif ($MyInvocation.InvocationName.Contains($MyInvocation.MyCommand.Name) -or
    $MyInvocation.InvocationName -eq $PSCommandPath) {
    # Script is being run directly; invoke main function with default folders
    Ask-Sign-Files -InitialFolder "E:\CodeSign\ToBeSigned" -DestinationFolder "E:\CodeSign\SignedCode"
}
#endregion Script Execution Control

And somehow in the loss of progress, you cannot set up a shortcut to run powershell, and have it accept parameters. (Or I never learned. I'm sure the comments will correct me shortly.) So you can do that with the following batch script.

files/2026/listings/Sign-Code.bat (Source)

@echo off
REM File: Sign-Code.bat
REM Purpose: make it possible to drop files onto this
:: Check for admin rights by trying to create a folder in %windir%
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
if '%errorlevel%' NEQ '0' (
    echo Requesting administrative privileges...
    powershell.exe -Command "Start-Process -FilePath '%~f0' -ArgumentList '%*' -Verb RunAs"
    exit /b
)
:: If we get here, we have admin rights
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "E:\CodeSign\Scripts\Sign-Code.ps1" %*
pause

Set up a shortcut to the script. You don't need to customize parameters; it will already handle them correctly. Set up your .lnk to point to C:\Path\Sign-Code.bat and that is all.

Comments