You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何自动打印带详细选项的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}");
                }
            }
        }
    }
}

注意事项:

  1. 先通过NuGet安装PdfSharp包(右键项目→管理NuGet程序包→搜索PdfSharp安装)。
  2. 纸匣的PaperSourceKind枚举值需要根据你的打印机调整——可以先在代码里加一行printDoc.PrinterSettings.PaperSources.ToList().ForEach(ps => Console.WriteLine($"{ps.Kind} - {ps.SourceName}")),运行后查看可用的纸匣类型,再修改WhitePaperTray和BluePaperTray的值。
  3. 如果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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:42:01