Skip to content
AgileLogics
6 min read

Security and Script Signing

Prerequisites: Your first script

Execution policy is not security

The execution policy stops accidental script runs, but anyone can bypass it (pwsh -ExecutionPolicy Bypass). Real protection comes from signing, application control (AppLocker / App Control for Business), and logging.

Script signing

A digital signature proves who wrote a script and that it hasn't been changed since. With the AllSigned policy, only signed scripts run.

# illustrative
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
Set-AuthenticodeSignature -FilePath .\deploy.ps1 -Certificate $cert `
  -TimestampServer 'http://timestamp.digicert.com'
Get-AuthenticodeSignature .\deploy.ps1 | Select-Object Status, SignerCertificate

Editing a signed script invalidates the signature. In organizations, the certificate usually comes from an internal certificate authority.

Never run untrusted input as code

$userInput = 'report.txt; Remove-Item -Recurse important'

# Dangerous: the input is executed as PowerShell code
# Invoke-Expression "Get-Content $userInput"

# Safe: the input is only ever a value
Set-Content report.txt 'quarterly numbers'
Get-Content -LiteralPath $userInput -ErrorAction SilentlyContinue
"Input treated as a file name: $(Test-Path -LiteralPath $userInput)"
Input treated as a file name: False

Invoke-Expression (and building commands as strings) is the PowerShell equivalent of SQL injection. Pass values as parameters — PowerShell never executes them. -LiteralPath also stops wildcards (*, [) in names being interpreted.

Handling credentials

$secure = ConvertTo-SecureString 'P@ssw0rd!' -AsPlainText -Force
$cred = [pscredential]::new('svc-backup', $secure)
$cred.UserName
$cred.Password.GetType().Name
"$cred"
svc-backup
SecureString
System.Management.Automation.PSCredential
  • A [pscredential] holds a user name and a SecureString password, which doesn't print or leak into logs by accident.
  • ConvertTo-SecureString -AsPlainText is shown only to build an example — in real scripts, the plain password shouldn't exist at all. Get it from Get-Credential or a vault.

Logging and auditing

Enable script block logging (Group Policy: Turn on PowerShell Script Block Logging) to record every script block that runs, in the Microsoft-Windows-PowerShell/Operational event log. Security teams rely on it to detect malicious PowerShell.

Common mistakes

  • Downloading and running scripts in one line (iwr https://... | iex) without reading them first.
  • Disabling the execution policy system-wide instead of signing scripts.