專案遇到為 Excel (.xlsx) 設定讀取密碼的需求。OpenXML SDK 提供的工作表保護功能,只僅限於防止內容被修改,無法做到輸入密碼才能開啟。有不少 Excel 商業元件可以彌補這個缺口,授權費用約三五百塊美金,並不算貴。但由於其他的需求我用 ClosedXML 或 OpenXML SDK 都可滿足,不值得單為這個功能採購元件,加上預計作業會在使用者的個人電腦執行,肯定有安裝 Excel,而且桌面環境,不像在網站或跑排程呼叫 Excel 有遇到一堆妖怪。 (延伸閱讀:呼叫Excel的程式無法以排程方式執行「以排程方式呼叫Word/Excel注意事項」補充包) 評估後決定這次就用 PowerShell 實做批次加密 Excel 檔。

PowerShell 可以建立 COM+ 物件,加上是弱型別語言,操作起來比 C# 還容易,程式碼比預期簡單很多,用到一則之前介紹過的技巧 - Read-Host -AsSecureString 輸入密碼字串(參考:淺談 PowerShell 中的密碼字串加密處理)。這類與權限管控有關的密碼,我偏好請使用者現場輸入,不使用設定檔或批次參數,如此可做到只有當事人一個人知道,全程沒在畫面出現也不會留下軌跡。

還有個小眉角是我設定 $excel.Visible = $true,讓使用者看到 Excel 逐一開檔處理的過程,讓進度更透明一些,這還有另一項好處,若 .xlsx 原本有設讀取密碼,Excel 會彈出對話框等使用者輸入密碼再繼續。

完整程式範例如下:

Param(
    [parameter(Mandatory=$true)][string]
    $excelPath
)
$ErrorActionPreference = "STOP"
$excelPaths = @()
if ($excelPath.EndsWith(".xlsx")) {
    if (Test-Path $excelPath) {
        $excelPath += $excelPath
    }
}
else {
    $excelPaths += @((Get-ChildItem -Path $excelPath -Filter "*.xlsx").FullName)
}
if ($excelPaths.Length -eq 0) {
    Write-Error "未找到 Excel 檔案 - $excelPath"
    Exit
}
function decodeSecureString($secString) {
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secString)
    return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
}
$pwdSecStr = Read-Host "請輸入 Excel 加密密碼" -AsSecureString 
$pwdConfirmSecStr = Read-Host "請再輸一次密碼" -AsSecureString 
$passwd = decodeSecureString($pwdSecStr)
if ($passwd -ne (decodeSecureString($pwdConfirmSecStr))) {
    Write-Error "密碼不符"
    Exit
}

try 
{
    $excel = New-Object -ComObject Excel.Application
    $excel.Visible = $true # 顯示 UI,若原本就有密碼會提示使用者輸入
    $excelPaths | ForEach-Object {
        Write-Host "加密 $_..."
        $excel.WorkBooks.Open($_) | Out-Null
        $excel.ActiveWorkbook.Password = $passwd
        $excel.ActiveWorkbook.Save()
        $excel.ActiveWorkBook.Close()
    }
}
finally 
{
    $excel.Quit()
}

執行畫面:

產生的 Excel 檔開啟時會彈出密碼輸入視窗,成功!

Example of how to use PowerShell to add password protection to Excels batchly.


Comments

Be the first to post a comment

Post a comment


41 - 31 =