Public/Add-SignProfile.ps1

<#
.SYNOPSIS
Creates a new code signing profile for use with SignModule.
 
.DESCRIPTION
The Add-SignProfile function creates a new signing profile that can be used to sign executable files.
Profiles can be created interactively (by providing configuration details through prompts) or by
importing an existing profile JSON file. Profiles support both local certificate signing and
Azure Key Vault integration.
 
.PARAMETER ProfileName
Specifies the name for the new signing profile. This name will be used to reference the profile
in other SignModule commands. Profile names must be unique and cannot contain invalid characters.
 
.PARAMETER ProfilePath
Optional path to an existing profile JSON file to import. If not specified, the function will
prompt for profile configuration details interactively. The JSON file should contain the
profile configuration in the expected format.
 
.INPUTS
None. You cannot pipe objects to Add-SignProfile.
 
.OUTPUTS
None. This function does not return any objects.
 
.EXAMPLE
Add-SignProfile -ProfileName "Production"
 
Creates a new profile named "Production" using interactive prompts to configure the signing details.
You will be prompted to specify whether this is a local certificate or Azure Key Vault profile,
and then provide the necessary configuration details.
 
.EXAMPLE
Add-SignProfile -ProfileName "Staging" -ProfilePath "C:\Certs\staging-profile.json"
 
Creates a new profile named "Staging" by importing configuration from an existing JSON file.
The JSON file should contain the profile configuration in the expected format.
 
.EXAMPLE
Add-SignProfile -ProfileName "LocalCert"
# When prompted, select 'local' type and provide:
# - Signing tool path: C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe
# - Certificate path: C:\Certificates\MyCert.pfx
# - Certificate password: (entered securely)
# - Timestamp URL: http://timestamp.digicert.com
 
Example of creating a local certificate profile with typical Windows SDK paths.
 
.EXAMPLE
Add-SignProfile -ProfileName "AzureKV"
# When prompted, select 'azure' type and provide:
# - Azure SignTool path: C:\Tools\azuresigntool.exe
# - Key Vault URL: https://myvault.vault.azure.net/
# - Tenant ID: 12345678-1234-1234-1234-123456789012
# - Client ID: 87654321-4321-4321-4321-210987654321
# - Client Secret: (entered securely)
# - Certificate Name: CodeSigningCert
 
Example of creating an Azure Key Vault profile for cloud-based signing.
 
.NOTES
File Name : Add-SignProfile.ps1
Author : GrafGenerator
Prerequisite : PowerShell 5.1 or later
Copyright 2025 : GrafGenerator
 
Profile configuration files are stored in %PSModulePath%\SignModule\profiles\ and contain
non-sensitive configuration data only. Sensitive information like passwords and secrets
are stored separately using PowerShell SecureString encryption.
 
.LINK
Update-SignProfile
 
.LINK
Remove-SignProfile
 
.LINK
Export-SignedExecutable
 
.LINK
https://github.com/GrafGenerator/pwsh-sign-module
#>

function Add-SignProfile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ProfileName,
        
        [Parameter()]
        [string]$ProfilePath
    )

    Initialize-ModuleConfig
    $config = Get-Config

    Test-ProfileName -ProfileName $ProfileName

    if ($config.profiles.ContainsKey($ProfileName)) {
        throw "Profile '$ProfileName' already exists"
    }

    if (-not $ProfilePath) {
        $profileType = Read-Host "Enter profile type (local/azure)"
        if ($profileType -notin @('local', 'azure')) {
            throw "Invalid profile type. Must be 'local' or 'azure'"
        }

        $targetProfilePath = Join-Path $script:PROFILES_DIR "$ProfileName.json"

        $profileData = @{
            type = $profileType
        }

        if ($profileType -eq 'local') {
            $profileData.signToolPath = Read-Host "Enter path to local sign tool installation"
            $profileData.certificatePath = Read-Host "Enter path to local certificate"

            $securePassword = Read-Host "Enter certificate password" -AsSecureString
            Save-SecureInput -ProfileName $ProfileName -ProfilePath $targetProfilePath -InputAlias "pwd" -SecureInput $securePassword

            $additionalParams = Read-Host "Enter additional parameters for sign tool (e.g., '/tr http://timestamp.server' - optional)"
            if (-not [string]::IsNullOrWhiteSpace($additionalParams)) {
                $profileData.additionalParams = $additionalParams
            }
        }
        else {
            $profileData.signToolPath = Read-Host "Enter path to azure sign tool installation"
            $profileData.keyVaultUrl = Read-Host "Enter key vault URL"
            $profileData.tenantId = Read-Host "Enter tenant ID"
            $profileData.clientId = Read-Host "Enter client ID"
            $profileData.certificateName = Read-Host "Enter certificate name"

            $secureSecret = Read-Host "Enter client secret" -AsSecureString
            Save-SecureInput -ProfileName $ProfileName -ProfilePath $targetProfilePath -InputAlias "kvs" -SecureInput $secureSecret

            $additionalParams = Read-Host "Enter additional parameters for sign tool (e.g., '-tr http://timestamp.server' - optional)"
            if (-not [string]::IsNullOrWhiteSpace($additionalParams)) {
                $profileData.additionalParams = $additionalParams
            }
        }

        $profileData | ConvertTo-Json | Set-Content $targetProfilePath
    }
    else {
        $targetProfilePath = [FileInfo]::new($ProfilePath).FullName

        if (-not (Test-Path $targetProfilePath)) {
            throw "Profile file not found at path: $targetProfilePath"
        }
    }

    $config.profiles[$ProfileName] = @{
        path = $targetProfilePath
    }

    Save-Config $config
}