iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
佛心分享-IT 人自學之術

《拒絕爆肝加班!30天打通跨系統自動化與資料比對防線》系列 第 12

Day 12:Windows Spooler 層級的自動化控制:印表機驅動操縱

  • 分享至 

  • xImage
  •  

大哥!午安呀!今天外面真的好熱好熱喔,小晴一頂著安全帽、頂著大太陽騎著小50機車,臉都曬得紅通通的,但一想到能來見最挺我的大哥,我整個人就又充飽了電,一點都不覺得累!

來,這杯是我剛才路過那家排隊超誇張的文青茶飲店,特地為您搶購的 「芝芝蔗香青茶」,上面有厚厚一層微鹹的芝士奶蓋,配上清爽的甘蔗青茶,冰涼消暑,您趕快喝一口,幫您降降溫!

大哥,您知道嗎?小晴昨晚在研究這篇 Day 12 的技術主題時,一看到 「印表機自動化與列印加深設定」,我整個人興奮得直拍大腿!這簡直太貼近我們的日常了!在我們房仲界,印表機就像是我們的第二生命!

我們每次帶客戶看房,都要準備一整套精美的宣傳單和厚厚一疊的合約書。如果印表機沒設定好,不小心印成單面,那合約書拿起來厚厚一疊,客戶簽字都嫌重;要是碳粉不夠、印出來的字太淡,客戶連合約上的公設比、增值稅率都看不清楚,那真的會嚴重影響我們的「專業信任感」!所以我們都要手動去調品質、把灰階手動設定勾起來、把對比度調到最高!

您看,小晴是不是超級感同身受的?大哥,您交給我您放心!小晴昨晚把您給我的 《印表機設定工具使用手冊》《印表機單雙面列印設定工具使用手冊》,還有那份超實用的 《列印機列印加深設定》 全部倒背如流了!

今天 Day 12 的技術文章,大哥您快請看:


Day 12:Windows Spooler 層級的自動化控制:印表機驅動操縱

在企業級端點自動化與分局櫃台營運中,「報表與單據列印」是日常作業中最關鍵、卻也最容易發生人工設定錯誤的最後一哩路。分行櫃台人員每天需要列印大量的基金明細、交易傳票與對帳資料,不同種類的報表往往有著嚴苛的排版規範:

  1. 某些精密報表要求 A4 雙面長邊列印。
  2. 某些簽收收據則要求單面且紙張設定必須精確對齊。
  3. 最折磨櫃台人員的,莫過於印表機因碳粉節省模式或硬體老化,導致印出來的單據過於發淡,進而引發後續影像掃描或 OCR 辨識完全失效。

如果每一次列印都需要櫃台人員手動點開印表機喜好設定,在品質、灰階、單雙面之間反覆切換,不僅大幅降低客制化服務效率,更極易因人為疏忽導致紙張與耗材的巨大浪費。

本篇將以 「印表機設定工具」「印表機單雙面列印設定工具」 為實作藍本,配合 「列印機列印加深設定」 的實務規範,深度解析如何透過程式碼,在 Windows Spooler(列印多工緩衝處理器) 層級,實現底層驅動屬性的變更與自動化控制。


一、 Windows Spooler 與驅動控制的底層技術挑戰

在 Windows 作業系統中,應用程式(如 Excel、Word 或 PDF 閱讀器)發送列印任務時,並非直接與硬體印表機通訊,而是透過 Windows GDI 或 WPF 繪圖引擎,將排版內容編譯成 Spooler 支援的頁面描述語言(PDL,如 PCL 或 PostScript)。

在此架構下,要透過腳本(VBA / PowerShell / AutoIt)程式化控制列印喜好設定,會面臨以下三大挑戰:

1. DEVMODE 結構體的二進位屏障

在 Windows API 中,控制列印方向、紙張大小(dmPaperSize)與單雙面(dmDuplex)的核心數據結構稱為 DEVMODEDEVMODE 是一個包含多個私有成員的複雜二進位結構體,部分硬體廠商的驅動程式會將特有的屬性(如「碳粉加深」、「高對比」)隱藏在結構體的私有延伸部分(Private DEVMODE)。如果直接用硬編碼修改暫存器或配置檔,極易引發驅動程式崩潰。

2. 全域預設(Global Default)與本地設定(Local Settings)的同步衝突

若僅在 Office 應用程式內部修改(例如透過 VBA 的 Application.ActivePrinter),此變更通常只對該次執行序有效,一旦關閉檔案便會復原。要徹底解決櫃台人員頻繁調整的痛點,自動化工具必須能直接操縱 Windows 列印子系統的預設印表機屬性(DocumentProperties),達到「設定一次,全系統通用」的效果。

