PowerShell压缩脚本无法保留目录结构且报文件未找到错误
解决PowerShell自定义压缩脚本的两个问题
一、修复「File not found」错误
错误原因
- Test.ps1调用语法错误:原调用代码
Compress-CompatibleArchive (args[1]) (args[2])未给args加$符号,PowerShell无法识别为自动变量,导致参数传递失效,实际传入的路径无效;同时参数索引错误,命令行第一个参数是脚本本身,源路径应为$args[0]、目标路径为$args[1]。 - 脚本未支持目录处理:原
ValidatePaths函数仅通过FileInfo判断路径,若传入目录,后续用FileStream打开会触发错误,且脚本未递归处理目录内的文件。
修复步骤
- 修正Test.ps1调用代码:
Compress-CompatibleArchive $args[0] $args[1]
- 更新ValidatePaths函数:支持验证文件或目录路径的存在性:
function ValidatePaths([String[]] $paths) { foreach ($f in $paths) { if (-not (Test-Path -Path $f -PathType Any)) { throw [FileNotFoundException] "File or directory not found ($f)" } } }
二、修复Zip文件不保留目录结构问题
问题原因
原脚本仅使用[Path]::GetFileName($file)作为Zip条目名称,所有文件都会被压缩到Zip根目录,丢失原有目录层级。需要为每个文件生成相对于源路径的相对路径,作为Zip条目名称。
修复步骤
- 添加递归遍历目录的逻辑,获取目录下所有子文件;
- 计算每个文件相对于源路径的相对路径,作为Zip条目名称,保留原结构。
修改后的完整脚本
using namespace System.IO using namespace System.IO.Compression function Compress-CompatibleArchive { param ( [String[]] $Path, [String] $DestinationPath, [ValidateSet("Fastest", "NoCompression", "Optimal", "Maximum")] [String] $CompressionLevel = "Optimal" ) ValidatePaths($Path) ValidateDestinationPath($DestinationPath) # 解析压缩级别 $level = switch ($CompressionLevel) { "Fastest" { [CompressionLevel]::Fastest } "Optimal" { [CompressionLevel]::Optimal } "Maximum" { [CompressionLevel]::SmallestSize } default { [CompressionLevel]::NoCompression } } $outputStream = [FileStream]::new($DestinationPath, [FileMode]::Create, [FileAccess]::Write) try { $archive = [Compression.ZipArchive]::new($outputStream, [Compression.ZipArchiveMode]::Create) try { foreach ($itemPath in $Path) { $item = Get-Item -Path $itemPath $rootPath = $item.FullName # 处理目录或文件 if ($item.PSIsContainer) { # 递归获取目录下所有文件 $files = Get-ChildItem -Path $rootPath -Recurse -File foreach ($file in $files) { # 计算相对路径作为Zip条目名称 $relativePath = $file.FullName.Substring($rootPath.Length + 1) $entry = $archive.CreateEntry($relativePath, $level) CopyFileToArchiveEntry $file.FullName $entry } } else { # 处理单个文件 $entry = $archive.CreateEntry($item.Name, $level) CopyFileToArchiveEntry $item.FullName $entry } } } finally { $archive.Dispose() } } finally { $outputStream.Dispose() } } # 辅助函数:复制文件到Zip条目 function CopyFileToArchiveEntry([String]$filePath, [ZipArchiveEntry]$entry) { $entryStream = $entry.Open() try { $inputStream = [FileStream]::new($filePath, [FileMode]::Open, [FileAccess]::Read) try { $inputStream.CopyTo($entryStream) } finally { $inputStream.Dispose() } } finally { $entryStream.Dispose() } } function ValidatePaths([String[]] $paths) { foreach ($f in $paths) { if (-not (Test-Path -Path $f -PathType Any)) { throw [FileNotFoundException] "File or directory not found ($f)" } } } function ValidateDestinationPath { param ( [String] $destinationPath ) $di = [DirectoryInfo]::new($destinationPath) if ($di.Exists -or [Path]::EndsInDirectorySeparator($destinationPath)) { throw [IOException] "DestinationPath cannot be a directory ($destinationPath)" } $fi = [FileInfo]::new($destinationPath) if ($fi.Exists) { throw [IOException] "File already exists ($destinationPath)" } } Export-ModuleMember -Function Compress-CompatibleArchive
关键修改说明
- 添加默认压缩级别:为
$CompressionLevel设置默认值Optimal,提升易用性; - 优化压缩级别解析:用
switch替代原if-else链,代码更简洁; - 递归处理目录:通过
Get-ChildItem -Recurse -File获取目录下所有文件,避免遗漏子文件; - 保留目录结构:计算文件相对路径作为Zip条目名称,确保压缩后层级与原结构一致;
- 资源安全释放:用
try-finally块确保流和ZipArchive资源被正确释放,避免文件句柄泄漏; - 提取辅助函数:将文件复制逻辑独立,减少代码重复,提升可维护性。
测试验证
- 确保Test.ps1内容正确;
- 命令行调用示例:
./Test.ps1 "C:\Users\USER\OneDrive\Pictures\testing" "C:\output\test.zip"
此时脚本会正确压缩目录内所有文件并保留原结构,且不会触发「File not found」错误。
内容的提问来源于stack exchange,提问作者Royston
相关产品推荐
相关产品推荐

