How to Create a Password-Protected Zip File Using PowerShell

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
7z.exe creates one AES-256 zip and one ZipCrypto zip from PowerShell
Without -mem=AES256 the zip uses the old ZipCrypto cipher (console, PowerShell 7)

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