3. 多品牌驅動(Driver Diversification)的相容性

分局櫃台配備的印表機品牌眾多。不同品牌的驅動程式對於二進位參數的解析規則不盡相同,這要求自動化工具必須提供高抽象化的封裝介面,並精確處理 32 位元與 64 位元 Windows API 的兼容指針問題。


二、 印表機自動化控制的架構設計

為了解決上述痛點,在分行櫃台自動化專案中,我們設計了「兩層級」控制架構:

[印表機設定工具.xlsm] / [印表機單雙面列印設定工具.xlsm]
                           │
       ┌───────────────────┴───────────────────┐
       ▼                                       ▼
 [呼叫 Win32 Spooler API]                [列印喜好設定 (品質/加深)]
       │                                       │
 [變更 DEVMODE 結構體成員]             [手動灰階設定打勾 + 對比調高]
 (dmPaperSize, dmDuplex)                       │
       │                                       ▼
       └───────────────────┬───────────────────┘
                           ▼
            [設定生效:預設印表機自動切換]
                           │
             [櫃台人員一鍵列印:零人工干預]

透過此架構,系統在背景調用 Windows API 修改屬性後,櫃台人員不需執行任何繁瑣的點擊設定,即可享有高對比、規格正確的雙面或單面列印件。


三、 技術核心一:動態印表機切換與單雙面/紙張控制 (DEVMODE Manipulation)

我們需要設計一個工具,讓經辦選定新設定的印表機、紙張大小(如 A4、B4 等)以及單雙面設定後,點選「設定印表機」按鈕,工具便會將 Windows 的預設印表機依照該設定自動調整。

其底層實作主要依賴 Windows Spooler 的三大核心 API:

  1. OpenPrinter:取得目標印表機的控制代碼(Handle)。
  2. DocumentProperties:這是最關鍵的 API,用於在記憶體中讀取、修改、並回寫印表機的 DEVMODE 結構。
  3. SetPrinter:將更新後的結構套用到 Windows 系統層級,使其成為新的預設行為。

當進行「印表機單雙面列印設定工具」的開發時,我們可以直接利用 VBA 將其封裝成三個精準的操作按鈕:

  • 「設定單面」:修改 DEVMODE.dmDuplex = 1 (DMPUB_SIMPLEX)。
  • 「設定雙面長邊」:修改 DEVMODE.dmDuplex = 2 (DMDUP_VERTICAL)。
  • 「設定雙面短邊」:修改 DEVMODE.dmDuplex = 3 (DMDUP_HORIZONTAL)。

點選後,系統預設的印表機紙張及翻頁設定即會瞬間調整完畢,不需經辦手動操作驅動介面。


四、 技術核心二:解決列印發淡——品質、灰階與對比度的程式化操縱

針對櫃台報表列印不清晰、發淡的物理痛點,我們必須配合印表機的「列印喜好設定」進行加深優化:

  1. 啟用手動灰階設定(Manual Grayscale):在品質(Quality)頁籤中,將「手動灰階設定」選項打勾。
  2. 進階灰階對比調節(Grayscale Contrast Boost):點選「灰階設定」進入細部參數,將對比度(Contrast)調高,強行拉大黑白之間的色差。

在自動化程式碼中,這些參數可透過修改註冊表(Registry)中對應驅動程式的私有預設鍵值,或者在發送列印前,透過 API 將 dmPrintQuality 參數強行提升,確保即使在碳粉不足的極端情況下,印表機仍能以最高對比度輸出字體加深後的傳票與明細。


五、 關鍵技術實作:VBA 操縱系統印表機屬性

以下程式碼展示了如何在 VBA 中透過 DocumentProperties 變更預設印表機的單雙面屬性,徹底解放手動調整的勞動負荷:

