如何自动打印带详细选项的PDF?加密与选纸匣方案问询
解决方案:加密PDF批量打印与单打印机纸匣控制
针对你日常打印PDF时遇到的加密文件处理难题,以及未来想用单台双纸匣打印机统一完成打印的需求,我整理了两种问题的具体解决办法,直接上手就能用:
问题1:PowerShell中处理自动解密的加密PDF
你的现有脚本用PDFtk拆分失败,核心原因是这类Adobe能自动解密的PDF(通常是权限保护而非打开密码),PDFtk没有办法处理这种动态解密的逻辑。这里给你两种可行的思路:
方案1:用Adobe Acrobat COM接口导出解密版
如果公司有安装Adobe Acrobat Pro(注意Reader的COM功能不全,没法导出),可以直接通过PowerShell调用Acrobat的自动化接口,先打开PDF等解密完成,再导出无加密版本,之后再拆分:
# 初始化Acrobat应用(隐藏窗口) $acroApp = New-Object -ComObject AcroExch.App $acroApp.Hide() # 遍历目标文件夹的PDF foreach ($file1 in Get-ChildItem "C:\batchPrintPKs\unsplit_pdfs\*.pdf") { $acroPDDoc = New-Object -ComObject AcroExch.PDDoc # 打开PDF,等待自动解密 if (-not $acroPDDoc.Open($file1.FullName)) { Write-Warning "打不开文件:$($file1.Name)" $acroPDDoc.Close() continue } # 给3秒解密时间,根据实际情况调整 Start-Sleep -Seconds 3 # 导出解密后的临时PDF $decryptedPath = Join-Path $env:TEMP "decrypted_$($file1.Name)" if (-not $acroPDDoc.Save(1, $decryptedPath)) { # 1代表保存为PDF格式 Write-Warning "导出解密文件失败:$($file1.Name)" $acroPDDoc.Close() continue } $acroPDDoc.Close() # 用解密后的文件执行拆分逻辑 $pageCount = [regex]::match((& "C:\Program Files (x86)\PDFtk Server\bin\pdftk.exe" $decryptedPath dump_data), 'NumberOfPages: (\d+)').Groups[1].Value $lastMinusOne = $pageCount - 1 # 拆分前n-1页到白纸打印文件夹 & "C:\Program Files (x86)\PDFtk Server\bin\pdftk.exe" $decryptedPath cat 1-$lastMinusOne output "C:\batchPrintPKs\split_pdfs_f\f_$($file1.Name)" # 拆分最后一页到蓝纸打印文件夹 & "C:\Program Files (x86)\PDFtk Server\bin\pdftk.exe" $decryptedPath cat $pageCount output "C:\batchPrintPKs\split_pdfs_l\l_$($file1.Name)" # 清理临时文件和原文件 Remove-Item $decryptedPath Remove-Item $file1.FullName } # 释放COM对象,避免Acrobat进程残留 $acroApp.Exit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($acroPDDoc) | Out-Null [System.Runtime.Interopservices.Marshal]::ReleaseComObject($acroApp) | Out-Null
方案2:用iTextSharp直接处理加密PDF
如果没有Acrobat Pro,推荐用iTextSharp这个.NET库,它能直接解密大多数权限保护的PDF。先下载iTextSharp的DLL(或者通过NuGet安装),然后用下面的脚本:
# 加载iTextSharp库,替换成你的DLL路径 Add-Type -Path "C:\Tools\itextsharp.dll" foreach ($file1 in Get-ChildItem "C:\batchPrintPKs\unsplit_pdfs\*.pdf") { $reader = New-Object iTextSharp.text.pdf.PdfReader($file1.FullName) # 尝试解密(如果是无用户密码的权限加密,空字符串即可) if ($reader.IsEncrypted()) { $reader.Unlock("", "") } $pageCount = $reader.NumberOfPages $lastMinusOne = $pageCount - 1 # 生成前n-1页的PDF $fsFront = New-Object System.IO.FileStream("C:\batchPrintPKs\split_pdfs_f\f_$($file1.Name)", [System.IO.FileMode]::Create) $writerFront = New-Object iTextSharp.text.pdf.PdfCopy($fsFront, $reader) for ($i=1; $i -le $lastMinusOne; $i++) { $writerFront.AddPage($reader.GetPage($i)) } $writerFront.Close() $fsFront.Close() # 生成最后一页的PDF $fsLast = New-Object System.IO.FileStream("C:\batchPrintPKs\split_pdfs_l\l_$($file1.Name)", [System.IO.FileMode]::Create) $writerLast = New-Object iTextSharp.text.pdf.PdfCopy($fsLast, $reader) $writerLast.AddPage($reader.GetPage($pageCount)) $writerLast.Close() $fsLast.Close() $reader.Close() Remove-Item $file1.FullName }
这个方案不需要依赖Adobe,更轻量化。
问题2:单台双纸匣打印机自动选纸打印(C#实现)
等公司配备双纸匣打印机后,用C#直接控制每个页面的纸张来源是最优解——不用拆分PDF,直接按页面指定纸匣,完美保证顺序。下面是完整的控制台程序:
using System; using System.IO; using System.Drawing.Printing; using System.Linq; using PdfSharp.Pdf; using PdfSharp.Pdf.IO; using PdfSharp.Drawing; namespace PdfTrayPrinter { class Program { // 配置参数,根据实际情况修改 private const string InputFolder = @"C:\batchPrintPKs\unsplit_pdfs"; private const string PrinterName = @"Brother DCP-LXXX Dual-Tray Printer"; // 你的打印机名称 private const PaperSourceKind WhitePaperTray = PaperSourceKind.Upper; // 白纸匣的类型 private const PaperSourceKind BluePaperTray = PaperSourceKind.Lower; // 蓝纸匣的类型 static void Main(string[] args) { foreach (var pdfPath in Directory.GetFiles(InputFolder, "*.pdf")) { Console.WriteLine($"正在打印:{Path.GetFileName(pdfPath)}"); PrintPdfWithTray(pdfPath); // 打印完成后删除原文件,可选 File.Delete(pdfPath); } Console.WriteLine("所有文件打印完成!"); Console.ReadKey(); } static void PrintPdfWithTray(string pdfPath) { using (var pdfDoc = PdfReader.Open(pdfPath, PdfDocumentOpenMode.Import)) { var printDoc = new PrintDocument { PrinterSettings = { PrinterName = PrinterName, FromPage = 1, ToPage = pdfDoc.PageCount } }; // 为每个页面指定纸匣 printDoc.QueryPageSettings += (sender, e) => { var currentPage = e.PageSettings.PrinterSettings.FromPage; var targetTray = currentPage == pdfDoc.PageCount ? BluePaperTray : WhitePaperTray; // 找到对应的纸匣 e.PageSettings.PaperSource = printDoc.PrinterSettings.PaperSources .First(ps => ps.Kind == targetTray); }; // 绘制PDF页面到打印文档 printDoc.PrintPage += (sender, e) => { var pageIndex = e.PageSettings.PrinterSettings.FromPage - 1; using (var gfx = XGraphics.FromPrintPageEventArgs(e)) { gfx.DrawPage(pdfDoc.Pages[pageIndex]); } }; try { printDoc.Print(); } catch (Exception ex) { Console.WriteLine($"打印失败:{ex.Message}"); } } } } }
注意事项:
- 先通过NuGet安装
PdfSharp包(右键项目→管理NuGet程序包→搜索PdfSharp安装)。 - 纸匣的
PaperSourceKind枚举值需要根据你的打印机调整——可以先在代码里加一行printDoc.PrinterSettings.PaperSources.ToList().ForEach(ps => Console.WriteLine($"{ps.Kind} - {ps.SourceName}")),运行后查看可用的纸匣类型,再修改WhitePaperTray和BluePaperTray的值。 - 如果PDF是加密的,先结合问题1的方法解密后再传入打印逻辑,或者在C#里用iTextSharp先解密。
给你现有脚本的小优化
原脚本里的Get-Childitem -path $input -filter *.pdf -recurse | foreach在循环里重复执行,会导致每次处理一个文件时都遍历所有PDF,改成只处理当前$file1就行,避免重复操作:
# 原代码(有问题) # Get-Childitem -path $input -filter *.pdf -recurse | foreach { & $pdftk $_.Fullname cat 1-$SecondToLastPage output $output_f\"f_\"$_ } # 修改后 & $pdftk $file1.FullName cat 1-$SecondToLastPage output (Join-Path $output_f "f_$($file1.Name)")
另外打印时用Start-Process -Wait代替sleep,能确保Adobe完成打印后再删除文件,避免文件被占用:
Start-Process $adobe $arglist1 -Wait
内容的提问来源于stack exchange,提问作者Kurt Hoelsema
相关产品推荐
相关产品推荐

