PowerShell’s own zip cmdlet can’t add a password, so to create a password-protected zip file using PowerShell you hand the job to 7-Zip. The shortest safe version uses the 7Zip4Powershell module and asks for the password instead of storing it:
$pw = Read-Host -Prompt 'Zip password' -AsSecureString
Compress-7Zip -Path C:\Reports -ArchiveFileName C:\Reports.zip -Format Zip -EncryptionMethod Aes256 -SecurePassword $pw
I tested every step below in PowerShell 7.6 and Windows PowerShell 5.1 with 7-Zip 24.05, including the wrong-password cases.
Why can’t Compress-Archive password-protect a zip file?
Compress-Archive simply has no password or encryption parameter. You can check it yourself:
$cmd = Get-Command -Name Compress-Archive
"Archive module version: $($cmd.Version)"
"Has a -Password parameter: $($cmd.Parameters.ContainsKey('Password'))"
$names = $cmd.Parameters.Keys | Where-Object { $_ -match 'Path|Compression|Update|Force|PassThru' }
"Parameters: $($names -join ', ')"
Output:
Archive module version: 1.2.6
Has a -Password parameter: False
Parameters: Path, LiteralPath, DestinationPath, CompressionLevel, Update, Force, PassThru
PowerShell 7.6 ships Archive module 1.2.6 and Windows PowerShell 5.1 ships 1.0.1.0, and neither one has a -Password parameter. If you only need to lock a single file rather than build an archive, encrypt a file with a password in PowerShell instead.
So you need an external engine. 7-Zip is free, writes both .zip and .7z, and gives you two ways in: the 7z.exe command line and a PowerShell module.
Build the demo: sample files and a 7-Zip check
If you want to follow along, this creates three small 2026 sales files in C:\psfaqs\Reports2026 and checks that 7-Zip is installed:
$folder = 'C:\psfaqs\Reports2026'
New-Item -Path $folder -ItemType Directory -Force | Out-Null
"Region,Month,SalesUSD`nTexas,January,48250`nColorado,January,31900" |
Set-Content -Path "$folder\sales-2026-01.csv"
"Region,Month,SalesUSD`nTexas,February,51120`nColorado,February,29875" |
Set-Content -Path "$folder\sales-2026-02.csv"
'Q1 2026 summary: total sales 161,145 USD' | Set-Content -Path "$folder\q1-summary.txt"
$7z = Join-Path $env:ProgramFiles '7-Zip\7z.exe'
if (Test-Path -Path $7z) {
"7-Zip $((Get-Item $7z).VersionInfo.ProductVersion) is installed"
} else {
'Install 7-Zip first: winget install --id 7zip.7zip -e'
}
Get-ChildItem -Path $folder | ForEach-Object { '{0,-18} {1,3} bytes' -f $_.Name, $_.Length }
Output:
7-Zip 24.05 is installed
q1-summary.txt 42 bytes
sales-2026-01.csv 66 bytes
sales-2026-02.csv 68 bytes
Not installed? Run winget install --id 7zip.7zip -e to get it from the official package; my guide shows how to install winget using PowerShell if that command is missing. Test-Path makes the check safe to put at the top of any script.
How do I keep the zip password out of my script?
Never type the password into the .ps1 file. For an interactive run, prompt for it as a SecureString. For a scheduled job, save it once with Export-Clixml:
Read-Host -Prompt 'Zip password' -AsSecureString |
Export-Clixml -Path C:\psfaqs\zip-pass.xml
On Windows, that XML file is encrypted with DPAPI, so only your account on that computer can read it back. For this post, I saved a demo password the same way. Loading it gives a SecureString:
$secure = Import-Clixml -Path C:\psfaqs\zip-pass.xml
"Type: $($secure.GetType().Name)"
"Characters: $($secure.Length)"
$plain = [pscredential]::new('zip', $secure).GetNetworkCredential().Password
"Plain-text copy ready: $($plain.Length -gt 0)"
$plain = $null
Output:
Type: SecureString
Characters: 10
Plain-text copy ready: True
7z.exe can’t take a SecureString, so the script turns it into plain text right before the call and clears the variable afterwards. The PSCredential object trick works in both versions; PowerShell 7 also has ConvertFrom-SecureString -AsPlainText.
Create a password-protected zip with 7z.exe: AES-256 vs ZipCrypto
The a command adds files to an archive, -tzip picks the zip format, and -p sets the password. The -mem switch picks the encryption method, and that choice matters:
$7z = Join-Path $env:ProgramFiles '7-Zip\7z.exe'
$secure = Import-Clixml -Path C:\psfaqs\zip-pass.xml
$plain = [pscredential]::new('zip', $secure).GetNetworkCredential().Password
$src = 'C:\psfaqs\Reports2026\*'
Set-Location -Path C:\psfaqs\SecureZips
& $7z a -tzip "-p$plain" -mem=AES256 sales-2026-aes.zip $src -bso0 -bsp0
& $7z a -tzip "-p$plain" sales-2026-zipcrypto.zip $src -bso0 -bsp0
$plain = $null
foreach ($zip in 'sales-2026-aes.zip', 'sales-2026-zipcrypto.zip') {
$method = & $7z l -slt $zip | Select-String -Pattern '^Method' | Select-Object -First 1
'{0,-25} {1}' -f $zip, $method
}
Output:
sales-2026-aes.zip Method = AES-256 Store
sales-2026-zipcrypto.zip Method = ZipCrypto Store

Without -mem, 7-Zip falls back to ZipCrypto, the old zip cipher, which is weak. Always add -mem=AES256. The word Store just means these tiny files weren’t worth compressing.
The trade-off: Windows File Explorer and Expand-Archive can’t open AES zip files, so the person receiving it needs 7-Zip or a similar tool. The -bso0 and -bsp0 switches only silence 7-Zip’s progress output.
Hide the file names too: a .7z archive with -mhe=on
A password-protected zip still shows its file names to anyone who opens it. The .7z format can encrypt the names as well, with -mhe=on (header encryption):
$7z = Join-Path $env:ProgramFiles '7-Zip\7z.exe'
$secure = Import-Clixml -Path C:\psfaqs\zip-pass.xml
$plain = [pscredential]::new('zip', $secure).GetNetworkCredential().Password
$archive = 'C:\psfaqs\SecureZips\sales-2026.7z'
& $7z a -t7z "-p$plain" -mhe=on $archive C:\psfaqs\Reports2026\* -bso0 -bsp0
$lines = & $7z l -slt "-p$plain" $archive | Select-String -Pattern '^Path = [^C]'
$plain = $n