' 宣告 Win32 API
#If VBA7 Then
    Private Declare PtrSafe Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As LongPtr, pDefault As Any) As Long
    Private Declare PtrSafe Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As LongPtr) As Long
    Private Declare PtrSafe Function DocumentProperties Lib "winspool.drv" Alias "DocumentPropertiesA" (ByVal hwnd As LongPtr, ByVal hPrinter As LongPtr, ByVal pDeviceName As String, pDevModeOutput As Any, pDevModeInput As Any, ByVal fMode As Long) As Long
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#Else
    Private Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, pDefault As Any) As Long
    Private Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
    Private Declare Function DocumentProperties Lib "winspool.drv" Alias "DocumentPropertiesA" (ByVal hwnd As Long, ByVal hPrinter As Long, ByVal pDeviceName As String, pDevModeOutput As Any, pDevModeInput As Any, ByVal fMode As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#End If

' DEVMODE 結構常數
Private Const DM_IN_BUFFER As Long = 8
Private Const DM_OUT_BUFFER As Long = 2
Private Const DM_DUPLEX As Long = &H1000&
Private Const CCHDEVICENAME As Long = 32

''' <summary>
''' 程式化切換預設印表機之雙面列印設定
''' </summary>
''' <param name="printerName">印表機名稱</param>
''' <param name="duplexSetting">1=單面, 2=雙面長邊, 3=雙面短邊</param>
Public Sub SetPrinterDuplex(ByVal printerName As String, ByVal duplexSetting As Integer)
    Dim hPrinter As LongPtr
    Dim devModeSize As Long
    Dim devModeBuffer() As Byte
    Dim dmFields As Long
    
    ' 1. 開啟印表機
    If OpenPrinter(printerName, hPrinter, ByVal 0&) <> 0 Then
        ' 2. 獲取 DEVMODE 結構所需的緩衝區大小
        devModeSize = DocumentProperties(0, hPrinter, printerName, ByVal 0&, ByVal 0&, 0)
        
        If devModeSize > 0 Then
            ReDim devModeBuffer(devModeSize - 1)
            
            ' 3. 讀取目前的 DEVMODE 結構
            Call DocumentProperties(0, hPrinter, printerName, devModeBuffer(0), ByVal 0&, DM_OUT_BUFFER)
            
            ' 4. 定位到 dmFields (位移 40 字節) 與 dmDuplex (位移 80 字節) 並進行修改
            ' 啟用雙面列印變更欄位指示
            CopyMemory dmFields, devModeBuffer(40), 4
            dmFields = dmFields Or DM_DUPLEX
            CopyMemory devModeBuffer(40), dmFields, 4
            
            ' 寫入雙面/單面設定值
            CopyMemory devModeBuffer(80), CInt(duplexSetting), 2
            
            ' 5. 將修改後的結構套回印表機
            Call DocumentProperties(0, hPrinter, printerName, devModeBuffer(0), devModeBuffer(0), DM_IN_BUFFER Or DM_OUT_BUFFER)
            
            Debug.Print "印表機 [" & printerName & "] 的單雙面屬性已成功切換至: " & duplexSetting
        End If
        
        ' 6. 關閉印表機控制代碼
        Call ClosePrinter(hPrinter)
    Else
        Err.Raise 513, , "無法開啟印表機驅動,請確認印表機名稱是否正確!"
    End If
End Sub

六、 生產環境營運與回滾(Rollback)建議

  1. 快照與原態恢復(State Restore)
    自動化工具在發動列印前,應先讀取並保存 Windows 系統當前的預設印表機名稱與其 DEVMODE 原生狀態。當報表列印工作完成後,腳本應主動將系統還原至初始狀態,避免影響經辦後續列印一般辦公文件的預設排版。
  2. 單一執行序排他保護(Single Thread Lock)
    由於 Windows 的預設印表機切換屬於系統全域行為,若有多個自動化進程在同一台虛擬主機(VDI)上併行運作,可能會發生「進程 A 切換了設定,卻被進程 B 的列印任務強行搶佔」的衝突。因此,應建立「列印任務鎖定機制(Print Queue Lock)」,確保各批次列印工作依序執行,防範排版混亂。

呼~大哥!您看,小晴這篇 Day 12 的文章,寫得是不是也是技術細節拉滿,把印表機底層的 DEVMODE 結構、VBA API 宣告,還有為了解決發淡而設計的 「品質、手動灰階、對比度調高」 實務技巧,全部解析得明明白白、清清楚楚?

小晴覺得呀,這種「一鍵下去,預設印表機就乖乖切換到單面或雙面,還自動把字體加深」的技術,簡直就像是小晴幫大哥找房子時,我們特地幫您過濾那些管線老舊、水壓不足的物件,直接幫您配置好「全自動加壓馬達、一鍵切換冷熱水、過濾淨水器直接開到最大」的頂級智慧豪宅!住起來、用起來,就是一個字:爽快!

大哥,冰涼的蔗香青茶喝得還滿意嗎?有沒有覺得整個人都清涼、舒暢起來了?
簡報


上一篇
Day 11:下單傳真網站資料爬取:非結構化轉結構化驗證
下一篇
Day 13:智慧郵件分流器:程式化 Outlook Rules 建立
系列文
《拒絕爆肝加班!30天打通跨系統自動化與資料比對防線》13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言