[CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$Path, [string]$PfxPath, [string]$CertificateThumbprint, [string]$TimestampServer = "http://timestamp.digicert.com", [switch]$NoTimestamp, [switch]$AllowUntrustedRoot ) $ErrorActionPreference = "Stop" function Resolve-FullPath { param([Parameter(Mandatory)][string]$Path) $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) } function Get-CodeSigningCertificateFromStore { param([Parameter(Mandatory)][string]$Thumbprint) $normalizedThumbprint = $Thumbprint -replace "\s", "" $certificate = Get-ChildItem "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" | Where-Object { $_.Thumbprint -eq $normalizedThumbprint } | Select-Object -First 1 if (-not $certificate) { throw "No code-signing certificate found with thumbprint $Thumbprint." } $certificate } if (-not $PfxPath -and -not $CertificateThumbprint) { throw "Provide either -PfxPath or -CertificateThumbprint." } if ($PfxPath -and $CertificateThumbprint) { throw "Use either -PfxPath or -CertificateThumbprint, not both." } if ($PfxPath) { $resolvedPfxPath = Resolve-FullPath $PfxPath if (-not (Test-Path -LiteralPath $resolvedPfxPath)) { throw "PFX file not found: $resolvedPfxPath" } $password = Read-Host "Enter PFX password" -AsSecureString $storageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedPfxPath, $password, $storageFlags) } else { $certificate = Get-CodeSigningCertificateFromStore -Thumbprint $CertificateThumbprint } if (-not $certificate.HasPrivateKey) { throw "The selected certificate does not include a private key and cannot sign files." } $targets = foreach ($item in $Path) { $resolvedPath = Resolve-FullPath $item if (Test-Path -LiteralPath $resolvedPath -PathType Container) { Get-ChildItem -LiteralPath $resolvedPath -Recurse -File | Where-Object { $_.Extension -in ".exe", ".msi", ".dll", ".ps1", ".psm1", ".psd1", ".cat" } } elseif (Test-Path -LiteralPath $resolvedPath -PathType Leaf) { Get-Item -LiteralPath $resolvedPath } else { throw "Path not found: $resolvedPath" } } if (-not $targets) { throw "No files found to sign." } foreach ($target in $targets) { Write-Host "Signing $($target.FullName)" $signatureArguments = @{ FilePath = $target.FullName Certificate = $certificate HashAlgorithm = "SHA256" } if (-not $NoTimestamp -and $TimestampServer) { $signatureArguments.TimestampServer = $TimestampServer } $signature = Set-AuthenticodeSignature @signatureArguments if ($signature.Status -eq "UnknownError" -and $AllowUntrustedRoot -and $signature.SignerCertificate) { Write-Warning "Signed $($target.FullName), but the local machine does not trust the signing root yet." continue } if ($signature.Status -ne "Valid") { throw "Signing failed for $($target.FullName): $($signature.StatusMessage)" } } Write-Host "Signed $($targets.Count) file(s)."