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 aSecureStringpassword, which doesn't print or leak into logs by accident. ConvertTo-SecureString -AsPlainTextis shown only to build an example — in real scripts, the plain password shouldn't exist at all. Get it fromGet-Credentialor 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.