PowerShell 压缩 PDF 后无法删除源文件,如何正确释放文件句柄?
需要显式释放 PDF 读取流和 ZIP 条目写入流。原代码只释放了 ZipArchive 和 ZIP 文件流,没有释放 OpenRead() 返回的源文件流,这直接导致 PDF 持续被占用。
哪一行没有释放文件?
问题出在这一行:
[System.IO.File]::OpenRead($file.FullName).CopyTo($entry.Open())
它打开了两个流:
OpenRead($file.FullName)返回读取 PDF 的FileStream。$entry.Open()返回写入 ZIP 条目的流。
CopyTo() 只负责复制数据,不会替调用方关闭这两个流。代码没有把流保存到变量中,也没有显式调用它们的 Dispose()。
$archive.Dispose() 会完成归档并释放相关资源,但不会关闭独立打开的 PDF 读取流。PowerShell 也没有一个通用的“关闭所有文件”命令可以代替对这些对象的资源管理。
脚本结束后能够删除文件,这与句柄最终被释放的现象一致,但不应依赖脚本结束或垃圾回收来决定文件何时可删除。
如何修改脚本?
将每个流保存到变量中,并用 try/finally 释放。下面的代码假设 $sourceDir 已设置,可用来替换原来的压缩循环和最后扫描 ZIP 删除 PDF 的部分:
$files = @(Get-ChildItem -LiteralPath $sourceDir -Filter '*.pdf' -File)
if ($files.Count -eq 0) {
Write-Host "No PDF files found in '$sourceDir'."
}
foreach ($file in $files) {
$zipPath = Join-Path $sourceDir ($file.BaseName + '.ZIP')
try {
$zipStream = [System.IO.File]::Open($zipPath, 'Create')
try {
$archive = [System.IO.Compression.ZipArchive]::new(
$zipStream,
[System.IO.Compression.ZipArchiveMode]::Create
)
try {
$entry = $archive.CreateEntry($file.Name)
$sourceStream = [System.IO.File]::OpenRead($file.FullName)
try {
$entryStream = $entry.Open()
try {
$sourceStream.CopyTo($entryStream)
}
finally {
$entryStream.Dispose()
}
}
finally {
$sourceStream.Dispose()
}
}
finally {
$archive.Dispose()
}
}
finally {
$zipStream.Dispose()
}
Write-Host "Zipped '$($file.Name)' to '$zipPath'"
# Delete the source only after copying and archive disposal succeed.
Remove-Item -LiteralPath $file.FullName -ErrorAction Stop
Write-Host "Deleted '$($file.Name)'"
}
catch {
Write-Error "Failed to process '$($file.Name)': $_"
}
}
删除操作放在当前文件的所有资源释放之后。如果复制或归档结束过程抛出异常,代码会进入 catch,不会继续删除该 PDF。嵌套的 finally 也能确保内层操作失败时,外层资源仍会尝试释放。
为什么不继续扫描 ZIP 来删除 PDF?
原代码以目录中存在同名 ZIP 为依据删除 PDF,但这不能证明本次压缩成功:ZIP 可能来自之前的运行,也可能是本次异常后留下的不完整文件。
根据当前文件的处理结果执行删除,可以避免因这些 ZIP 误删源文件。示例中的成功条件是复制和归档结束过程没有报告异常;它没有重新解压或校验内容。如果源 PDF 很重要,可以在删除前增加归档读取检查或解压后的内容比对。
另外,File.Open($zipPath, 'Create') 会覆盖已有的同名 ZIP,与原代码的行为一致。如果需要保留已有文件,应先检查目标是否存在,或改用 CreateNew,让同名冲突直接报错。
修改后仍然提示占用怎么办?
先确认原来的链式 OpenRead(...).CopyTo(...) 已被替换,脚本其他位置也没有留下未释放的读取流。之前运行所遗留的句柄不会因为修改了后续代码就自动关闭。
如果错误仍然出现,再检查 PDF 阅读器、资源管理器预览或其他进程是否打开了文件。Remove-Item -Force 无法绕过仍然有效的文件占用锁,必须先释放持有文件的句柄。
备注:内容仅供参考。
